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:
Waleed
2026-04-04 16:49:52 -07:00
committed by GitHub
parent ce53275e9d
commit b4d9b8c396
12 changed files with 330 additions and 73 deletions
+8
View File
@@ -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,
+8
View File
@@ -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',
+8
View File
@@ -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)
+8
View File
@@ -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(() => {
@@ -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>
@@ -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]}`
)
+57 -4
View File
@@ -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