From ea7cc13056c4f514836d80c50a8fad77af74b1f8 Mon Sep 17 00:00:00 2001 From: chihlasm Date: Mon, 16 Mar 2026 18:47:46 -0400 Subject: [PATCH] feat: wire remaining PostHog events across all key user actions - export_generated: session copy, copy-for-ticket, download - ai_feature_used: copilot, assistant chat, session-to-flow, KB accelerator, flow assist - psa_connected: ConnectWise integration creation - session_shared: share link creation - flow_created: troubleshooting editor, procedural editor, session-to-flow All 9 events from the product analytics plan are now fully wired. Co-Authored-By: Claude Opus 4.6 (1M context) --- frontend/src/components/copilot/CopilotPanel.tsx | 2 ++ frontend/src/components/session/ShareSessionModal.tsx | 2 ++ frontend/src/hooks/useEditorAI.ts | 2 ++ frontend/src/pages/AssistantChatPage.tsx | 2 ++ frontend/src/pages/KBAcceleratorPage.tsx | 2 ++ frontend/src/pages/ProceduralEditorPage.tsx | 2 ++ frontend/src/pages/SessionDetailPage.tsx | 6 ++++++ frontend/src/pages/TreeEditorPage.tsx | 3 +++ frontend/src/pages/account/IntegrationsPage.tsx | 2 ++ 9 files changed, 23 insertions(+) diff --git a/frontend/src/components/copilot/CopilotPanel.tsx b/frontend/src/components/copilot/CopilotPanel.tsx index 3ab306c5..99d47f97 100644 --- a/frontend/src/components/copilot/CopilotPanel.tsx +++ b/frontend/src/components/copilot/CopilotPanel.tsx @@ -2,6 +2,7 @@ import { useState, useRef, useEffect, useCallback } from 'react' import { X, Send, Sparkles, Loader2 } from 'lucide-react' import { MarkdownContent } from '@/components/ui/MarkdownContent' import { copilotApi } from '@/api/copilot' +import { analytics } from '@/lib/analytics' import { SuggestedFlowCard } from '@/components/assistant/SuggestedFlowCard' import type { CopilotMessage, SuggestedFlow } from '@/types/copilot' @@ -32,6 +33,7 @@ export function CopilotPanel({ isOpen, onClose, treeId, sessionId, currentNodeId current_node_id: currentNodeId, }) setConversationId(response.conversation_id) + analytics.aiFeatureUsed({ feature: 'copilot' }) setMessages([{ role: 'assistant', content: response.greeting }]) } catch { setMessages([{ role: 'assistant', content: 'Failed to start copilot. Please try again.' }]) diff --git a/frontend/src/components/session/ShareSessionModal.tsx b/frontend/src/components/session/ShareSessionModal.tsx index fa271a51..6bab9cc3 100644 --- a/frontend/src/components/session/ShareSessionModal.tsx +++ b/frontend/src/components/session/ShareSessionModal.tsx @@ -1,6 +1,7 @@ import { useState, useEffect, useCallback } from 'react' import { Copy, Check, Globe, Users, Clock, Trash2, Link2 } from 'lucide-react' import type { SessionShare, SessionShareVisibility } from '@/types' +import { analytics } from '@/lib/analytics' import { sessionsApi } from '@/api/sessions' import { buildSessionShareUrl, filterSharesForSession } from '@/lib/sessionShare' import { cn } from '@/lib/utils' @@ -122,6 +123,7 @@ export function ShareSessionModal({ sessionId, sessionLabel, isOpen, onClose }: expires_at, }) setShares([newShare, ...shares]) + analytics.sessionShared({ session_id: sessionId, visibility }) toast.success('Share link generated') setShareName('') setExpirationPreset('never') diff --git a/frontend/src/hooks/useEditorAI.ts b/frontend/src/hooks/useEditorAI.ts index cbbadcc6..db747e3c 100644 --- a/frontend/src/hooks/useEditorAI.ts +++ b/frontend/src/hooks/useEditorAI.ts @@ -1,5 +1,6 @@ import { useState, useCallback, useRef } from 'react' import { editorAIApi } from '@/api/editorAI' +import { analytics } from '@/lib/analytics' import type { AIActionType, EditorAIChatMessage, @@ -101,6 +102,7 @@ export function useEditorAI({ flowType, treeId, getFlowContext, onFlowUpdate }: focalNodeId: currentFocalNodeId, flowContext: getFlowContext?.() || null, }) + analytics.aiFeatureUsed({ feature: 'flow_assist' }) setMessages((prev) => [ ...prev, diff --git a/frontend/src/pages/AssistantChatPage.tsx b/frontend/src/pages/AssistantChatPage.tsx index 1eb51cfb..325c3945 100644 --- a/frontend/src/pages/AssistantChatPage.tsx +++ b/frontend/src/pages/AssistantChatPage.tsx @@ -3,6 +3,7 @@ import { useLocation, useNavigate } from 'react-router-dom' import { Sparkles, Send, Loader2, Flag } from 'lucide-react' import { PageMeta } from '@/components/common/PageMeta' import { assistantChatApi } from '@/api/assistantChat' +import { analytics } from '@/lib/analytics' import { toast } from '@/lib/toast' import { ChatSidebar } from '@/components/assistant/ChatSidebar' import { ChatMessage } from '@/components/assistant/ChatMessage' @@ -147,6 +148,7 @@ export default function AssistantChatPage() { try { const response = await assistantChatApi.sendMessage(activeChatId, userMessage) + analytics.aiFeatureUsed({ feature: 'assistant_chat' }) setMessages(prev => [ ...prev, { role: 'assistant', content: response.content, suggestedFlows: response.suggested_flows }, diff --git a/frontend/src/pages/KBAcceleratorPage.tsx b/frontend/src/pages/KBAcceleratorPage.tsx index b66aa3e4..efc65b3f 100644 --- a/frontend/src/pages/KBAcceleratorPage.tsx +++ b/frontend/src/pages/KBAcceleratorPage.tsx @@ -1,6 +1,7 @@ import { useState, useEffect, useCallback, useRef } from 'react' import { useNavigate } from 'react-router-dom' import { Sparkles, Loader2 } from 'lucide-react' +import { analytics } from '@/lib/analytics' import { toast } from '@/lib/toast' import { kbAcceleratorApi } from '@/api' import { UploadScreen } from '@/components/kb-accelerator/UploadScreen' @@ -64,6 +65,7 @@ export default function KBAcceleratorPage() { target_type: targetType, }) setImportId(resp.id) + analytics.aiFeatureUsed({ feature: 'kb_accelerator' }) setPhase('processing') startPolling(resp.id) } catch (err: unknown) { diff --git a/frontend/src/pages/ProceduralEditorPage.tsx b/frontend/src/pages/ProceduralEditorPage.tsx index a00611ee..807ec36b 100644 --- a/frontend/src/pages/ProceduralEditorPage.tsx +++ b/frontend/src/pages/ProceduralEditorPage.tsx @@ -1,6 +1,7 @@ import { useEffect, useState, useCallback } from 'react' import { useParams, useNavigate, useSearchParams } from 'react-router-dom' import { Save, ArrowLeft, ListOrdered, Wrench, Settings, FileText, Calendar, Sparkles, Layers } from 'lucide-react' +import { analytics } from '@/lib/analytics' import { Button } from '@/components/ui/Button' import { treesApi } from '@/api/trees' import { useProceduralEditorStore } from '@/store/proceduralEditorStore' @@ -222,6 +223,7 @@ export function ProceduralEditorPage() { toast.success(`${flowLabel} saved`) } else { const created = await treesApi.create(payload) + analytics.flowCreated({ flow_type: payload.tree_type || 'procedural', method: 'manual' }) markSaved() toast.success(`${flowLabel} created`) navigate(`/flows/${created.id}/edit`, { replace: true }) diff --git a/frontend/src/pages/SessionDetailPage.tsx b/frontend/src/pages/SessionDetailPage.tsx index 635967c8..067a233c 100644 --- a/frontend/src/pages/SessionDetailPage.tsx +++ b/frontend/src/pages/SessionDetailPage.tsx @@ -8,6 +8,7 @@ import { treesApi } from '@/api/trees' import { sessionToFlowApi } from '@/api/sessionToFlow' import { ExportPreviewModal } from '@/components/session/ExportPreviewModal' import { SaveSessionAsTreeModal } from '@/components/session/SaveSessionAsTreeModal' +import { analytics } from '@/lib/analytics' import { ShareSessionModal } from '@/components/session/ShareSessionModal' import { SessionOutcomeModal } from '@/components/session/SessionOutcomeModal' import { SessionTimeline } from '@/components/session/SessionTimeline' @@ -150,6 +151,7 @@ export function SessionDetailPage() { setCopied(true) setTimeout(() => setCopied(false), 2000) toast.success('Copied to clipboard') + analytics.exportGenerated({ session_id: session?.id || '', format: exportFormat }) } } catch (err) { console.error('Copy failed:', err) @@ -168,6 +170,7 @@ export function SessionDetailPage() { setCopiedPsa(true) setTimeout(() => setCopiedPsa(false), 2000) toast.success('Copied ticket notes to clipboard') + analytics.exportGenerated({ session_id: session.id, format: 'psa' }) } } catch (err) { console.error('Copy for ticket failed:', err) @@ -177,6 +180,7 @@ export function SessionDetailPage() { const handleDownload = (content: string) => { if (!session) return + analytics.exportGenerated({ session_id: session.id, format: exportFormat }) const blob = new Blob([content], { type: 'text/plain' }) const url = URL.createObjectURL(blob) const a = document.createElement('a') @@ -256,6 +260,7 @@ export function SessionDetailPage() { setIsGeneratingFlow(true) try { const flowData = await sessionToFlowApi.generate(session.id) + analytics.aiFeatureUsed({ feature: 'session_to_flow' }) const tree = await treesApi.create({ name: flowData.name, description: flowData.description, @@ -264,6 +269,7 @@ export function SessionDetailPage() { tags: flowData.tags, }) toast.success('Flow generated! Opening editor...') + analytics.flowCreated({ flow_type: 'procedural', method: 'session_to_flow' }) navigate(getTreeEditorPath(tree.id, 'procedural')) } catch (err) { console.error('Failed to generate flow from session:', err) diff --git a/frontend/src/pages/TreeEditorPage.tsx b/frontend/src/pages/TreeEditorPage.tsx index 8949be15..2cd5b735 100644 --- a/frontend/src/pages/TreeEditorPage.tsx +++ b/frontend/src/pages/TreeEditorPage.tsx @@ -2,6 +2,7 @@ import { useEffect, useState, useCallback, useRef } from 'react' import { useParams, useNavigate, useBlocker } from 'react-router-dom' import { useStore } from 'zustand' import { Undo2, Redo2, Save, CheckCircle2, Monitor, FileText, Code2, LayoutList, BarChart3, Settings, Download, Sparkles } from 'lucide-react' +import { analytics } from '@/lib/analytics' import { Button } from '@/components/ui/Button' import { getMonacoEditor } from '@/components/tree-editor/code-mode' import { treesApi } from '@/api/trees' @@ -364,6 +365,7 @@ export function TreeEditorPage() { toast.success('Draft saved successfully') } else { const newTree = await treesApi.create(treeData as TreeCreate) + analytics.flowCreated({ flow_type: 'troubleshooting', method: 'manual' }) setTreeStatus('draft') markSaved() toast.success('Draft created successfully') @@ -443,6 +445,7 @@ export function TreeEditorPage() { toast.success('Tree published successfully') } else { const newTree = await treesApi.create(treeData as TreeCreate) + analytics.flowCreated({ flow_type: 'troubleshooting', method: 'manual' }) setTreeStatus('published') markSaved() toast.success('Tree published successfully') diff --git a/frontend/src/pages/account/IntegrationsPage.tsx b/frontend/src/pages/account/IntegrationsPage.tsx index ae9a55ac..7c8d59f1 100644 --- a/frontend/src/pages/account/IntegrationsPage.tsx +++ b/frontend/src/pages/account/IntegrationsPage.tsx @@ -1,5 +1,6 @@ import { useEffect, useState } from 'react' import { Plug, CheckCircle2, AlertCircle, Loader2, Pencil, Trash2, Shield, History, Ticket, Users, Zap, Save } from 'lucide-react' +import { analytics } from '@/lib/analytics' import { PageMeta } from '@/components/common/PageMeta' import { integrationsApi } from '@/api/integrations' import type { PsaConnectionResponse, PsaConnectionCreate, PsaConnectionUpdate, PsaConnectionTestResponse } from '@/types' @@ -97,6 +98,7 @@ export function IntegrationsPage() { } const created = await integrationsApi.createConnection(payload) setConnection(created) + analytics.psaConnected({ provider: 'connectwise' }) setMode('view') setForm(emptyForm) } catch (err) {