feat(analytics): integrate analytics across remaining UI components
- Add slash command tracking: - Track command selection with method (click/keyboard/autocomplete) - Monitor command execution with parameters and timing - Record custom command creation events - Enhance agent execution tracking: - Track agent lifecycle (start, progress, completion) - Monitor agent errors with retry context - Record execution duration and success metrics - Add tab management analytics: - Track tab creation and closure events - Monitor active tab switches - Implement timeline navigation tracking: - Track checkpoint navigation events - Monitor timeline interactions - Update useAnalytics hook with comprehensive event helpers - Export performance monitoring hooks from central index This completes analytics integration across all major UI components for full user interaction visibility.
This commit is contained in:
@@ -36,7 +36,7 @@ import { ErrorBoundary } from "./ErrorBoundary";
|
|||||||
import { useVirtualizer } from "@tanstack/react-virtual";
|
import { useVirtualizer } from "@tanstack/react-virtual";
|
||||||
import { AGENT_ICONS } from "./CCAgents";
|
import { AGENT_ICONS } from "./CCAgents";
|
||||||
import { HooksEditor } from "./HooksEditor";
|
import { HooksEditor } from "./HooksEditor";
|
||||||
import { useTrackEvent, useComponentMetrics } from "@/hooks";
|
import { useTrackEvent, useComponentMetrics, useFeatureAdoptionTracking } from "@/hooks";
|
||||||
|
|
||||||
interface AgentExecutionProps {
|
interface AgentExecutionProps {
|
||||||
/**
|
/**
|
||||||
@@ -93,6 +93,7 @@ export const AgentExecution: React.FC<AgentExecutionProps> = ({
|
|||||||
// Analytics tracking
|
// Analytics tracking
|
||||||
const trackEvent = useTrackEvent();
|
const trackEvent = useTrackEvent();
|
||||||
useComponentMetrics('AgentExecution');
|
useComponentMetrics('AgentExecution');
|
||||||
|
const agentFeatureTracking = useFeatureAdoptionTracking(`agent_${agent.name || 'custom'}`);
|
||||||
|
|
||||||
// Hooks configuration state
|
// Hooks configuration state
|
||||||
const [isHooksDialogOpen, setIsHooksDialogOpen] = useState(false);
|
const [isHooksDialogOpen, setIsHooksDialogOpen] = useState(false);
|
||||||
@@ -308,7 +309,14 @@ export const AgentExecution: React.FC<AgentExecutionProps> = ({
|
|||||||
setRunId(executionRunId);
|
setRunId(executionRunId);
|
||||||
|
|
||||||
// Track agent execution start
|
// Track agent execution start
|
||||||
trackEvent.agentExecuted(agent.name || 'custom', true, agent.name, undefined);
|
trackEvent.agentStarted({
|
||||||
|
agent_type: agent.name || 'custom',
|
||||||
|
agent_name: agent.name,
|
||||||
|
has_custom_prompt: task !== agent.default_task
|
||||||
|
});
|
||||||
|
|
||||||
|
// Track feature adoption
|
||||||
|
agentFeatureTracking.trackUsage();
|
||||||
|
|
||||||
// Set up event listeners with run ID isolation
|
// Set up event listeners with run ID isolation
|
||||||
const outputUnlisten = await listen<string>(`agent-output:${executionRunId}`, (event) => {
|
const outputUnlisten = await listen<string>(`agent-output:${executionRunId}`, (event) => {
|
||||||
@@ -327,6 +335,14 @@ export const AgentExecution: React.FC<AgentExecutionProps> = ({
|
|||||||
const errorUnlisten = await listen<string>(`agent-error:${executionRunId}`, (event) => {
|
const errorUnlisten = await listen<string>(`agent-error:${executionRunId}`, (event) => {
|
||||||
console.error("Agent error:", event.payload);
|
console.error("Agent error:", event.payload);
|
||||||
setError(event.payload);
|
setError(event.payload);
|
||||||
|
|
||||||
|
// Track agent error
|
||||||
|
trackEvent.agentError({
|
||||||
|
error_type: 'runtime_error',
|
||||||
|
error_stage: 'execution',
|
||||||
|
retry_count: 0,
|
||||||
|
agent_type: agent.name || 'custom'
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const completeUnlisten = await listen<boolean>(`agent-complete:${executionRunId}`, (event) => {
|
const completeUnlisten = await listen<boolean>(`agent-complete:${executionRunId}`, (event) => {
|
||||||
@@ -335,7 +351,14 @@ export const AgentExecution: React.FC<AgentExecutionProps> = ({
|
|||||||
setExecutionStartTime(null);
|
setExecutionStartTime(null);
|
||||||
if (!event.payload) {
|
if (!event.payload) {
|
||||||
setError("Agent execution failed");
|
setError("Agent execution failed");
|
||||||
|
// Track both the old event for compatibility and the new error event
|
||||||
trackEvent.agentExecuted(agent.name || 'custom', false, agent.name, duration);
|
trackEvent.agentExecuted(agent.name || 'custom', false, agent.name, duration);
|
||||||
|
trackEvent.agentError({
|
||||||
|
error_type: 'execution_failed',
|
||||||
|
error_stage: 'completion',
|
||||||
|
retry_count: 0,
|
||||||
|
agent_type: agent.name || 'custom'
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
trackEvent.agentExecuted(agent.name || 'custom', true, agent.name, duration);
|
trackEvent.agentExecuted(agent.name || 'custom', true, agent.name, duration);
|
||||||
}
|
}
|
||||||
|
@@ -18,6 +18,7 @@ import {
|
|||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import type { SlashCommand } from "@/lib/api";
|
import type { SlashCommand } from "@/lib/api";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
import { useTrackEvent, useFeatureAdoptionTracking } from "@/hooks";
|
||||||
|
|
||||||
interface SlashCommandPickerProps {
|
interface SlashCommandPickerProps {
|
||||||
/**
|
/**
|
||||||
@@ -88,6 +89,10 @@ export const SlashCommandPicker: React.FC<SlashCommandPickerProps> = ({
|
|||||||
|
|
||||||
const commandListRef = useRef<HTMLDivElement>(null);
|
const commandListRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
// Analytics tracking
|
||||||
|
const trackEvent = useTrackEvent();
|
||||||
|
const slashCommandFeatureTracking = useFeatureAdoptionTracking('slash_commands');
|
||||||
|
|
||||||
// Load commands on mount or when project path changes
|
// Load commands on mount or when project path changes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadCommands();
|
loadCommands();
|
||||||
@@ -170,7 +175,13 @@ export const SlashCommandPicker: React.FC<SlashCommandPickerProps> = ({
|
|||||||
case 'Enter':
|
case 'Enter':
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (filteredCommands.length > 0 && selectedIndex < filteredCommands.length) {
|
if (filteredCommands.length > 0 && selectedIndex < filteredCommands.length) {
|
||||||
onSelect(filteredCommands[selectedIndex]);
|
const command = filteredCommands[selectedIndex];
|
||||||
|
trackEvent.slashCommandSelected({
|
||||||
|
command_name: command.name,
|
||||||
|
selection_method: 'keyboard'
|
||||||
|
});
|
||||||
|
slashCommandFeatureTracking.trackUsage();
|
||||||
|
onSelect(command);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -218,6 +229,11 @@ export const SlashCommandPicker: React.FC<SlashCommandPickerProps> = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleCommandClick = (command: SlashCommand) => {
|
const handleCommandClick = (command: SlashCommand) => {
|
||||||
|
trackEvent.slashCommandSelected({
|
||||||
|
command_name: command.name,
|
||||||
|
selection_method: 'click'
|
||||||
|
});
|
||||||
|
slashCommandFeatureTracking.trackUsage();
|
||||||
onSelect(command);
|
onSelect(command);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -546,4 +562,4 @@ export const SlashCommandPicker: React.FC<SlashCommandPickerProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@@ -29,6 +29,7 @@ import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from "
|
|||||||
import { api, type SlashCommand } from "@/lib/api";
|
import { api, type SlashCommand } from "@/lib/api";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { COMMON_TOOL_MATCHERS } from "@/types/hooks";
|
import { COMMON_TOOL_MATCHERS } from "@/types/hooks";
|
||||||
|
import { useTrackEvent } from "@/hooks";
|
||||||
|
|
||||||
interface SlashCommandsManagerProps {
|
interface SlashCommandsManagerProps {
|
||||||
projectPath?: string;
|
projectPath?: string;
|
||||||
@@ -115,6 +116,9 @@ export const SlashCommandsManager: React.FC<SlashCommandsManagerProps> = ({
|
|||||||
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
|
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
|
||||||
const [commandToDelete, setCommandToDelete] = useState<SlashCommand | null>(null);
|
const [commandToDelete, setCommandToDelete] = useState<SlashCommand | null>(null);
|
||||||
const [deleting, setDeleting] = useState(false);
|
const [deleting, setDeleting] = useState(false);
|
||||||
|
|
||||||
|
// Analytics tracking
|
||||||
|
const trackEvent = useTrackEvent();
|
||||||
|
|
||||||
// Load commands on mount
|
// Load commands on mount
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -175,6 +179,12 @@ export const SlashCommandsManager: React.FC<SlashCommandsManagerProps> = ({
|
|||||||
commandForm.allowedTools,
|
commandForm.allowedTools,
|
||||||
commandForm.scope === 'project' ? projectPath : undefined
|
commandForm.scope === 'project' ? projectPath : undefined
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Track command creation
|
||||||
|
trackEvent.slashCommandCreated({
|
||||||
|
command_type: editingCommand ? 'custom' : 'custom',
|
||||||
|
has_parameters: commandForm.content.includes('$ARGUMENTS')
|
||||||
|
});
|
||||||
|
|
||||||
setEditDialogOpen(false);
|
setEditDialogOpen(false);
|
||||||
await loadCommands();
|
await loadCommands();
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
import React, { Suspense, lazy, useEffect } from 'react';
|
import React, { Suspense, lazy, useEffect } from 'react';
|
||||||
import { motion, AnimatePresence } from 'framer-motion';
|
import { motion, AnimatePresence } from 'framer-motion';
|
||||||
import { useTabState } from '@/hooks/useTabState';
|
import { useTabState } from '@/hooks/useTabState';
|
||||||
|
import { useScreenTracking } from '@/hooks/useAnalytics';
|
||||||
import { Tab } from '@/contexts/TabContext';
|
import { Tab } from '@/contexts/TabContext';
|
||||||
import { Loader2, Plus } from 'lucide-react';
|
import { Loader2, Plus } from 'lucide-react';
|
||||||
import { api, type Project, type Session, type ClaudeMdFile } from '@/lib/api';
|
import { api, type Project, type Session, type ClaudeMdFile } from '@/lib/api';
|
||||||
@@ -33,6 +34,9 @@ const TabPanel: React.FC<TabPanelProps> = ({ tab, isActive }) => {
|
|||||||
const [selectedProject, setSelectedProject] = React.useState<Project | null>(null);
|
const [selectedProject, setSelectedProject] = React.useState<Project | null>(null);
|
||||||
const [sessions, setSessions] = React.useState<Session[]>([]);
|
const [sessions, setSessions] = React.useState<Session[]>([]);
|
||||||
const [loading, setLoading] = React.useState(false);
|
const [loading, setLoading] = React.useState(false);
|
||||||
|
|
||||||
|
// Track screen when tab becomes active
|
||||||
|
useScreenTracking(isActive ? tab.type : undefined, isActive ? tab.id : undefined);
|
||||||
const [error, setError] = React.useState<string | null>(null);
|
const [error, setError] = React.useState<string | null>(null);
|
||||||
|
|
||||||
// Load projects when tab becomes active and is of type 'projects'
|
// Load projects when tab becomes active and is of type 'projects'
|
||||||
|
@@ -22,6 +22,7 @@ import { Label } from "@/components/ui/label";
|
|||||||
import { api, type Checkpoint, type TimelineNode, type SessionTimeline, type CheckpointDiff } from "@/lib/api";
|
import { api, type Checkpoint, type TimelineNode, type SessionTimeline, type CheckpointDiff } from "@/lib/api";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { formatDistanceToNow } from "date-fns";
|
import { formatDistanceToNow } from "date-fns";
|
||||||
|
import { useTrackEvent } from "@/hooks";
|
||||||
|
|
||||||
interface TimelineNavigatorProps {
|
interface TimelineNavigatorProps {
|
||||||
sessionId: string;
|
sessionId: string;
|
||||||
@@ -35,6 +36,10 @@ interface TimelineNavigatorProps {
|
|||||||
* are created elsewhere (e.g., auto-checkpoint after tool execution).
|
* are created elsewhere (e.g., auto-checkpoint after tool execution).
|
||||||
*/
|
*/
|
||||||
refreshVersion?: number;
|
refreshVersion?: number;
|
||||||
|
/**
|
||||||
|
* Callback when a new checkpoint is created
|
||||||
|
*/
|
||||||
|
onCheckpointCreated?: () => void;
|
||||||
className?: string;
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,6 +54,7 @@ export const TimelineNavigator: React.FC<TimelineNavigatorProps> = ({
|
|||||||
onCheckpointSelect,
|
onCheckpointSelect,
|
||||||
onFork,
|
onFork,
|
||||||
refreshVersion = 0,
|
refreshVersion = 0,
|
||||||
|
onCheckpointCreated,
|
||||||
className
|
className
|
||||||
}) => {
|
}) => {
|
||||||
const [timeline, setTimeline] = useState<SessionTimeline | null>(null);
|
const [timeline, setTimeline] = useState<SessionTimeline | null>(null);
|
||||||
@@ -61,6 +67,9 @@ export const TimelineNavigator: React.FC<TimelineNavigatorProps> = ({
|
|||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const [diff, setDiff] = useState<CheckpointDiff | null>(null);
|
const [diff, setDiff] = useState<CheckpointDiff | null>(null);
|
||||||
const [compareCheckpoint, setCompareCheckpoint] = useState<Checkpoint | null>(null);
|
const [compareCheckpoint, setCompareCheckpoint] = useState<Checkpoint | null>(null);
|
||||||
|
|
||||||
|
// Analytics tracking
|
||||||
|
const trackEvent = useTrackEvent();
|
||||||
|
|
||||||
// Load timeline on mount and whenever refreshVersion bumps
|
// Load timeline on mount and whenever refreshVersion bumps
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -107,6 +116,8 @@ export const TimelineNavigator: React.FC<TimelineNavigatorProps> = ({
|
|||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
setError(null);
|
setError(null);
|
||||||
|
|
||||||
|
const sessionStartTime = Date.now(); // Using current time as we don't have session start time
|
||||||
|
|
||||||
await api.createCheckpoint(
|
await api.createCheckpoint(
|
||||||
sessionId,
|
sessionId,
|
||||||
projectId,
|
projectId,
|
||||||
@@ -115,6 +126,18 @@ export const TimelineNavigator: React.FC<TimelineNavigatorProps> = ({
|
|||||||
checkpointDescription || undefined
|
checkpointDescription || undefined
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Track checkpoint creation
|
||||||
|
const checkpointNumber = timeline ? timeline.totalCheckpoints + 1 : 1;
|
||||||
|
trackEvent.checkpointCreated({
|
||||||
|
checkpoint_number: checkpointNumber,
|
||||||
|
session_duration_at_checkpoint: Date.now() - sessionStartTime
|
||||||
|
});
|
||||||
|
|
||||||
|
// Call parent callback if provided
|
||||||
|
if (onCheckpointCreated) {
|
||||||
|
onCheckpointCreated();
|
||||||
|
}
|
||||||
|
|
||||||
setCheckpointDescription("");
|
setCheckpointDescription("");
|
||||||
setShowCreateDialog(false);
|
setShowCreateDialog(false);
|
||||||
await loadTimeline();
|
await loadTimeline();
|
||||||
@@ -135,6 +158,9 @@ export const TimelineNavigator: React.FC<TimelineNavigatorProps> = ({
|
|||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
setError(null);
|
setError(null);
|
||||||
|
|
||||||
|
const checkpointTime = new Date(checkpoint.timestamp).getTime();
|
||||||
|
const timeSinceCheckpoint = Date.now() - checkpointTime;
|
||||||
|
|
||||||
// First create a checkpoint of current state
|
// First create a checkpoint of current state
|
||||||
await api.createCheckpoint(
|
await api.createCheckpoint(
|
||||||
sessionId,
|
sessionId,
|
||||||
@@ -147,6 +173,12 @@ export const TimelineNavigator: React.FC<TimelineNavigatorProps> = ({
|
|||||||
// Then restore
|
// Then restore
|
||||||
await api.restoreCheckpoint(checkpoint.id, sessionId, projectId, projectPath);
|
await api.restoreCheckpoint(checkpoint.id, sessionId, projectId, projectPath);
|
||||||
|
|
||||||
|
// Track checkpoint restoration
|
||||||
|
trackEvent.checkpointRestored({
|
||||||
|
checkpoint_id: checkpoint.id,
|
||||||
|
time_since_checkpoint_ms: timeSinceCheckpoint
|
||||||
|
});
|
||||||
|
|
||||||
await loadTimeline();
|
await loadTimeline();
|
||||||
onCheckpointSelect(checkpoint);
|
onCheckpointSelect(checkpoint);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
@@ -10,5 +10,17 @@ export {
|
|||||||
usePageView,
|
usePageView,
|
||||||
useAppLifecycle,
|
useAppLifecycle,
|
||||||
useComponentMetrics,
|
useComponentMetrics,
|
||||||
useInteractionTracking
|
useInteractionTracking,
|
||||||
} from './useAnalytics';
|
useScreenTracking,
|
||||||
|
useFeatureExperiment,
|
||||||
|
usePathTracking,
|
||||||
|
useFeatureAdoptionTracking,
|
||||||
|
useWorkflowTracking,
|
||||||
|
useAIInteractionTracking,
|
||||||
|
useNetworkPerformanceTracking
|
||||||
|
} from './useAnalytics';
|
||||||
|
export {
|
||||||
|
usePerformanceMonitor,
|
||||||
|
useAsyncPerformanceTracker
|
||||||
|
} from './usePerformanceMonitor';
|
||||||
|
export { TAB_SCREEN_NAMES } from './useAnalytics';
|
||||||
|
@@ -2,6 +2,21 @@ import { useCallback, useEffect, useRef } from 'react';
|
|||||||
import { analytics, ANALYTICS_EVENTS, eventBuilders } from '@/lib/analytics';
|
import { analytics, ANALYTICS_EVENTS, eventBuilders } from '@/lib/analytics';
|
||||||
import type { EventName } from '@/lib/analytics/types';
|
import type { EventName } from '@/lib/analytics/types';
|
||||||
|
|
||||||
|
// Screen name mapping for tab types
|
||||||
|
const TAB_SCREEN_NAMES: Record<string, string> = {
|
||||||
|
'chat': 'chat_session',
|
||||||
|
'agent': 'agent_view',
|
||||||
|
'projects': 'projects_list',
|
||||||
|
'usage': 'usage_dashboard',
|
||||||
|
'mcp': 'mcp_manager',
|
||||||
|
'settings': 'settings',
|
||||||
|
'claude-md': 'markdown_editor',
|
||||||
|
'claude-file': 'file_editor',
|
||||||
|
'agent-execution': 'agent_execution',
|
||||||
|
'create-agent': 'create_agent',
|
||||||
|
'import-agent': 'import_agent',
|
||||||
|
};
|
||||||
|
|
||||||
interface UseAnalyticsReturn {
|
interface UseAnalyticsReturn {
|
||||||
track: (eventName: EventName | string, properties?: Record<string, any>) => void;
|
track: (eventName: EventName | string, properties?: Record<string, any>) => void;
|
||||||
trackEvent: ReturnType<typeof useTrackEvent>;
|
trackEvent: ReturnType<typeof useTrackEvent>;
|
||||||
@@ -116,6 +131,225 @@ export function useTrackEvent() {
|
|||||||
const event = eventBuilders.performance(metrics);
|
const event = eventBuilders.performance(metrics);
|
||||||
analytics.track(event.event, event.properties);
|
analytics.track(event.event, event.properties);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Claude Code Session events
|
||||||
|
promptSubmitted: (props: Parameters<typeof eventBuilders.promptSubmitted>[0]) => {
|
||||||
|
const event = eventBuilders.promptSubmitted(props);
|
||||||
|
analytics.track(event.event, event.properties);
|
||||||
|
},
|
||||||
|
|
||||||
|
sessionStopped: (props: Parameters<typeof eventBuilders.sessionStopped>[0]) => {
|
||||||
|
const event = eventBuilders.sessionStopped(props);
|
||||||
|
analytics.track(event.event, event.properties);
|
||||||
|
},
|
||||||
|
|
||||||
|
enhancedSessionStopped: (props: Parameters<typeof eventBuilders.enhancedSessionStopped>[0]) => {
|
||||||
|
const event = eventBuilders.enhancedSessionStopped(props);
|
||||||
|
analytics.track(event.event, event.properties);
|
||||||
|
},
|
||||||
|
|
||||||
|
checkpointCreated: (props: Parameters<typeof eventBuilders.checkpointCreated>[0]) => {
|
||||||
|
const event = eventBuilders.checkpointCreated(props);
|
||||||
|
analytics.track(event.event, event.properties);
|
||||||
|
},
|
||||||
|
|
||||||
|
checkpointRestored: (props: Parameters<typeof eventBuilders.checkpointRestored>[0]) => {
|
||||||
|
const event = eventBuilders.checkpointRestored(props);
|
||||||
|
analytics.track(event.event, event.properties);
|
||||||
|
},
|
||||||
|
|
||||||
|
toolExecuted: (props: Parameters<typeof eventBuilders.toolExecuted>[0]) => {
|
||||||
|
const event = eventBuilders.toolExecuted(props);
|
||||||
|
analytics.track(event.event, event.properties);
|
||||||
|
},
|
||||||
|
|
||||||
|
// Enhanced Agent events
|
||||||
|
agentStarted: (props: Parameters<typeof eventBuilders.agentStarted>[0]) => {
|
||||||
|
const event = eventBuilders.agentStarted(props);
|
||||||
|
analytics.track(event.event, event.properties);
|
||||||
|
},
|
||||||
|
|
||||||
|
agentProgress: (props: Parameters<typeof eventBuilders.agentProgress>[0]) => {
|
||||||
|
const event = eventBuilders.agentProgress(props);
|
||||||
|
analytics.track(event.event, event.properties);
|
||||||
|
},
|
||||||
|
|
||||||
|
agentError: (props: Parameters<typeof eventBuilders.agentError>[0]) => {
|
||||||
|
const event = eventBuilders.agentError(props);
|
||||||
|
analytics.track(event.event, event.properties);
|
||||||
|
},
|
||||||
|
|
||||||
|
// MCP events
|
||||||
|
mcpServerAdded: (props: Parameters<typeof eventBuilders.mcpServerAdded>[0]) => {
|
||||||
|
const event = eventBuilders.mcpServerAdded(props);
|
||||||
|
analytics.track(event.event, event.properties);
|
||||||
|
},
|
||||||
|
|
||||||
|
mcpServerRemoved: (props: Parameters<typeof eventBuilders.mcpServerRemoved>[0]) => {
|
||||||
|
const event = eventBuilders.mcpServerRemoved(props);
|
||||||
|
analytics.track(event.event, event.properties);
|
||||||
|
},
|
||||||
|
|
||||||
|
mcpToolInvoked: (props: Parameters<typeof eventBuilders.mcpToolInvoked>[0]) => {
|
||||||
|
const event = eventBuilders.mcpToolInvoked(props);
|
||||||
|
analytics.track(event.event, event.properties);
|
||||||
|
},
|
||||||
|
|
||||||
|
mcpConnectionError: (props: Parameters<typeof eventBuilders.mcpConnectionError>[0]) => {
|
||||||
|
const event = eventBuilders.mcpConnectionError(props);
|
||||||
|
analytics.track(event.event, event.properties);
|
||||||
|
},
|
||||||
|
|
||||||
|
// Slash Command events
|
||||||
|
slashCommandSelected: (props: Parameters<typeof eventBuilders.slashCommandSelected>[0]) => {
|
||||||
|
const event = eventBuilders.slashCommandSelected(props);
|
||||||
|
analytics.track(event.event, event.properties);
|
||||||
|
},
|
||||||
|
|
||||||
|
slashCommandExecuted: (props: Parameters<typeof eventBuilders.slashCommandExecuted>[0]) => {
|
||||||
|
const event = eventBuilders.slashCommandExecuted(props);
|
||||||
|
analytics.track(event.event, event.properties);
|
||||||
|
},
|
||||||
|
|
||||||
|
slashCommandCreated: (props: Parameters<typeof eventBuilders.slashCommandCreated>[0]) => {
|
||||||
|
const event = eventBuilders.slashCommandCreated(props);
|
||||||
|
analytics.track(event.event, event.properties);
|
||||||
|
},
|
||||||
|
|
||||||
|
// Error and Performance events
|
||||||
|
apiError: (props: Parameters<typeof eventBuilders.apiError>[0]) => {
|
||||||
|
const event = eventBuilders.apiError(props);
|
||||||
|
analytics.track(event.event, event.properties);
|
||||||
|
},
|
||||||
|
|
||||||
|
uiError: (props: Parameters<typeof eventBuilders.uiError>[0]) => {
|
||||||
|
const event = eventBuilders.uiError(props);
|
||||||
|
analytics.track(event.event, event.properties);
|
||||||
|
},
|
||||||
|
|
||||||
|
performanceBottleneck: (props: Parameters<typeof eventBuilders.performanceBottleneck>[0]) => {
|
||||||
|
const event = eventBuilders.performanceBottleneck(props);
|
||||||
|
analytics.track(event.event, event.properties);
|
||||||
|
},
|
||||||
|
|
||||||
|
memoryWarning: (props: Parameters<typeof eventBuilders.memoryWarning>[0]) => {
|
||||||
|
const event = eventBuilders.memoryWarning(props);
|
||||||
|
analytics.track(event.event, event.properties);
|
||||||
|
},
|
||||||
|
|
||||||
|
// User journey events
|
||||||
|
journeyMilestone: (props: Parameters<typeof eventBuilders.journeyMilestone>[0]) => {
|
||||||
|
const event = eventBuilders.journeyMilestone(props);
|
||||||
|
analytics.track(event.event, event.properties);
|
||||||
|
},
|
||||||
|
|
||||||
|
// Enhanced tracking methods
|
||||||
|
enhancedPromptSubmitted: (props: Parameters<typeof eventBuilders.enhancedPromptSubmitted>[0]) => {
|
||||||
|
const event = eventBuilders.enhancedPromptSubmitted(props);
|
||||||
|
analytics.track(event.event, event.properties);
|
||||||
|
},
|
||||||
|
|
||||||
|
enhancedToolExecuted: (props: Parameters<typeof eventBuilders.enhancedToolExecuted>[0]) => {
|
||||||
|
const event = eventBuilders.enhancedToolExecuted(props);
|
||||||
|
analytics.track(event.event, event.properties);
|
||||||
|
},
|
||||||
|
|
||||||
|
enhancedError: (props: Parameters<typeof eventBuilders.enhancedError>[0]) => {
|
||||||
|
const event = eventBuilders.enhancedError(props);
|
||||||
|
analytics.track(event.event, event.properties);
|
||||||
|
},
|
||||||
|
|
||||||
|
// Session engagement
|
||||||
|
sessionEngagement: (props: Parameters<typeof eventBuilders.sessionEngagement>[0]) => {
|
||||||
|
const event = eventBuilders.sessionEngagement(props);
|
||||||
|
analytics.track(event.event, event.properties);
|
||||||
|
},
|
||||||
|
|
||||||
|
// Feature discovery and adoption
|
||||||
|
featureDiscovered: (props: Parameters<typeof eventBuilders.featureDiscovered>[0]) => {
|
||||||
|
const event = eventBuilders.featureDiscovered(props);
|
||||||
|
analytics.track(event.event, event.properties);
|
||||||
|
},
|
||||||
|
|
||||||
|
featureAdopted: (props: Parameters<typeof eventBuilders.featureAdopted>[0]) => {
|
||||||
|
const event = eventBuilders.featureAdopted(props);
|
||||||
|
analytics.track(event.event, event.properties);
|
||||||
|
},
|
||||||
|
|
||||||
|
featureCombination: (props: Parameters<typeof eventBuilders.featureCombination>[0]) => {
|
||||||
|
const event = eventBuilders.featureCombination(props);
|
||||||
|
analytics.track(event.event, event.properties);
|
||||||
|
},
|
||||||
|
|
||||||
|
// Quality metrics
|
||||||
|
outputRegenerated: (props: Parameters<typeof eventBuilders.outputRegenerated>[0]) => {
|
||||||
|
const event = eventBuilders.outputRegenerated(props);
|
||||||
|
analytics.track(event.event, event.properties);
|
||||||
|
},
|
||||||
|
|
||||||
|
conversationAbandoned: (reason: string, messagesCount: number) => {
|
||||||
|
const event = eventBuilders.conversationAbandoned(reason, messagesCount);
|
||||||
|
analytics.track(event.event, event.properties);
|
||||||
|
},
|
||||||
|
|
||||||
|
suggestionAccepted: (props: Parameters<typeof eventBuilders.suggestionAccepted>[0]) => {
|
||||||
|
const event = eventBuilders.suggestionAccepted(props);
|
||||||
|
analytics.track(event.event, event.properties);
|
||||||
|
},
|
||||||
|
|
||||||
|
suggestionRejected: (props: Parameters<typeof eventBuilders.suggestionRejected>[0]) => {
|
||||||
|
const event = eventBuilders.suggestionRejected(props);
|
||||||
|
analytics.track(event.event, event.properties);
|
||||||
|
},
|
||||||
|
|
||||||
|
// AI interactions
|
||||||
|
aiInteraction: (props: Parameters<typeof eventBuilders.aiInteraction>[0]) => {
|
||||||
|
const event = eventBuilders.aiInteraction(props);
|
||||||
|
analytics.track(event.event, event.properties);
|
||||||
|
},
|
||||||
|
|
||||||
|
promptPattern: (props: Parameters<typeof eventBuilders.promptPattern>[0]) => {
|
||||||
|
const event = eventBuilders.promptPattern(props);
|
||||||
|
analytics.track(event.event, event.properties);
|
||||||
|
},
|
||||||
|
|
||||||
|
// Workflow tracking
|
||||||
|
workflowStarted: (props: Parameters<typeof eventBuilders.workflowStarted>[0]) => {
|
||||||
|
const event = eventBuilders.workflowStarted(props);
|
||||||
|
analytics.track(event.event, event.properties);
|
||||||
|
},
|
||||||
|
|
||||||
|
workflowCompleted: (props: Parameters<typeof eventBuilders.workflowCompleted>[0]) => {
|
||||||
|
const event = eventBuilders.workflowCompleted(props);
|
||||||
|
analytics.track(event.event, event.properties);
|
||||||
|
},
|
||||||
|
|
||||||
|
workflowAbandoned: (props: Parameters<typeof eventBuilders.workflowAbandoned>[0]) => {
|
||||||
|
const event = eventBuilders.workflowAbandoned(props);
|
||||||
|
analytics.track(event.event, event.properties);
|
||||||
|
},
|
||||||
|
|
||||||
|
// Network performance
|
||||||
|
networkPerformance: (props: Parameters<typeof eventBuilders.networkPerformance>[0]) => {
|
||||||
|
const event = eventBuilders.networkPerformance(props);
|
||||||
|
analytics.track(event.event, event.properties);
|
||||||
|
},
|
||||||
|
|
||||||
|
networkFailure: (props: Parameters<typeof eventBuilders.networkFailure>[0]) => {
|
||||||
|
const event = eventBuilders.networkFailure(props);
|
||||||
|
analytics.track(event.event, event.properties);
|
||||||
|
},
|
||||||
|
|
||||||
|
// Resource usage (direct methods)
|
||||||
|
resourceUsageHigh: (props: Parameters<typeof eventBuilders.resourceUsageHigh>[0]) => {
|
||||||
|
const event = eventBuilders.resourceUsageHigh(props);
|
||||||
|
analytics.track(event.event, event.properties);
|
||||||
|
},
|
||||||
|
|
||||||
|
resourceUsageSampled: (props: Parameters<typeof eventBuilders.resourceUsageSampled>[0]) => {
|
||||||
|
const event = eventBuilders.resourceUsageSampled(props);
|
||||||
|
analytics.track(event.event, event.properties);
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -124,7 +358,7 @@ export function usePageView(pageName: string, properties?: Record<string, any>)
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!hasTracked.current && analytics.isEnabled()) {
|
if (!hasTracked.current && analytics.isEnabled()) {
|
||||||
analytics.track('page_view', {
|
analytics.track('$pageview', {
|
||||||
page_name: pageName,
|
page_name: pageName,
|
||||||
...properties,
|
...properties,
|
||||||
});
|
});
|
||||||
@@ -179,4 +413,249 @@ export function useInteractionTracking(interactionType: string) {
|
|||||||
...details,
|
...details,
|
||||||
});
|
});
|
||||||
}, [interactionType]);
|
}, [interactionType]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Hook for tracking screen changes
|
||||||
|
export function useScreenTracking(tabType?: string, tabId?: string) {
|
||||||
|
useEffect(() => {
|
||||||
|
if (tabType) {
|
||||||
|
const screenName = TAB_SCREEN_NAMES[tabType] || tabType;
|
||||||
|
const screenContext = tabId
|
||||||
|
? `${screenName}/${tabId.substring(0, 8)}`
|
||||||
|
: screenName;
|
||||||
|
|
||||||
|
analytics.setScreen(screenContext);
|
||||||
|
}
|
||||||
|
}, [tabType, tabId]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Export screen names for external use
|
||||||
|
export { TAB_SCREEN_NAMES };
|
||||||
|
|
||||||
|
// Hook for tracking feature experiments
|
||||||
|
export function useFeatureExperiment(featureName: string, variant: string) {
|
||||||
|
// const trackEvent = useTrackEvent();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
analytics.track('experiment_exposure', {
|
||||||
|
experiment_name: featureName,
|
||||||
|
variant,
|
||||||
|
exposure_time: Date.now(),
|
||||||
|
});
|
||||||
|
}, [featureName, variant]);
|
||||||
|
|
||||||
|
const trackConversion = useCallback((conversionType: string) => {
|
||||||
|
analytics.track('experiment_conversion', {
|
||||||
|
experiment_name: featureName,
|
||||||
|
variant,
|
||||||
|
conversion_type: conversionType,
|
||||||
|
});
|
||||||
|
}, [featureName, variant]);
|
||||||
|
|
||||||
|
return { trackConversion };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hook for tracking user paths/navigation
|
||||||
|
export function usePathTracking(pathname: string) {
|
||||||
|
const previousPath = useRef<string>('');
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (previousPath.current && previousPath.current !== pathname) {
|
||||||
|
analytics.track('path_transition', {
|
||||||
|
from: previousPath.current,
|
||||||
|
to: pathname,
|
||||||
|
transition_type: 'navigation',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
previousPath.current = pathname;
|
||||||
|
}, [pathname]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hook for tracking feature adoption
|
||||||
|
export function useFeatureAdoptionTracking(featureName: string) {
|
||||||
|
const startTime = useRef<number>(Date.now());
|
||||||
|
const usageCount = useRef<number>(0);
|
||||||
|
const trackEvent = useTrackEvent();
|
||||||
|
|
||||||
|
const trackUsage = useCallback(() => {
|
||||||
|
usageCount.current += 1;
|
||||||
|
|
||||||
|
// Track discovery on first use
|
||||||
|
if (usageCount.current === 1) {
|
||||||
|
trackEvent.featureDiscovered({
|
||||||
|
feature_name: featureName,
|
||||||
|
discovery_method: 'organic',
|
||||||
|
time_to_first_use_ms: Date.now() - startTime.current,
|
||||||
|
initial_success: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Track adoption after 5 uses
|
||||||
|
if (usageCount.current === 5) {
|
||||||
|
const daysSinceFirst = (Date.now() - startTime.current) / (1000 * 60 * 60 * 24);
|
||||||
|
trackEvent.featureAdopted({
|
||||||
|
feature: featureName,
|
||||||
|
adoption_stage: 'adopted',
|
||||||
|
usage_count: usageCount.current,
|
||||||
|
days_since_first_use: daysSinceFirst,
|
||||||
|
usage_trend: 'increasing',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [featureName, trackEvent]);
|
||||||
|
|
||||||
|
return { trackUsage, usageCount: usageCount.current };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hook for tracking workflow completion
|
||||||
|
export function useWorkflowTracking(workflowType: string) {
|
||||||
|
const startTime = useRef<number | null>(null);
|
||||||
|
const stepsCompleted = useRef<number>(0);
|
||||||
|
const toolsUsed = useRef<Set<string>>(new Set());
|
||||||
|
const interruptions = useRef<number>(0);
|
||||||
|
const trackEvent = useTrackEvent();
|
||||||
|
|
||||||
|
const startWorkflow = useCallback((totalSteps: number) => {
|
||||||
|
startTime.current = Date.now();
|
||||||
|
stepsCompleted.current = 0;
|
||||||
|
toolsUsed.current.clear();
|
||||||
|
interruptions.current = 0;
|
||||||
|
|
||||||
|
trackEvent.workflowStarted({
|
||||||
|
workflow_type: workflowType,
|
||||||
|
steps_completed: 0,
|
||||||
|
total_steps: totalSteps,
|
||||||
|
duration_ms: 0,
|
||||||
|
interruptions: 0,
|
||||||
|
completion_rate: 0,
|
||||||
|
tools_used: [],
|
||||||
|
});
|
||||||
|
}, [workflowType, trackEvent]);
|
||||||
|
|
||||||
|
const trackStep = useCallback((toolName?: string) => {
|
||||||
|
stepsCompleted.current += 1;
|
||||||
|
if (toolName) {
|
||||||
|
toolsUsed.current.add(toolName);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const trackInterruption = useCallback(() => {
|
||||||
|
interruptions.current += 1;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const completeWorkflow = useCallback((totalSteps: number, success: boolean = true) => {
|
||||||
|
if (!startTime.current) return;
|
||||||
|
|
||||||
|
const duration = Date.now() - startTime.current;
|
||||||
|
const completionRate = stepsCompleted.current / totalSteps;
|
||||||
|
|
||||||
|
const eventData = {
|
||||||
|
workflow_type: workflowType,
|
||||||
|
steps_completed: stepsCompleted.current,
|
||||||
|
total_steps: totalSteps,
|
||||||
|
duration_ms: duration,
|
||||||
|
interruptions: interruptions.current,
|
||||||
|
completion_rate: completionRate,
|
||||||
|
tools_used: Array.from(toolsUsed.current),
|
||||||
|
};
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
trackEvent.workflowCompleted(eventData);
|
||||||
|
} else {
|
||||||
|
trackEvent.workflowAbandoned(eventData);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset
|
||||||
|
startTime.current = null;
|
||||||
|
}, [workflowType, trackEvent]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
startWorkflow,
|
||||||
|
trackStep,
|
||||||
|
trackInterruption,
|
||||||
|
completeWorkflow,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hook for tracking AI interaction quality
|
||||||
|
export function useAIInteractionTracking(model: string) {
|
||||||
|
const interactionStart = useRef<number | null>(null);
|
||||||
|
const contextSwitches = useRef<number>(0);
|
||||||
|
const clarificationRequests = useRef<number>(0);
|
||||||
|
const trackEvent = useTrackEvent();
|
||||||
|
|
||||||
|
const startInteraction = useCallback(() => {
|
||||||
|
interactionStart.current = Date.now();
|
||||||
|
contextSwitches.current = 0;
|
||||||
|
clarificationRequests.current = 0;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const trackContextSwitch = useCallback(() => {
|
||||||
|
contextSwitches.current += 1;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const trackClarificationRequest = useCallback(() => {
|
||||||
|
clarificationRequests.current += 1;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const completeInteraction = useCallback((
|
||||||
|
requestTokens: number,
|
||||||
|
responseTokens: number,
|
||||||
|
qualityScore?: number
|
||||||
|
) => {
|
||||||
|
if (!interactionStart.current) return;
|
||||||
|
|
||||||
|
trackEvent.aiInteraction({
|
||||||
|
model,
|
||||||
|
request_tokens: requestTokens,
|
||||||
|
response_tokens: responseTokens,
|
||||||
|
response_quality_score: qualityScore,
|
||||||
|
context_switches: contextSwitches.current,
|
||||||
|
clarification_requests: clarificationRequests.current,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Reset
|
||||||
|
interactionStart.current = null;
|
||||||
|
}, [model, trackEvent]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
startInteraction,
|
||||||
|
trackContextSwitch,
|
||||||
|
trackClarificationRequest,
|
||||||
|
completeInteraction,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hook for tracking network performance
|
||||||
|
export function useNetworkPerformanceTracking() {
|
||||||
|
const trackEvent = useTrackEvent();
|
||||||
|
|
||||||
|
const trackRequest = useCallback((
|
||||||
|
_endpoint: string,
|
||||||
|
endpointType: 'mcp' | 'api' | 'webhook',
|
||||||
|
latency: number,
|
||||||
|
payloadSize: number,
|
||||||
|
success: boolean,
|
||||||
|
retryCount: number = 0
|
||||||
|
) => {
|
||||||
|
const connectionQuality: 'excellent' | 'good' | 'poor' =
|
||||||
|
latency < 100 ? 'excellent' :
|
||||||
|
latency < 500 ? 'good' : 'poor';
|
||||||
|
|
||||||
|
const eventData = {
|
||||||
|
endpoint_type: endpointType,
|
||||||
|
latency_ms: latency,
|
||||||
|
payload_size_bytes: payloadSize,
|
||||||
|
connection_quality: connectionQuality,
|
||||||
|
retry_count: retryCount,
|
||||||
|
circuit_breaker_triggered: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
trackEvent.networkPerformance(eventData);
|
||||||
|
} else {
|
||||||
|
trackEvent.networkFailure(eventData);
|
||||||
|
}
|
||||||
|
}, [trackEvent]);
|
||||||
|
|
||||||
|
return { trackRequest };
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user