diff --git a/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/table.tsx b/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/table.tsx index 9eb5a8de8e8..14fa7c6408c 100644 --- a/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/table.tsx +++ b/apps/sim/app/workspace/[workspaceId]/tables/[tableId]/table.tsx @@ -3,6 +3,7 @@ import { useCallback, useMemo, useReducer, useRef, useState } from 'react' import { createLogger } from '@sim/logger' import { useParams, useRouter } from 'next/navigation' +import { usePostHog } from 'posthog-js/react' import { Button, Modal, @@ -15,6 +16,7 @@ import { } from '@/components/emcn' import { Download, Pencil, Table as TableIcon, Trash, Upload } from '@/components/emcn/icons' import type { RunLimit, RunMode } from '@/lib/api/contracts/tables' +import { captureEvent } from '@/lib/posthog/client' import type { ColumnDefinition, Filter, TableRow as TableRowType, WorkflowGroup } from '@/lib/table' import { type ColumnOption, @@ -123,6 +125,10 @@ export function Table({ const workspaceId = propWorkspaceId || (params.workspaceId as string) const tableId = propTableId || (params.tableId as string) + const posthog = usePostHog() + const posthogRef = useRef(posthog) + posthogRef.current = posthog + useTableEventStream({ tableId, workspaceId }) const [slideout, dispatch] = useReducer(slideoutReducer, { kind: 'none' }) @@ -225,24 +231,40 @@ export function Table({ // gutter, action-bar Play/Refresh, right-click context menu) reduces to a // (groupIds, rowIds?, runMode) triple. Empty groupIds = no-op. const runScope = useCallback( - (args: { groupIds: string[]; rowIds?: string[]; runMode: RunMode; limit?: RunLimit }) => { - if (args.groupIds.length === 0) return - if (args.rowIds && args.rowIds.length === 0) return - runColumnMutate(args) + (args: { + groupIds: string[] + rowIds?: string[] + runMode: RunMode + limit?: RunLimit + source: 'row' | 'rows' | 'column' + }) => { + const { source, ...mutateArgs } = args + if (mutateArgs.groupIds.length === 0) return + if (mutateArgs.rowIds && mutateArgs.rowIds.length === 0) return + runColumnMutate(mutateArgs) + captureEvent(posthogRef.current, 'table_workflow_run', { + table_id: tableId, + workspace_id: workspaceId, + source, + run_mode: mutateArgs.runMode, + group_count: mutateArgs.groupIds.length, + row_count: mutateArgs.rowIds?.length ?? null, + has_limit: mutateArgs.limit != null, + }) }, - [runColumnMutate] + [runColumnMutate, tableId, workspaceId] ) const onRunColumn = useCallback( (groupId: string, runMode: RunMode, rowIds?: string[], limit?: RunLimit) => { - runScope({ groupIds: [groupId], rowIds, runMode, limit }) + runScope({ groupIds: [groupId], rowIds, runMode, limit, source: 'column' }) }, [runScope] ) const onRunRows = useCallback( (rowIds: string[], runMode: RunMode) => { - runScope({ groupIds: tableWorkflowGroups.map((g) => g.id), rowIds, runMode }) + runScope({ groupIds: tableWorkflowGroups.map((g) => g.id), rowIds, runMode, source: 'rows' }) }, [runScope, tableWorkflowGroups] ) @@ -253,6 +275,7 @@ export function Table({ groupIds: tableWorkflowGroups.map((g) => g.id), rowIds: [rowId], runMode: 'incomplete', + source: 'row', }) }, [runScope, tableWorkflowGroups] @@ -263,8 +286,14 @@ export function Table({ const onStopRow = useCallback( (rowId: string) => { cancelRunsMutate({ scope: 'row', rowId }) + captureEvent(posthogRef.current, 'table_workflow_stopped', { + table_id: tableId, + workspace_id: workspaceId, + scope: 'row', + row_count: 1, + }) }, - [cancelRunsMutate] + [cancelRunsMutate, tableId, workspaceId] ) const onStopRows = (rowIds: string[]) => { @@ -272,12 +301,24 @@ export function Table({ for (const rowId of rowIds) { cancelRunsMutate({ scope: 'row', rowId }) } + captureEvent(posthogRef.current, 'table_workflow_stopped', { + table_id: tableId, + workspace_id: workspaceId, + scope: 'rows', + row_count: rowIds.length, + }) } // useCallback because is memo-wrapped. const onStopAll = useCallback(() => { cancelRunsMutate({ scope: 'all' }) - }, [cancelRunsMutate]) + captureEvent(posthogRef.current, 'table_workflow_stopped', { + table_id: tableId, + workspace_id: workspaceId, + scope: 'all', + row_count: null, + }) + }, [cancelRunsMutate, tableId, workspaceId]) const onSelectionChange = (next: SelectionSnapshot) => { setSelection(next) @@ -547,6 +588,7 @@ export function Table({ groupIds: scope.groupIds, rowIds: scope.allRows ? undefined : scope.rowIds, runMode: 'incomplete', + source: 'rows', }) }} onRefresh={() => { @@ -556,6 +598,7 @@ export function Table({ groupIds: scope.groupIds, rowIds: scope.allRows ? undefined : scope.rowIds, runMode: 'all', + source: 'rows', }) }} onStopWorkflows={() => { diff --git a/apps/sim/lib/posthog/events.ts b/apps/sim/lib/posthog/events.ts index 73bce6f2ad1..c80da7076bf 100644 --- a/apps/sim/lib/posthog/events.ts +++ b/apps/sim/lib/posthog/events.ts @@ -379,6 +379,36 @@ export interface PostHogEventMap { workspace_id: string } + /** + * A table-workflow run was dispatched from the grid. + * `source` distinguishes the gesture: a single row's gutter Play (`row`), + * a multi-row selection across every workflow column (`rows`), or a single + * workflow column header / column-scoped selection (`column`). + */ + table_workflow_run: { + table_id: string + workspace_id: string + source: 'row' | 'rows' | 'column' + run_mode: 'all' | 'incomplete' + group_count: number + /** Number of explicitly targeted rows; `null` when the run targets all rows in scope. */ + row_count: number | null + has_limit: boolean + } + + /** + * Running table workflows were cancelled. + * `scope` is `all` (every running row), `row` (one row's gutter Stop), or + * `rows` (a multi-row selection). + */ + table_workflow_stopped: { + table_id: string + workspace_id: string + scope: 'all' | 'row' | 'rows' + /** Number of rows targeted; `null` for the `all` scope. */ + row_count: number | null + } + custom_tool_saved: { tool_id: string workspace_id: string