feat(tables): add PostHog events for table-workflow run/stop gestures (#4839)

* feat(tables): add PostHog events for table-workflow run/stop gestures

* fix(tables): pass run source from action-bar play/refresh dispatchers
This commit is contained in:
Theodore Li
2026-06-01 19:51:47 -04:00
committed by GitHub
parent a7b0bd311d
commit 8aa74a2d8d
2 changed files with 82 additions and 9 deletions
@@ -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 <RunStatusControl> 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={() => {
+30
View File
@@ -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