mirror of
https://github.com/simstudioai/sim.git
synced 2026-09-24 15:45:35 +08:00
feat(analytics): posthog audit — remove noise, add 10 new events (#3960)
* feat(analytics): posthog audit — remove noise, add 10 new events Remove task_marked_read (fires automatically on every task view). Add workspace_id to task_message_sent for group analytics. New events: - search_result_selected: block/tool/trigger/workflow/table/file/ knowledge_base/workspace/task/page/docs with query_length - workflow_imported: count + format (json/zip) - workflow_exported: count + format (json/zip) - folder_created / folder_deleted - logs_filter_applied: status/workflow/folder/trigger/time - knowledge_base_document_deleted - scheduled_task_created / scheduled_task_deleted * fix(analytics): use usePostHog + captureEvent in hooks, track custom date range * fix(analytics): always fire scheduled_task_deleted regardless of workspaceId * fix(analytics): correct format field logic and add missing useCallback deps
This commit is contained in:
@@ -5,6 +5,7 @@ import { eq } from 'drizzle-orm'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { z } from 'zod'
|
||||
import { getSession } from '@/lib/auth'
|
||||
import { captureServerEvent } from '@/lib/posthog/server'
|
||||
import { performDeleteFolder } from '@/lib/workflows/orchestration'
|
||||
import { checkForCircularReference } from '@/lib/workflows/utils'
|
||||
import { getUserEntityPermissions } from '@/lib/workspaces/permissions/utils'
|
||||
@@ -156,6 +157,13 @@ export async function DELETE(
|
||||
return NextResponse.json({ error: result.error }, { status })
|
||||
}
|
||||
|
||||
captureServerEvent(
|
||||
session.user.id,
|
||||
'folder_deleted',
|
||||
{ workspace_id: existingFolder.workspaceId },
|
||||
{ groups: { workspace: existingFolder.workspaceId } }
|
||||
)
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
deletedItems: result.deletedItems,
|
||||
|
||||
@@ -6,6 +6,7 @@ import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { z } from 'zod'
|
||||
import { AuditAction, AuditResourceType, recordAudit } from '@/lib/audit/log'
|
||||
import { getSession } from '@/lib/auth'
|
||||
import { captureServerEvent } from '@/lib/posthog/server'
|
||||
import { getUserEntityPermissions } from '@/lib/workspaces/permissions/utils'
|
||||
|
||||
const logger = createLogger('FoldersAPI')
|
||||
@@ -145,6 +146,13 @@ export async function POST(request: NextRequest) {
|
||||
|
||||
logger.info('Created new folder:', { id, name, workspaceId, parentId })
|
||||
|
||||
captureServerEvent(
|
||||
session.user.id,
|
||||
'folder_created',
|
||||
{ workspace_id: workspaceId },
|
||||
{ groups: { workspace: workspaceId } }
|
||||
)
|
||||
|
||||
recordAudit({
|
||||
workspaceId,
|
||||
actorId: session.user.id,
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
retryDocumentProcessing,
|
||||
updateDocument,
|
||||
} from '@/lib/knowledge/documents/service'
|
||||
import { captureServerEvent } from '@/lib/posthog/server'
|
||||
import { checkDocumentAccess, checkDocumentWriteAccess } from '@/app/api/knowledge/utils'
|
||||
|
||||
const logger = createLogger('DocumentByIdAPI')
|
||||
@@ -285,6 +286,14 @@ export async function DELETE(
|
||||
request: req,
|
||||
})
|
||||
|
||||
const kbWorkspaceId = accessCheck.knowledgeBase?.workspaceId ?? ''
|
||||
captureServerEvent(
|
||||
userId,
|
||||
'knowledge_base_document_deleted',
|
||||
{ knowledge_base_id: knowledgeBaseId, workspace_id: kbWorkspaceId },
|
||||
kbWorkspaceId ? { groups: { workspace: kbWorkspaceId } } : undefined
|
||||
)
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
data: result,
|
||||
|
||||
@@ -159,16 +159,7 @@ export async function PATCH(
|
||||
}
|
||||
)
|
||||
}
|
||||
if (isUnread === false) {
|
||||
captureServerEvent(
|
||||
userId,
|
||||
'task_marked_read',
|
||||
{ workspace_id: updatedChat.workspaceId },
|
||||
{
|
||||
groups: { workspace: updatedChat.workspaceId },
|
||||
}
|
||||
)
|
||||
} else if (isUnread === true) {
|
||||
if (isUnread === true) {
|
||||
captureServerEvent(
|
||||
userId,
|
||||
'task_marked_unread',
|
||||
|
||||
@@ -7,6 +7,7 @@ import { z } from 'zod'
|
||||
import { AuditAction, AuditResourceType, recordAudit } from '@/lib/audit/log'
|
||||
import { getSession } from '@/lib/auth'
|
||||
import { generateRequestId } from '@/lib/core/utils/request'
|
||||
import { captureServerEvent } from '@/lib/posthog/server'
|
||||
import { validateCronExpression } from '@/lib/workflows/schedules/utils'
|
||||
import { authorizeWorkflowByWorkspacePermission } from '@/lib/workflows/utils'
|
||||
import { verifyWorkspaceMembership } from '@/app/api/workflows/utils'
|
||||
@@ -298,6 +299,13 @@ export async function DELETE(
|
||||
request,
|
||||
})
|
||||
|
||||
captureServerEvent(
|
||||
session.user.id,
|
||||
'scheduled_task_deleted',
|
||||
{ workspace_id: workspaceId ?? '' },
|
||||
workspaceId ? { groups: { workspace: workspaceId } } : undefined
|
||||
)
|
||||
|
||||
return NextResponse.json({ message: 'Schedule deleted successfully' })
|
||||
} catch (error) {
|
||||
logger.error(`[${requestId}] Error deleting schedule`, error)
|
||||
|
||||
@@ -5,6 +5,7 @@ import { and, eq, isNull, or } from 'drizzle-orm'
|
||||
import { type NextRequest, NextResponse } from 'next/server'
|
||||
import { getSession } from '@/lib/auth'
|
||||
import { generateRequestId } from '@/lib/core/utils/request'
|
||||
import { captureServerEvent } from '@/lib/posthog/server'
|
||||
import { validateCronExpression } from '@/lib/workflows/schedules/utils'
|
||||
import { authorizeWorkflowByWorkspacePermission } from '@/lib/workflows/utils'
|
||||
import { verifyWorkspaceMembership } from '@/app/api/workflows/utils'
|
||||
@@ -277,6 +278,13 @@ export async function POST(req: NextRequest) {
|
||||
lifecycle,
|
||||
})
|
||||
|
||||
captureServerEvent(
|
||||
session.user.id,
|
||||
'scheduled_task_created',
|
||||
{ workspace_id: workspaceId },
|
||||
{ groups: { workspace: workspaceId } }
|
||||
)
|
||||
|
||||
return NextResponse.json(
|
||||
{ schedule: { id, status: 'active', cronExpression, nextRunAt } },
|
||||
{ status: 201 }
|
||||
|
||||
@@ -228,6 +228,7 @@ export function Home({ chatId }: HomeProps = {}) {
|
||||
if (!trimmed && !(fileAttachments && fileAttachments.length > 0)) return
|
||||
|
||||
captureEvent(posthogRef.current, 'task_message_sent', {
|
||||
workspace_id: workspaceId,
|
||||
has_attachments: !!(fileAttachments && fileAttachments.length > 0),
|
||||
has_contexts: !!(contexts && contexts.length > 0),
|
||||
is_new_task: !chatId,
|
||||
@@ -239,7 +240,7 @@ export function Home({ chatId }: HomeProps = {}) {
|
||||
|
||||
sendMessage(trimmed || 'Analyze the attached file(s).', fileAttachments, contexts)
|
||||
},
|
||||
[sendMessage]
|
||||
[sendMessage, workspaceId, chatId]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
+60
-10
@@ -1,8 +1,9 @@
|
||||
'use client'
|
||||
|
||||
import { memo, useCallback, useMemo, useState } from 'react'
|
||||
import { memo, useCallback, useMemo, useRef, useState } from 'react'
|
||||
import { ArrowUp, Bell, Library, MoreHorizontal, RefreshCw } from 'lucide-react'
|
||||
import { useParams } from 'next/navigation'
|
||||
import { usePostHog } from 'posthog-js/react'
|
||||
import { useShallow } from 'zustand/react/shallow'
|
||||
import {
|
||||
Button,
|
||||
@@ -18,6 +19,7 @@ import { DatePicker } from '@/components/emcn/components/date-picker/date-picker
|
||||
import { cn } from '@/lib/core/utils/cn'
|
||||
import { hasActiveFilters } from '@/lib/logs/filters'
|
||||
import { getTriggerOptions } from '@/lib/logs/get-trigger-options'
|
||||
import { captureEvent } from '@/lib/posthog/client'
|
||||
import { type LogStatus, STATUS_CONFIG } from '@/app/workspace/[workspaceId]/logs/utils'
|
||||
import { getBlock } from '@/blocks/registry'
|
||||
import { useFolderMap } from '@/hooks/queries/folders'
|
||||
@@ -179,6 +181,9 @@ export const LogsToolbar = memo(function LogsToolbar({
|
||||
}: LogsToolbarProps) {
|
||||
const params = useParams()
|
||||
const workspaceId = params.workspaceId as string
|
||||
const posthog = usePostHog()
|
||||
const posthogRef = useRef(posthog)
|
||||
posthogRef.current = posthog
|
||||
|
||||
const {
|
||||
level,
|
||||
@@ -258,8 +263,45 @@ export const LogsToolbar = memo(function LogsToolbar({
|
||||
} else {
|
||||
setLevel(values.join(','))
|
||||
}
|
||||
captureEvent(posthogRef.current, 'logs_filter_applied', {
|
||||
filter_type: 'status',
|
||||
workspace_id: workspaceId,
|
||||
})
|
||||
},
|
||||
[setLevel]
|
||||
[setLevel, workspaceId]
|
||||
)
|
||||
|
||||
const handleWorkflowFilterChange = useCallback(
|
||||
(values: string[]) => {
|
||||
setWorkflowIds(values)
|
||||
captureEvent(posthogRef.current, 'logs_filter_applied', {
|
||||
filter_type: 'workflow',
|
||||
workspace_id: workspaceId,
|
||||
})
|
||||
},
|
||||
[setWorkflowIds, workspaceId]
|
||||
)
|
||||
|
||||
const handleFolderFilterChange = useCallback(
|
||||
(values: string[]) => {
|
||||
setFolderIds(values)
|
||||
captureEvent(posthogRef.current, 'logs_filter_applied', {
|
||||
filter_type: 'folder',
|
||||
workspace_id: workspaceId,
|
||||
})
|
||||
},
|
||||
[setFolderIds, workspaceId]
|
||||
)
|
||||
|
||||
const handleTriggerFilterChange = useCallback(
|
||||
(values: string[]) => {
|
||||
setTriggers(values)
|
||||
captureEvent(posthogRef.current, 'logs_filter_applied', {
|
||||
filter_type: 'trigger',
|
||||
workspace_id: workspaceId,
|
||||
})
|
||||
},
|
||||
[setTriggers, workspaceId]
|
||||
)
|
||||
|
||||
const statusDisplayLabel = useMemo(() => {
|
||||
@@ -348,9 +390,13 @@ export const LogsToolbar = memo(function LogsToolbar({
|
||||
} else {
|
||||
clearDateRange()
|
||||
setTimeRange(val as typeof timeRange)
|
||||
captureEvent(posthogRef.current, 'logs_filter_applied', {
|
||||
filter_type: 'time',
|
||||
workspace_id: workspaceId,
|
||||
})
|
||||
}
|
||||
},
|
||||
[timeRange, setTimeRange, clearDateRange]
|
||||
[timeRange, setTimeRange, clearDateRange, workspaceId]
|
||||
)
|
||||
|
||||
/**
|
||||
@@ -360,8 +406,12 @@ export const LogsToolbar = memo(function LogsToolbar({
|
||||
(start: string, end: string) => {
|
||||
setDateRange(start, end)
|
||||
setDatePickerOpen(false)
|
||||
captureEvent(posthogRef.current, 'logs_filter_applied', {
|
||||
filter_type: 'time',
|
||||
workspace_id: workspaceId,
|
||||
})
|
||||
},
|
||||
[setDateRange]
|
||||
[setDateRange, workspaceId]
|
||||
)
|
||||
|
||||
/**
|
||||
@@ -545,7 +595,7 @@ export const LogsToolbar = memo(function LogsToolbar({
|
||||
options={workflowOptions}
|
||||
multiSelect
|
||||
multiSelectValues={workflowIds}
|
||||
onMultiSelectChange={setWorkflowIds}
|
||||
onMultiSelectChange={handleWorkflowFilterChange}
|
||||
placeholder='All workflows'
|
||||
overlayContent={
|
||||
<span className='flex items-center gap-1.5 truncate text-[var(--text-primary)]'>
|
||||
@@ -580,7 +630,7 @@ export const LogsToolbar = memo(function LogsToolbar({
|
||||
options={folderOptions}
|
||||
multiSelect
|
||||
multiSelectValues={folderIds}
|
||||
onMultiSelectChange={setFolderIds}
|
||||
onMultiSelectChange={handleFolderFilterChange}
|
||||
placeholder='All folders'
|
||||
overlayContent={
|
||||
<span className='truncate text-[var(--text-primary)]'>
|
||||
@@ -605,7 +655,7 @@ export const LogsToolbar = memo(function LogsToolbar({
|
||||
options={triggerOptions}
|
||||
multiSelect
|
||||
multiSelectValues={triggers}
|
||||
onMultiSelectChange={setTriggers}
|
||||
onMultiSelectChange={handleTriggerFilterChange}
|
||||
placeholder='All triggers'
|
||||
overlayContent={
|
||||
<span className='truncate text-[var(--text-primary)]'>
|
||||
@@ -676,7 +726,7 @@ export const LogsToolbar = memo(function LogsToolbar({
|
||||
options={workflowOptions}
|
||||
multiSelect
|
||||
multiSelectValues={workflowIds}
|
||||
onMultiSelectChange={setWorkflowIds}
|
||||
onMultiSelectChange={handleWorkflowFilterChange}
|
||||
placeholder='Workflow'
|
||||
overlayContent={
|
||||
<span className='flex items-center gap-1.5 truncate text-[var(--text-primary)]'>
|
||||
@@ -707,7 +757,7 @@ export const LogsToolbar = memo(function LogsToolbar({
|
||||
options={folderOptions}
|
||||
multiSelect
|
||||
multiSelectValues={folderIds}
|
||||
onMultiSelectChange={setFolderIds}
|
||||
onMultiSelectChange={handleFolderFilterChange}
|
||||
placeholder='Folder'
|
||||
overlayContent={
|
||||
<span className='truncate text-[var(--text-primary)]'>{folderDisplayLabel}</span>
|
||||
@@ -726,7 +776,7 @@ export const LogsToolbar = memo(function LogsToolbar({
|
||||
options={triggerOptions}
|
||||
multiSelect
|
||||
multiSelectValues={triggers}
|
||||
onMultiSelectChange={setTriggers}
|
||||
onMultiSelectChange={handleTriggerFilterChange}
|
||||
placeholder='Trigger'
|
||||
overlayContent={
|
||||
<span className='truncate text-[var(--text-primary)]'>{triggerDisplayLabel}</span>
|
||||
|
||||
+146
-47
@@ -3,11 +3,13 @@
|
||||
import { useCallback, useDeferredValue, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { Command } from 'cmdk'
|
||||
import { useParams, useRouter } from 'next/navigation'
|
||||
import { usePostHog } from 'posthog-js/react'
|
||||
import { createPortal } from 'react-dom'
|
||||
import { Library } from '@/components/emcn'
|
||||
import { Calendar, Database, File, HelpCircle, Settings, Table } from '@/components/emcn/icons'
|
||||
import { Search } from '@/components/emcn/icons/search'
|
||||
import { cn } from '@/lib/core/utils/cn'
|
||||
import { captureEvent } from '@/lib/posthog/client'
|
||||
import { hasTriggerCapability } from '@/lib/workflows/triggers/trigger-utils'
|
||||
import { SIDEBAR_SCROLL_EVENT } from '@/app/workspace/[workspaceId]/w/components/sidebar/sidebar'
|
||||
import { usePermissionConfig } from '@/hooks/use-permission-config'
|
||||
@@ -55,11 +57,14 @@ export function SearchModal({
|
||||
const [mounted, setMounted] = useState(false)
|
||||
const { navigateToSettings } = useSettingsNavigation()
|
||||
const { config: permissionConfig } = usePermissionConfig()
|
||||
const posthog = usePostHog()
|
||||
|
||||
const routerRef = useRef(router)
|
||||
routerRef.current = router
|
||||
const onOpenChangeRef = useRef(onOpenChange)
|
||||
onOpenChangeRef.current = onOpenChange
|
||||
const posthogRef = useRef(posthog)
|
||||
posthogRef.current = posthog
|
||||
|
||||
useEffect(() => {
|
||||
setMounted(true)
|
||||
@@ -154,6 +159,8 @@ export function SearchModal({
|
||||
}, [open])
|
||||
|
||||
const deferredSearch = useDeferredValue(search)
|
||||
const deferredSearchRef = useRef(deferredSearch)
|
||||
deferredSearchRef.current = deferredSearch
|
||||
|
||||
const handleSearchChange = useCallback((value: string) => {
|
||||
setSearch(value)
|
||||
@@ -188,59 +195,151 @@ export function SearchModal({
|
||||
detail: { type: block.type, enableTriggerMode },
|
||||
})
|
||||
)
|
||||
captureEvent(posthogRef.current, 'search_result_selected', {
|
||||
result_type: type,
|
||||
query_length: deferredSearchRef.current.length,
|
||||
workspace_id: workspaceId,
|
||||
})
|
||||
onOpenChangeRef.current(false)
|
||||
},
|
||||
[]
|
||||
[workspaceId]
|
||||
)
|
||||
|
||||
const handleToolOperationSelect = useCallback((op: SearchToolOperationItem) => {
|
||||
window.dispatchEvent(
|
||||
new CustomEvent('add-block-from-toolbar', {
|
||||
detail: { type: op.blockType, presetOperation: op.operationId },
|
||||
})
|
||||
)
|
||||
onOpenChangeRef.current(false)
|
||||
}, [])
|
||||
|
||||
const handleWorkflowSelect = useCallback((workflow: WorkflowItem) => {
|
||||
if (!workflow.isCurrent && workflow.href) {
|
||||
routerRef.current.push(workflow.href)
|
||||
const handleToolOperationSelect = useCallback(
|
||||
(op: SearchToolOperationItem) => {
|
||||
window.dispatchEvent(
|
||||
new CustomEvent(SIDEBAR_SCROLL_EVENT, { detail: { itemId: workflow.id } })
|
||||
new CustomEvent('add-block-from-toolbar', {
|
||||
detail: { type: op.blockType, presetOperation: op.operationId },
|
||||
})
|
||||
)
|
||||
}
|
||||
onOpenChangeRef.current(false)
|
||||
}, [])
|
||||
captureEvent(posthogRef.current, 'search_result_selected', {
|
||||
result_type: 'tool_operation',
|
||||
query_length: deferredSearchRef.current.length,
|
||||
workspace_id: workspaceId,
|
||||
})
|
||||
onOpenChangeRef.current(false)
|
||||
},
|
||||
[workspaceId]
|
||||
)
|
||||
|
||||
const handleWorkspaceSelect = useCallback((workspace: WorkspaceItem) => {
|
||||
if (!workspace.isCurrent && workspace.href) {
|
||||
routerRef.current.push(workspace.href)
|
||||
}
|
||||
onOpenChangeRef.current(false)
|
||||
}, [])
|
||||
|
||||
const handleTaskSelect = useCallback((task: TaskItem) => {
|
||||
routerRef.current.push(task.href)
|
||||
onOpenChangeRef.current(false)
|
||||
}, [])
|
||||
|
||||
const handlePageSelect = useCallback((page: PageItem) => {
|
||||
if (page.onClick) {
|
||||
page.onClick()
|
||||
} else if (page.href) {
|
||||
if (page.href.startsWith('http')) {
|
||||
window.open(page.href, '_blank', 'noopener,noreferrer')
|
||||
} else {
|
||||
routerRef.current.push(page.href)
|
||||
const handleWorkflowSelect = useCallback(
|
||||
(workflow: WorkflowItem) => {
|
||||
if (!workflow.isCurrent && workflow.href) {
|
||||
routerRef.current.push(workflow.href)
|
||||
window.dispatchEvent(
|
||||
new CustomEvent(SIDEBAR_SCROLL_EVENT, { detail: { itemId: workflow.id } })
|
||||
)
|
||||
}
|
||||
}
|
||||
onOpenChangeRef.current(false)
|
||||
}, [])
|
||||
captureEvent(posthogRef.current, 'search_result_selected', {
|
||||
result_type: 'workflow',
|
||||
query_length: deferredSearchRef.current.length,
|
||||
workspace_id: workspaceId,
|
||||
})
|
||||
onOpenChangeRef.current(false)
|
||||
},
|
||||
[workspaceId]
|
||||
)
|
||||
|
||||
const handleDocSelect = useCallback((doc: SearchDocItem) => {
|
||||
window.open(doc.href, '_blank', 'noopener,noreferrer')
|
||||
onOpenChangeRef.current(false)
|
||||
}, [])
|
||||
const handleWorkspaceSelect = useCallback(
|
||||
(workspace: WorkspaceItem) => {
|
||||
if (!workspace.isCurrent && workspace.href) {
|
||||
routerRef.current.push(workspace.href)
|
||||
}
|
||||
captureEvent(posthogRef.current, 'search_result_selected', {
|
||||
result_type: 'workspace',
|
||||
query_length: deferredSearchRef.current.length,
|
||||
workspace_id: workspaceId,
|
||||
})
|
||||
onOpenChangeRef.current(false)
|
||||
},
|
||||
[workspaceId]
|
||||
)
|
||||
|
||||
const handleTaskSelect = useCallback(
|
||||
(task: TaskItem) => {
|
||||
routerRef.current.push(task.href)
|
||||
captureEvent(posthogRef.current, 'search_result_selected', {
|
||||
result_type: 'task',
|
||||
query_length: deferredSearchRef.current.length,
|
||||
workspace_id: workspaceId,
|
||||
})
|
||||
onOpenChangeRef.current(false)
|
||||
},
|
||||
[workspaceId]
|
||||
)
|
||||
|
||||
const handleTableSelect = useCallback(
|
||||
(item: TaskItem) => {
|
||||
routerRef.current.push(item.href)
|
||||
captureEvent(posthogRef.current, 'search_result_selected', {
|
||||
result_type: 'table',
|
||||
query_length: deferredSearchRef.current.length,
|
||||
workspace_id: workspaceId,
|
||||
})
|
||||
onOpenChangeRef.current(false)
|
||||
},
|
||||
[workspaceId]
|
||||
)
|
||||
|
||||
const handleFileSelect = useCallback(
|
||||
(item: TaskItem) => {
|
||||
routerRef.current.push(item.href)
|
||||
captureEvent(posthogRef.current, 'search_result_selected', {
|
||||
result_type: 'file',
|
||||
query_length: deferredSearchRef.current.length,
|
||||
workspace_id: workspaceId,
|
||||
})
|
||||
onOpenChangeRef.current(false)
|
||||
},
|
||||
[workspaceId]
|
||||
)
|
||||
|
||||
const handleKbSelect = useCallback(
|
||||
(item: TaskItem) => {
|
||||
routerRef.current.push(item.href)
|
||||
captureEvent(posthogRef.current, 'search_result_selected', {
|
||||
result_type: 'knowledge_base',
|
||||
query_length: deferredSearchRef.current.length,
|
||||
workspace_id: workspaceId,
|
||||
})
|
||||
onOpenChangeRef.current(false)
|
||||
},
|
||||
[workspaceId]
|
||||
)
|
||||
|
||||
const handlePageSelect = useCallback(
|
||||
(page: PageItem) => {
|
||||
if (page.onClick) {
|
||||
page.onClick()
|
||||
} else if (page.href) {
|
||||
if (page.href.startsWith('http')) {
|
||||
window.open(page.href, '_blank', 'noopener,noreferrer')
|
||||
} else {
|
||||
routerRef.current.push(page.href)
|
||||
}
|
||||
}
|
||||
captureEvent(posthogRef.current, 'search_result_selected', {
|
||||
result_type: 'page',
|
||||
query_length: deferredSearchRef.current.length,
|
||||
workspace_id: workspaceId,
|
||||
})
|
||||
onOpenChangeRef.current(false)
|
||||
},
|
||||
[workspaceId]
|
||||
)
|
||||
|
||||
const handleDocSelect = useCallback(
|
||||
(doc: SearchDocItem) => {
|
||||
window.open(doc.href, '_blank', 'noopener,noreferrer')
|
||||
captureEvent(posthogRef.current, 'search_result_selected', {
|
||||
result_type: 'docs',
|
||||
query_length: deferredSearchRef.current.length,
|
||||
workspace_id: workspaceId,
|
||||
})
|
||||
onOpenChangeRef.current(false)
|
||||
},
|
||||
[workspaceId]
|
||||
)
|
||||
|
||||
const handleBlockSelectAsBlock = useCallback(
|
||||
(block: SearchBlockItem) => handleBlockSelect(block, 'block'),
|
||||
@@ -370,9 +469,9 @@ export function SearchModal({
|
||||
<TriggersGroup items={filteredTriggers} onSelect={handleBlockSelectAsTrigger} />
|
||||
<WorkflowsGroup items={filteredWorkflows} onSelect={handleWorkflowSelect} />
|
||||
<TasksGroup items={filteredTasks} onSelect={handleTaskSelect} />
|
||||
<TablesGroup items={filteredTables} onSelect={handleTaskSelect} />
|
||||
<FilesGroup items={filteredFiles} onSelect={handleTaskSelect} />
|
||||
<KnowledgeBasesGroup items={filteredKnowledgeBases} onSelect={handleTaskSelect} />
|
||||
<TablesGroup items={filteredTables} onSelect={handleTableSelect} />
|
||||
<FilesGroup items={filteredFiles} onSelect={handleFileSelect} />
|
||||
<KnowledgeBasesGroup items={filteredKnowledgeBases} onSelect={handleKbSelect} />
|
||||
<ToolOpsGroup items={filteredToolOps} onSelect={handleToolOperationSelect} />
|
||||
<WorkspacesGroup items={filteredWorkspaces} onSelect={handleWorkspaceSelect} />
|
||||
<DocsGroup items={filteredDocs} onSelect={handleDocSelect} />
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { useCallback, useRef, useState } from 'react'
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { useParams } from 'next/navigation'
|
||||
import { usePostHog } from 'posthog-js/react'
|
||||
import { captureEvent } from '@/lib/posthog/client'
|
||||
import {
|
||||
downloadFile,
|
||||
exportWorkflowsToZip,
|
||||
@@ -27,6 +29,7 @@ export function useExportWorkflow({ onSuccess }: UseExportWorkflowProps = {}) {
|
||||
const [isExporting, setIsExporting] = useState(false)
|
||||
const params = useParams()
|
||||
const workspaceId = params.workspaceId as string | undefined
|
||||
const posthog = usePostHog()
|
||||
|
||||
const onSuccessRef = useRef(onSuccess)
|
||||
onSuccessRef.current = onSuccess
|
||||
@@ -34,6 +37,9 @@ export function useExportWorkflow({ onSuccess }: UseExportWorkflowProps = {}) {
|
||||
const workspaceIdRef = useRef(workspaceId)
|
||||
workspaceIdRef.current = workspaceId
|
||||
|
||||
const posthogRef = useRef(posthog)
|
||||
posthogRef.current = posthog
|
||||
|
||||
/**
|
||||
* Export the workflow(s) to JSON or ZIP
|
||||
* - Single workflow: exports as JSON file
|
||||
@@ -100,6 +106,12 @@ export function useExportWorkflow({ onSuccess }: UseExportWorkflowProps = {}) {
|
||||
const { clearSelection } = useFolderStore.getState()
|
||||
clearSelection()
|
||||
|
||||
captureEvent(posthogRef.current, 'workflow_exported', {
|
||||
workspace_id: workspaceIdRef.current ?? '',
|
||||
workflow_count: exportedWorkflows.length,
|
||||
format: exportedWorkflows.length === 1 ? 'json' : 'zip',
|
||||
})
|
||||
|
||||
logger.info('Workflow(s) exported successfully', {
|
||||
workflowIds: workflowIdsToExport,
|
||||
count: exportedWorkflows.length,
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { useCallback, useState } from 'react'
|
||||
import { useCallback, useRef, useState } from 'react'
|
||||
import { createLogger } from '@sim/logger'
|
||||
import { useQueryClient } from '@tanstack/react-query'
|
||||
import { useRouter } from 'next/navigation'
|
||||
import { usePostHog } from 'posthog-js/react'
|
||||
import { captureEvent } from '@/lib/posthog/client'
|
||||
import {
|
||||
extractWorkflowsFromFiles,
|
||||
extractWorkflowsFromZip,
|
||||
@@ -36,6 +38,9 @@ export function useImportWorkflow({ workspaceId }: UseImportWorkflowProps) {
|
||||
const queryClient = useQueryClient()
|
||||
const createFolderMutation = useCreateFolder()
|
||||
const clearDiff = useWorkflowDiffStore((state) => state.clearDiff)
|
||||
const posthog = usePostHog()
|
||||
const posthogRef = useRef(posthog)
|
||||
posthogRef.current = posthog
|
||||
const [isImporting, setIsImporting] = useState(false)
|
||||
|
||||
/**
|
||||
@@ -204,6 +209,11 @@ export function useImportWorkflow({ workspaceId }: UseImportWorkflowProps) {
|
||||
logger.info(`Import complete. Imported ${importedWorkflowIds.length} workflow(s)`)
|
||||
|
||||
if (importedWorkflowIds.length > 0) {
|
||||
captureEvent(posthogRef.current, 'workflow_imported', {
|
||||
workspace_id: workspaceId,
|
||||
workflow_count: importedWorkflowIds.length,
|
||||
format: hasZip && fileArray.length === 1 ? 'zip' : 'json',
|
||||
})
|
||||
router.push(
|
||||
`/workspace/${workspaceId}/w/${importedWorkflowIds[importedWorkflowIds.length - 1]}`
|
||||
)
|
||||
|
||||
@@ -358,15 +358,12 @@ export interface PostHogEventMap {
|
||||
workspace_id: string
|
||||
}
|
||||
|
||||
task_marked_read: {
|
||||
workspace_id: string
|
||||
}
|
||||
|
||||
task_marked_unread: {
|
||||
workspace_id: string
|
||||
}
|
||||
|
||||
task_message_sent: {
|
||||
workspace_id: string
|
||||
has_attachments: boolean
|
||||
has_contexts: boolean
|
||||
is_new_task: boolean
|
||||
@@ -389,6 +386,62 @@ export interface PostHogEventMap {
|
||||
source: 'help_menu' | 'editor_button' | 'toolbar_context_menu'
|
||||
block_type?: string
|
||||
}
|
||||
|
||||
search_result_selected: {
|
||||
result_type:
|
||||
| 'block'
|
||||
| 'tool'
|
||||
| 'trigger'
|
||||
| 'tool_operation'
|
||||
| 'workflow'
|
||||
| 'workspace'
|
||||
| 'task'
|
||||
| 'table'
|
||||
| 'file'
|
||||
| 'knowledge_base'
|
||||
| 'page'
|
||||
| 'docs'
|
||||
query_length: number
|
||||
workspace_id: string
|
||||
}
|
||||
|
||||
workflow_imported: {
|
||||
workspace_id: string
|
||||
workflow_count: number
|
||||
format: 'json' | 'zip'
|
||||
}
|
||||
|
||||
workflow_exported: {
|
||||
workspace_id: string
|
||||
workflow_count: number
|
||||
format: 'json' | 'zip'
|
||||
}
|
||||
|
||||
folder_created: {
|
||||
workspace_id: string
|
||||
}
|
||||
|
||||
folder_deleted: {
|
||||
workspace_id: string
|
||||
}
|
||||
|
||||
logs_filter_applied: {
|
||||
filter_type: 'status' | 'workflow' | 'folder' | 'trigger' | 'time'
|
||||
workspace_id: string
|
||||
}
|
||||
|
||||
knowledge_base_document_deleted: {
|
||||
knowledge_base_id: string
|
||||
workspace_id: string
|
||||
}
|
||||
|
||||
scheduled_task_created: {
|
||||
workspace_id: string
|
||||
}
|
||||
|
||||
scheduled_task_deleted: {
|
||||
workspace_id: string
|
||||
}
|
||||
}
|
||||
|
||||
export type PostHogEventName = keyof PostHogEventMap
|
||||
|
||||
Reference in New Issue
Block a user