Skip to content

Commit 8dfcc90

Browse files
improvement(table): collapse run ops into run_column, derive action-bar buttons from selection
The action bar now reflects what's actually selected: - Selection-driven scope (cells the user highlighted, not their full rows) - Play visible when there's anything empty/failed; Refresh when there's anything completed; both for mixed - run_cell / run_row deleted; everything funnels through run_column - Per-row gutter Play, right-click "Run workflows on N rows", and column-header menu all share the canonical run path - Shared RunMode type from the contract; cleanup pass via /simplify (readExecution / isExecInFlight reuse, runScope helper, flat onViewExecution prop)
1 parent d8e96c5 commit 8dfcc90

12 files changed

Lines changed: 301 additions & 533 deletions

File tree

apps/sim/app/api/table/[tableId]/rows/[rowId]/cells/[groupId]/run/route.ts

Lines changed: 0 additions & 47 deletions
This file was deleted.

apps/sim/app/api/table/[tableId]/rows/run/route.ts

Lines changed: 0 additions & 46 deletions
This file was deleted.

apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/table-action-bar/table-action-bar.tsx

Lines changed: 104 additions & 143 deletions
Original file line numberDiff line numberDiff line change
@@ -6,38 +6,32 @@ import { Eye, PlayOutline, RefreshCw, Square } from '@/components/emcn/icons'
66
import { cn } from '@/lib/core/utils/cn'
77

