feat: wire remaining PostHog analytics events #111
@@ -2,6 +2,7 @@ import { useState, useRef, useEffect, useCallback } from 'react'
|
|||||||
import { X, Send, Sparkles, Loader2 } from 'lucide-react'
|
import { X, Send, Sparkles, Loader2 } from 'lucide-react'
|
||||||
import { MarkdownContent } from '@/components/ui/MarkdownContent'
|
import { MarkdownContent } from '@/components/ui/MarkdownContent'
|
||||||
import { copilotApi } from '@/api/copilot'
|
import { copilotApi } from '@/api/copilot'
|
||||||
|
import { analytics } from '@/lib/analytics'
|
||||||
import { SuggestedFlowCard } from '@/components/assistant/SuggestedFlowCard'
|
import { SuggestedFlowCard } from '@/components/assistant/SuggestedFlowCard'
|
||||||
import type { CopilotMessage, SuggestedFlow } from '@/types/copilot'
|
import type { CopilotMessage, SuggestedFlow } from '@/types/copilot'
|
||||||
|
|
||||||
@@ -32,6 +33,7 @@ export function CopilotPanel({ isOpen, onClose, treeId, sessionId, currentNodeId
|
|||||||
current_node_id: currentNodeId,
|
current_node_id: currentNodeId,
|
||||||
})
|
})
|
||||||
setConversationId(response.conversation_id)
|
setConversationId(response.conversation_id)
|
||||||
|
analytics.aiFeatureUsed({ feature: 'copilot' })
|
||||||
setMessages([{ role: 'assistant', content: response.greeting }])
|
setMessages([{ role: 'assistant', content: response.greeting }])
|
||||||
} catch {
|
} catch {
|
||||||
setMessages([{ role: 'assistant', content: 'Failed to start copilot. Please try again.' }])
|
setMessages([{ role: 'assistant', content: 'Failed to start copilot. Please try again.' }])
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { useState, useEffect, useCallback } from 'react'
|
import { useState, useEffect, useCallback } from 'react'
|
||||||
import { Copy, Check, Globe, Users, Clock, Trash2, Link2 } from 'lucide-react'
|
import { Copy, Check, Globe, Users, Clock, Trash2, Link2 } from 'lucide-react'
|
||||||
import type { SessionShare, SessionShareVisibility } from '@/types'
|
import type { SessionShare, SessionShareVisibility } from '@/types'
|
||||||
|
import { analytics } from '@/lib/analytics'
|
||||||
import { sessionsApi } from '@/api/sessions'
|
import { sessionsApi } from '@/api/sessions'
|
||||||
import { buildSessionShareUrl, filterSharesForSession } from '@/lib/sessionShare'
|
import { buildSessionShareUrl, filterSharesForSession } from '@/lib/sessionShare'
|
||||||
import { cn } from '@/lib/utils'
|
import { cn } from '@/lib/utils'
|
||||||
@@ -122,6 +123,7 @@ export function ShareSessionModal({ sessionId, sessionLabel, isOpen, onClose }:
|
|||||||
expires_at,
|
expires_at,
|
||||||
})
|
})
|
||||||
setShares([newShare, ...shares])
|
setShares([newShare, ...shares])
|
||||||
|
analytics.sessionShared({ session_id: sessionId, visibility })
|
||||||
toast.success('Share link generated')
|
toast.success('Share link generated')
|
||||||
setShareName('')
|
setShareName('')
|
||||||
setExpirationPreset('never')
|
setExpirationPreset('never')
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { useState, useCallback, useRef } from 'react'
|
import { useState, useCallback, useRef } from 'react'
|
||||||
import { editorAIApi } from '@/api/editorAI'
|
import { editorAIApi } from '@/api/editorAI'
|
||||||
|
import { analytics } from '@/lib/analytics'
|
||||||
import type {
|
import type {
|
||||||
AIActionType,
|
AIActionType,
|
||||||
EditorAIChatMessage,
|
EditorAIChatMessage,
|
||||||
@@ -101,6 +102,7 @@ export function useEditorAI({ flowType, treeId, getFlowContext, onFlowUpdate }:
|
|||||||
focalNodeId: currentFocalNodeId,
|
focalNodeId: currentFocalNodeId,
|
||||||
flowContext: getFlowContext?.() || null,
|
flowContext: getFlowContext?.() || null,
|
||||||
})
|
})
|
||||||
|
analytics.aiFeatureUsed({ feature: 'flow_assist' })
|
||||||
|
|
||||||
setMessages((prev) => [
|
setMessages((prev) => [
|
||||||
...prev,
|
...prev,
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { useLocation, useNavigate } from 'react-router-dom'
|
|||||||
import { Sparkles, Send, Loader2, Flag } from 'lucide-react'
|
import { Sparkles, Send, Loader2, Flag } from 'lucide-react'
|
||||||
import { PageMeta } from '@/components/common/PageMeta'
|
import { PageMeta } from '@/components/common/PageMeta'
|
||||||
import { assistantChatApi } from '@/api/assistantChat'
|
import { assistantChatApi } from '@/api/assistantChat'
|
||||||
|
import { analytics } from '@/lib/analytics'
|
||||||
import { toast } from '@/lib/toast'
|
import { toast } from '@/lib/toast'
|
||||||
import { ChatSidebar } from '@/components/assistant/ChatSidebar'
|
import { ChatSidebar } from '@/components/assistant/ChatSidebar'
|
||||||
import { ChatMessage } from '@/components/assistant/ChatMessage'
|
import { ChatMessage } from '@/components/assistant/ChatMessage'
|
||||||
@@ -147,6 +148,7 @@ export default function AssistantChatPage() {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await assistantChatApi.sendMessage(activeChatId, userMessage)
|
const response = await assistantChatApi.sendMessage(activeChatId, userMessage)
|
||||||
|
analytics.aiFeatureUsed({ feature: 'assistant_chat' })
|
||||||
setMessages(prev => [
|
setMessages(prev => [
|
||||||
...prev,
|
...prev,
|
||||||
{ role: 'assistant', content: response.content, suggestedFlows: response.suggested_flows },
|
{ role: 'assistant', content: response.content, suggestedFlows: response.suggested_flows },
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { useState, useEffect, useCallback, useRef } from 'react'
|
import { useState, useEffect, useCallback, useRef } from 'react'
|
||||||
import { useNavigate } from 'react-router-dom'
|
import { useNavigate } from 'react-router-dom'
|
||||||
import { Sparkles, Loader2 } from 'lucide-react'
|
import { Sparkles, Loader2 } from 'lucide-react'
|
||||||
|
import { analytics } from '@/lib/analytics'
|
||||||
import { toast } from '@/lib/toast'
|
import { toast } from '@/lib/toast'
|
||||||
import { kbAcceleratorApi } from '@/api'
|
import { kbAcceleratorApi } from '@/api'
|
||||||
import { UploadScreen } from '@/components/kb-accelerator/UploadScreen'
|
import { UploadScreen } from '@/components/kb-accelerator/UploadScreen'
|
||||||
@@ -64,6 +65,7 @@ export default function KBAcceleratorPage() {
|
|||||||
target_type: targetType,
|
target_type: targetType,
|
||||||
})
|
})
|
||||||
setImportId(resp.id)
|
setImportId(resp.id)
|
||||||
|
analytics.aiFeatureUsed({ feature: 'kb_accelerator' })
|
||||||
setPhase('processing')
|
setPhase('processing')
|
||||||
startPolling(resp.id)
|
startPolling(resp.id)
|
||||||
} catch (err: unknown) {
|
} catch (err: unknown) {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { useEffect, useState, useCallback } from 'react'
|
import { useEffect, useState, useCallback } from 'react'
|
||||||
import { useParams, useNavigate, useSearchParams } from 'react-router-dom'
|
import { useParams, useNavigate, useSearchParams } from 'react-router-dom'
|
||||||
import { Save, ArrowLeft, ListOrdered, Wrench, Settings, FileText, Calendar, Sparkles, Layers } from 'lucide-react'
|
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 { Button } from '@/components/ui/Button'
|
||||||
import { treesApi } from '@/api/trees'
|
import { treesApi } from '@/api/trees'
|
||||||
import { useProceduralEditorStore } from '@/store/proceduralEditorStore'
|
import { useProceduralEditorStore } from '@/store/proceduralEditorStore'
|
||||||
@@ -222,6 +223,7 @@ export function ProceduralEditorPage() {
|
|||||||
toast.success(`${flowLabel} saved`)
|
toast.success(`${flowLabel} saved`)
|
||||||
} else {
|
} else {
|
||||||
const created = await treesApi.create(payload)
|
const created = await treesApi.create(payload)
|
||||||
|
analytics.flowCreated({ flow_type: payload.tree_type || 'procedural', method: 'manual' })
|
||||||
markSaved()
|
markSaved()
|
||||||
toast.success(`${flowLabel} created`)
|
toast.success(`${flowLabel} created`)
|
||||||
navigate(`/flows/${created.id}/edit`, { replace: true })
|
navigate(`/flows/${created.id}/edit`, { replace: true })
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { treesApi } from '@/api/trees'
|
|||||||
import { sessionToFlowApi } from '@/api/sessionToFlow'
|
import { sessionToFlowApi } from '@/api/sessionToFlow'
|
||||||
import { ExportPreviewModal } from '@/components/session/ExportPreviewModal'
|
import { ExportPreviewModal } from '@/components/session/ExportPreviewModal'
|
||||||
import { SaveSessionAsTreeModal } from '@/components/session/SaveSessionAsTreeModal'
|
import { SaveSessionAsTreeModal } from '@/components/session/SaveSessionAsTreeModal'
|
||||||
|
import { analytics } from '@/lib/analytics'
|
||||||
import { ShareSessionModal } from '@/components/session/ShareSessionModal'
|
import { ShareSessionModal } from '@/components/session/ShareSessionModal'
|
||||||
import { SessionOutcomeModal } from '@/components/session/SessionOutcomeModal'
|
import { SessionOutcomeModal } from '@/components/session/SessionOutcomeModal'
|
||||||
import { SessionTimeline } from '@/components/session/SessionTimeline'
|
import { SessionTimeline } from '@/components/session/SessionTimeline'
|
||||||
@@ -150,6 +151,7 @@ export function SessionDetailPage() {
|
|||||||
setCopied(true)
|
setCopied(true)
|
||||||
setTimeout(() => setCopied(false), 2000)
|
setTimeout(() => setCopied(false), 2000)
|
||||||
toast.success('Copied to clipboard')
|
toast.success('Copied to clipboard')
|
||||||
|
analytics.exportGenerated({ session_id: session?.id || '', format: exportFormat })
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Copy failed:', err)
|
console.error('Copy failed:', err)
|
||||||
@@ -168,6 +170,7 @@ export function SessionDetailPage() {
|
|||||||
setCopiedPsa(true)
|
setCopiedPsa(true)
|
||||||
setTimeout(() => setCopiedPsa(false), 2000)
|
setTimeout(() => setCopiedPsa(false), 2000)
|
||||||
toast.success('Copied ticket notes to clipboard')
|
toast.success('Copied ticket notes to clipboard')
|
||||||
|
analytics.exportGenerated({ session_id: session.id, format: 'psa' })
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Copy for ticket failed:', err)
|
console.error('Copy for ticket failed:', err)
|
||||||
@@ -177,6 +180,7 @@ export function SessionDetailPage() {
|
|||||||
|
|
||||||
const handleDownload = (content: string) => {
|
const handleDownload = (content: string) => {
|
||||||
if (!session) return
|
if (!session) return
|
||||||
|
analytics.exportGenerated({ session_id: session.id, format: exportFormat })
|
||||||
const blob = new Blob([content], { type: 'text/plain' })
|
const blob = new Blob([content], { type: 'text/plain' })
|
||||||
const url = URL.createObjectURL(blob)
|
const url = URL.createObjectURL(blob)
|
||||||
const a = document.createElement('a')
|
const a = document.createElement('a')
|
||||||
@@ -256,6 +260,7 @@ export function SessionDetailPage() {
|
|||||||
setIsGeneratingFlow(true)
|
setIsGeneratingFlow(true)
|
||||||
try {
|
try {
|
||||||
const flowData = await sessionToFlowApi.generate(session.id)
|
const flowData = await sessionToFlowApi.generate(session.id)
|
||||||
|
analytics.aiFeatureUsed({ feature: 'session_to_flow' })
|
||||||
const tree = await treesApi.create({
|
const tree = await treesApi.create({
|
||||||
name: flowData.name,
|
name: flowData.name,
|
||||||
description: flowData.description,
|
description: flowData.description,
|
||||||
@@ -264,6 +269,7 @@ export function SessionDetailPage() {
|
|||||||
tags: flowData.tags,
|
tags: flowData.tags,
|
||||||
})
|
})
|
||||||
toast.success('Flow generated! Opening editor...')
|
toast.success('Flow generated! Opening editor...')
|
||||||
|
analytics.flowCreated({ flow_type: 'procedural', method: 'session_to_flow' })
|
||||||
navigate(getTreeEditorPath(tree.id, 'procedural'))
|
navigate(getTreeEditorPath(tree.id, 'procedural'))
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Failed to generate flow from session:', err)
|
console.error('Failed to generate flow from session:', err)
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { useEffect, useState, useCallback, useRef } from 'react'
|
|||||||
import { useParams, useNavigate, useBlocker } from 'react-router-dom'
|
import { useParams, useNavigate, useBlocker } from 'react-router-dom'
|
||||||
import { useStore } from 'zustand'
|
import { useStore } from 'zustand'
|
||||||
import { Undo2, Redo2, Save, CheckCircle2, Monitor, FileText, Code2, LayoutList, BarChart3, Settings, Download, Sparkles } from 'lucide-react'
|
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 { Button } from '@/components/ui/Button'
|
||||||
import { getMonacoEditor } from '@/components/tree-editor/code-mode'
|
import { getMonacoEditor } from '@/components/tree-editor/code-mode'
|
||||||
import { treesApi } from '@/api/trees'
|
import { treesApi } from '@/api/trees'
|
||||||
@@ -364,6 +365,7 @@ export function TreeEditorPage() {
|
|||||||
toast.success('Draft saved successfully')
|
toast.success('Draft saved successfully')
|
||||||
} else {
|
} else {
|
||||||
const newTree = await treesApi.create(treeData as TreeCreate)
|
const newTree = await treesApi.create(treeData as TreeCreate)
|
||||||
|
analytics.flowCreated({ flow_type: 'troubleshooting', method: 'manual' })
|
||||||
setTreeStatus('draft')
|
setTreeStatus('draft')
|
||||||
markSaved()
|
markSaved()
|
||||||
toast.success('Draft created successfully')
|
toast.success('Draft created successfully')
|
||||||
@@ -443,6 +445,7 @@ export function TreeEditorPage() {
|
|||||||
toast.success('Tree published successfully')
|
toast.success('Tree published successfully')
|
||||||
} else {
|
} else {
|
||||||
const newTree = await treesApi.create(treeData as TreeCreate)
|
const newTree = await treesApi.create(treeData as TreeCreate)
|
||||||
|
analytics.flowCreated({ flow_type: 'troubleshooting', method: 'manual' })
|
||||||
setTreeStatus('published')
|
setTreeStatus('published')
|
||||||
markSaved()
|
markSaved()
|
||||||
toast.success('Tree published successfully')
|
toast.success('Tree published successfully')
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import { Plug, CheckCircle2, AlertCircle, Loader2, Pencil, Trash2, Shield, History, Ticket, Users, Zap, Save } from 'lucide-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 { PageMeta } from '@/components/common/PageMeta'
|
||||||
import { integrationsApi } from '@/api/integrations'
|
import { integrationsApi } from '@/api/integrations'
|
||||||
import type { PsaConnectionResponse, PsaConnectionCreate, PsaConnectionUpdate, PsaConnectionTestResponse } from '@/types'
|
import type { PsaConnectionResponse, PsaConnectionCreate, PsaConnectionUpdate, PsaConnectionTestResponse } from '@/types'
|
||||||
@@ -97,6 +98,7 @@ export function IntegrationsPage() {
|
|||||||
}
|
}
|
||||||
const created = await integrationsApi.createConnection(payload)
|
const created = await integrationsApi.createConnection(payload)
|
||||||
setConnection(created)
|
setConnection(created)
|
||||||
|
analytics.psaConnected({ provider: 'connectwise' })
|
||||||
setMode('view')
|
setMode('view')
|
||||||
setForm(emptyForm)
|
setForm(emptyForm)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|||||||
Reference in New Issue
Block a user