88
interface TableActionBarProps {
9-
/** Number of rows currently selected (checkbox + multi-row range). 0 in
10-
* single-cell mode (use `singleCell` instead). */
11-
selectedCount: number
12-
/** Total running/queued workflow cells across the selected rows. Drives the
13-
* Stop button's visibility (hidden when 0) and label. */
9+
/** Number of (row × group) cells the run/stop buttons would target. Drives
10+
* the bar's leading label ("N cells"). */
11+
selectedCellCount: number
12+
/** Total running/queued workflow cells in the selection. Drives Stop. */
1413
runningCount: number
15-
/** Whether the table has any workflow columns. The bar is hidden entirely
16-
* when there are none — Run/Stop have nothing to act on. */
14+
/** Whether the table has any workflow columns. The bar hides entirely when
15+
* there are none — Run/Stop have nothing to act on. */
1716
hasWorkflowColumns: boolean
18-
/** Smart run: fire workflows only on rows whose cells are empty / errored
19-
* / cancelled. Skips already-completed cells. Maps to server
20-
* `runMode: 'incomplete'`. The default action — what "play" should
21-
* intuitively do. */
22-
onRun: () => void
23-
/** Forceful re-run: fire workflows on every selected row, including ones
24-
* that already have results. Maps to server `runMode: 'all'`. */
25-
onRerun: () => void
26-
/** Cancel running/queued cells across selected rows. */
17+
/** Show the Play (incomplete-mode) button — true when any selected cell is
18+
* empty / errored / cancelled. */
19+
showPlay: boolean
20+
/** Show the Refresh (all-mode) button — true when any selected cell is
21+
* already completed. */
22+
showRefresh: boolean
23+
/** Smart run: fire workflows only on cells that are empty / errored /
24+
* cancelled. Maps to server `runMode: 'incomplete'`. */
25+
onPlay: () => void
26+
/** Forceful re-run: fire workflows on every selected cell, including
27+
* completed ones. Maps to server `runMode: 'all'`. */
28+
onRefresh: () => void
29+
/** Cancel running/queued cells in the selection. */
2730
onStopWorkflows: () => void
28-
/**
29-
* When the user has a single workflow-output cell highlighted (no row
30-
* selection), the bar switches to a per-cell mode showing the cell's
31-
* status + an Eye button to open the execution log. `null` for multi-row
32-
* selections.
33-
*/
34-
singleCell?: {
35-
canViewExecution: boolean
36-
onViewExecution: () => void
37-
isRunning: boolean
38-
onRunCell: () => void
39-
onStopCell: () => void
40-
} | null
31+
/** When the user has highlighted exactly one workflow cell (or N adjacent
32+
* cells in the same row + group), surface a "View execution" affordance
33+
* alongside the run buttons. Omit when no single-execution view applies. */
34+
onViewExecution?: () => void
4135
/** Disables actions while a bulk mutation is in flight. */
4236
isLoading?: boolean
4337
/** Additional className for the floating wrapper — used to lift the bar
@@ -46,34 +40,39 @@ interface TableActionBarProps {
4640
}
4741

4842
/**
49-
* Floating action bar shown at the bottom of the table when one or more rows
50-
* are selected, OR when a single workflow-output cell is highlighted. Mirrors
51-
* the shell + interaction pattern from the knowledge-base `<ActionBar>`.
43+
* Floating action bar shown at the bottom of the table when one or more
44+
* workflow cells are highlighted. Play / Refresh visibility is data-driven:
45+
* Play appears when there's anything empty/failed in the selection; Refresh
46+
* appears when there's anything already completed; both when the selection is
47+
* mixed.
5248
*
5349
* Rendered with `position: absolute` inside the table's container (not
5450
* `fixed`) so it scopes to the table's bounds — important for embedded mode,
5551
* where the table sits inside a panel and a fixed-positioned bar would land
5652
* centered on the whole viewport instead of the panel.
5753
*/
5854
export function TableActionBar({
59-
selectedCount,
55+
selectedCellCount,
6056
runningCount,
6157
hasWorkflowColumns,
62-
onRun,
63-
onRerun,
58+
showPlay,
59+
showRefresh,
60+
onPlay,
61+
onRefresh,
6462
onStopWorkflows,
65-
singleCell,
63+
onViewExecution,
6664
isLoading = false,
6765
className,
6866
}: TableActionBarProps) {
69-
const isMultiRow = selectedCount > 0
70-
const isSingleCell = !isMultiRow && Boolean(singleCell)
71-
const visible = hasWorkflowColumns && (isMultiRow || isSingleCell)
67+
const visible =
68+
hasWorkflowColumns &&
69+
selectedCellCount > 0 &&
70+
(showPlay || showRefresh || runningCount > 0 || Boolean(onViewExecution))
7271
const stopLabel =
7372
runningCount === 1 ? 'Stop running workflow' : `Stop ${runningCount} running workflows`
74-
const runLabel = 'Run workflows on empty or failed cells'
75-
const rerunLabel =
76-
selectedCount === 1 ? 'Re-run workflows on row' : `Re-run workflows on ${selectedCount} rows`
73+
const playLabel =
74+
selectedCellCount === 1 ? 'Run cell' : `Run ${selectedCellCount} empty or failed cells`
75+
const refreshLabel = selectedCellCount === 1 ? 'Re-run cell' : `Re-run ${selectedCellCount} cells`
7776

7877
return (
7978
<AnimatePresence>
@@ -91,114 +90,76 @@ export function TableActionBar({
9190
>
9291
<div className='pointer-events-auto flex items-center gap-2 rounded-[10px] border border-[var(--border)] bg-[var(--surface-2)] px-2 py-1.5'>
9392
<span className='px-1 text-[var(--text-secondary)] text-small'>
94-
{isMultiRow ? `${selectedCount} selected` : 'Cell'}
93+
{selectedCellCount === 1 ? 'Cell' : `${selectedCellCount} cells`}
9594
</span>
9695

9796
<div className='flex items-center gap-[5px]'>
98-
{isMultiRow && (
99-
<>
100-
<Tooltip.Root>
101-
<Tooltip.Trigger asChild>
102-
<Button
103-
variant='ghost'
104-
onClick={onRun}
105-
disabled={isLoading}
106-
className='hover-hover:!text-[var(--text-inverse)] h-[28px] w-[28px] rounded-lg bg-[var(--surface-5)] p-0 text-[var(--text-secondary)] hover-hover:bg-[var(--brand-secondary)]'
107-
aria-label={runLabel}
108-
>
109-
<PlayOutline className='h-[12px] w-[12px]' />
110-
</Button>
111-
</Tooltip.Trigger>
112-
<Tooltip.Content side='top'>{runLabel}</Tooltip.Content>
113-
</Tooltip.Root>
114-
115-
<Tooltip.Root>
116-
<Tooltip.Trigger asChild>
117-
<Button
118-
variant='ghost'
119-
onClick={onRerun}
120-
disabled={isLoading}
121-
className='hover-hover:!text-[var(--text-inverse)] h-[28px] w-[28px] rounded-lg bg-[var(--surface-5)] p-0 text-[var(--text-secondary)] hover-hover:bg-[var(--brand-secondary)]'
122-
aria-label={rerunLabel}
123-
>
124-
<RefreshCw className='h-[12px] w-[12px]' />
125-
</Button>
126-
</Tooltip.Trigger>
127-
<Tooltip.Content side='top'>{rerunLabel}</Tooltip.Content>
128-
</Tooltip.Root>
129-
130-
{runningCount > 0 && (
131-
<Tooltip.Root>
132-
<Tooltip.Trigger asChild>
133-
<Button
134-
variant='ghost'
135-
onClick={onStopWorkflows}
136-
disabled={isLoading}
137-
className='hover-hover:!text-[var(--text-inverse)] h-[28px] w-[28px] rounded-lg bg-[var(--surface-5)] p-0 text-[var(--text-secondary)] hover-hover:bg-[var(--brand-secondary)]'
138-
aria-label={stopLabel}
139-
>
140-
<Square className='h-[12px] w-[12px]' />
141-
</Button>
142-
</Tooltip.Trigger>
143-
<Tooltip.Content side='top'>{stopLabel}</Tooltip.Content>
144-
</Tooltip.Root>
145-
)}
146-
</>
97+
{showPlay && (
98+
<Tooltip.Root>
99+
<Tooltip.Trigger asChild>
100+
<Button
101+
variant='ghost'
102+
onClick={onPlay}
103+
disabled={isLoading}
104+
className='hover-hover:!text-[var(--text-inverse)] h-[28px] w-[28px] rounded-lg bg-[var(--surface-5)] p-0 text-[var(--text-secondary)] hover-hover:bg-[var(--brand-secondary)]'
105+
aria-label={playLabel}
106+
>
107+
<PlayOutline className='h-[12px] w-[12px]' />
108+
</Button>
109+
</Tooltip.Trigger>
110+
<Tooltip.Content side='top'>{playLabel}</Tooltip.Content>
111+
</Tooltip.Root>
147112
)}
148113

149-
{isSingleCell && singleCell && (
150-
<>
151-
{!singleCell.isRunning && (
152-
<Tooltip.Root>
153-
<Tooltip.Trigger asChild>
154-
<Button
155-
variant='ghost'
156-
onClick={singleCell.onRunCell}
157-
disabled={isLoading}
158-
className='hover-hover:!text-[var(--text-inverse)] h-[28px] w-[28px] rounded-lg bg-[var(--surface-5)] p-0 text-[var(--text-secondary)] hover-hover:bg-[var(--brand-secondary)]'
159-
aria-label='Run cell'
160-
>
161-
<PlayOutline className='h-[12px] w-[12px]' />
162-
</Button>
163-
</Tooltip.Trigger>
164-
<Tooltip.Content side='top'>Run cell</Tooltip.Content>
165-
</Tooltip.Root>
166-
)}
114+
{showRefresh && (
115+
<Tooltip.Root>
116+
<Tooltip.Trigger asChild>
117+
<Button
118+
variant='ghost'
119+
onClick={onRefresh}
120+
disabled={isLoading}
121+
className='hover-hover:!text-[var(--text-inverse)] h-[28px] w-[28px] rounded-lg bg-[var(--surface-5)] p-0 text-[var(--text-secondary)] hover-hover:bg-[var(--brand-secondary)]'
122+
aria-label={refreshLabel}
123+
>
124+
<RefreshCw className='h-[12px] w-[12px]' />
125+
</Button>
126+
</Tooltip.Trigger>
127+
<Tooltip.Content side='top'>{refreshLabel}</Tooltip.Content>
128+
</Tooltip.Root>
129+
)}
167130

168-
{singleCell.isRunning && (
169-
<Tooltip.Root>
170-
<Tooltip.Trigger asChild>
171-
<Button
172-
variant='ghost'
173-
onClick={singleCell.onStopCell}
174-
disabled={isLoading}
175-
className='hover-hover:!text-[var(--text-inverse)] h-[28px] w-[28px] rounded-lg bg-[var(--surface-5)] p-0 text-[var(--text-secondary)] hover-hover:bg-[var(--brand-secondary)]'
176-
aria-label='Stop cell'
177-
>
178-
<Square className='h-[12px] w-[12px]' />
179-
</Button>
180-
</Tooltip.Trigger>
181-
<Tooltip.Content side='top'>Stop cell</Tooltip.Content>
182-
</Tooltip.Root>
183-
)}
131+
{runningCount > 0 && (
132+
<Tooltip.Root>
133+
<Tooltip.Trigger asChild>
134+
<Button
135+
variant='ghost'
136+
onClick={onStopWorkflows}
137+
disabled={isLoading}
138+
className='hover-hover:!text-[var(--text-inverse)] h-[28px] w-[28px] rounded-lg bg-[var(--surface-5)] p-0 text-[var(--text-secondary)] hover-hover:bg-[var(--brand-secondary)]'
139+
aria-label={stopLabel}
140+
>
141+
<Square className='h-[12px] w-[12px]' />
142+
</Button>
143+
</Tooltip.Trigger>
144+
<Tooltip.Content side='top'>{stopLabel}</Tooltip.Content>
145+
</Tooltip.Root>
146+
)}
184147

185-
{singleCell.canViewExecution && (
186-
<Tooltip.Root>
187-
<Tooltip.Trigger asChild>
188-
<Button
189-
variant='ghost'
190-
onClick={singleCell.onViewExecution}
191-
disabled={isLoading}
192-
className='hover-hover:!text-[var(--text-inverse)] h-[28px] w-[28px] rounded-lg bg-[var(--surface-5)] p-0 text-[var(--text-secondary)] hover-hover:bg-[var(--brand-secondary)]'
193-
aria-label='View execution'
194-
>
195-
<Eye className='h-[12px] w-[12px]' />
196-
</Button>
197-
</Tooltip.Trigger>
198-
<Tooltip.Content side='top'>View execution</Tooltip.Content>
199-
</Tooltip.Root>
200-
)}
201-
</>
148+
{onViewExecution && (
149+
<Tooltip.Root>
150+
<Tooltip.Trigger asChild>
151+
<Button
152+
variant='ghost'
153+
onClick={onViewExecution}
154+
disabled={isLoading}
155+
className='hover-hover:!text-[var(--text-inverse)] h-[28px] w-[28px] rounded-lg bg-[var(--surface-5)] p-0 text-[var(--text-secondary)] hover-hover:bg-[var(--brand-secondary)]'
156+
aria-label='View execution'
157+
>
158+
<Eye className='h-[12px] w-[12px]' />
159+
</Button>
160+
</Tooltip.Trigger>
161+
<Tooltip.Content side='top'>View execution</Tooltip.Content>
162+
</Tooltip.Root>
202163
)}
203164
</div>
204165
</div>

0 commit comments

Comments
 (0)