Skip to content

Commit d6bf3cf

Browse files
Update tools
1 parent db9fcca commit d6bf3cf

22 files changed

Lines changed: 5307 additions & 7031 deletions

File tree

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,12 @@ export const POST = withRouteHandler(async (request: NextRequest, { params }: Ro
6262
return NextResponse.json({ error: 'Invalid workspace ID' }, { status: 400 })
6363
}
6464
const updatedTable = await addWorkflowGroup(
65-
{ tableId, group: validated.group, outputColumns: validated.outputColumns },
65+
{
66+
tableId,
67+
group: validated.group,
68+
outputColumns: validated.outputColumns,
69+
autoRun: validated.autoRun,
70+
},
6671
requestId
6772
)
6873
return NextResponse.json({

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,9 @@ export const PATCH = withRouteHandler(async (request: NextRequest, context: RowR
133133
table,
134134
requestId
135135
)
136+
// Only `null` when a `cancellationGuard` is supplied and the SQL guard
137+
// rejects the write — this route doesn't pass one, so reaching null is a bug.
138+
if (!updatedRow) throw new Error('updateRow returned null without a cancellationGuard')
136139

137140
return NextResponse.json({
138141
success: true,

apps/sim/app/api/v1/tables/[tableId]/rows/[rowId]/route.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,11 @@ export const PATCH = withRouteHandler(async (request: NextRequest, context: RowR
139139
table,
140140
requestId
141141
)
142+
// No `cancellationGuard` is passed here, so `updateRow` can't return null
143+
// from this caller. Defensive narrowing for TypeScript.
144+
if (!updatedRow) {
145+
return NextResponse.json({ error: 'Row not found' }, { status: 404 })
146+
}
142147

143148
return NextResponse.json({
144149
success: true,
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
'use client'
2+
3+
import type React from 'react'
4+
import { Circle } from 'lucide-react'
5+
import { Checkbox } from '@/components/emcn'
6+
import { Loader } from '@/components/emcn/icons/loader'
7+
import { cn } from '@/lib/core/utils/cn'
8+
import type { RowExecutionMetadata } from '@/lib/table'
9+
import type { SaveReason } from '../../../types'
10+
import { storageToDisplay } from '../../../utils'
11+
import type { DisplayColumn } from '../types'
12+
import { InlineEditor } from './inline-editors'
13+
14+
interface CellContentProps {
15+
value: unknown
16+
exec?: RowExecutionMetadata
17+
column: DisplayColumn
18+
isEditing: boolean
19+
initialCharacter?: string | null
20+
onSave: (value: unknown, reason: SaveReason) => void
21+
onCancel: () => void
22+
workflowNameById?: Record<string, string>
23+
}
24+
25+
/**
26+
* Renders the visible content of a single cell. Workflow-output cells follow
27+
* a status-state-machine (block error / value / running / waiting / cancelled
28+
* / dash); plain cells render the typed value. When `isEditing` is true the
29+
* `InlineEditor` overlay sits on top of the static content.
30+
*/
31+
export function CellContent({
32+
value,
33+
exec,
34+
column,
35+
isEditing,
36+
initialCharacter,
37+
onSave,
38+
onCancel,
39+
}: CellContentProps) {
40+
const isNull = value === null || value === undefined
41+
42+
let displayContent: React.ReactNode = null
43+
if (column.workflowGroupId) {
44+
const blockId = column.outputBlockId
45+
const blockError = blockId ? exec?.blockErrors?.[blockId] : undefined
46+
const blockRunning = blockId ? (exec?.runningBlockIds?.includes(blockId) ?? false) : false
47+
const hasValue = !isNull
48+
const valueText =
49+
typeof value === 'string'
50+
? value
51+
: value === null || value === undefined
52+
? ''
53+
: JSON.stringify(value)
54+
55+
// Once any block in the group has reported an error, downstream cells
56+
// that haven't started won't run on this attempt — collapse them to dash
57+
// instead of leaving a stale "Waiting" spinner if the cell task didn't
58+
// reach a clean terminal state.
59+
const groupHasBlockErrors = !!(exec?.blockErrors && Object.keys(exec.blockErrors).length > 0)
60+
if (blockError) {
61+
displayContent = (
62+
<span
63+
className='block overflow-clip text-ellipsis text-[var(--text-error)]'
64+
title={blockError}
65+
>
66+
Error
67+
</span>
68+
)
69+
} else if (hasValue) {
70+
displayContent = (
71+
<span className='block overflow-clip text-ellipsis text-[var(--text-primary)]'>
72+
{valueText}
73+
</span>
74+
)
75+
} else if (
76+
(exec?.status === 'running' || exec?.status === 'pending') &&
77+
!(groupHasBlockErrors && !blockRunning)
78+
) {
79+
// Motion only when this cell's own block is in flight. Pending and
80+
// upstream-blocked Waiting render as static dots — the moving spinner
81+
// is reserved for "right now, actually running".
82+
if (blockRunning) {
83+
displayContent = (
84+
<div className='flex min-h-[20px] min-w-0 items-center gap-1.5'>
85+
<Loader animate className='h-3.5 w-3.5 shrink-0 text-[var(--text-tertiary)]' />
86+
<span className='min-w-0 overflow-clip text-ellipsis whitespace-nowrap text-[var(--text-tertiary)]'>
87+
Running
88+
</span>
89+
</div>
90+
)
91+
} else {
92+
const label = exec.status === 'pending' ? 'Pending' : 'Waiting'
93+
displayContent = (
94+
<div className='flex min-h-[20px] min-w-0 items-center gap-1.5'>
95+
<Circle className='h-[10px] w-[10px] shrink-0 text-[var(--text-tertiary)]' />
96+
<span className='min-w-0 overflow-clip text-ellipsis whitespace-nowrap text-[var(--text-tertiary)]'>
97+
{label}
98+
</span>
99+
</div>
100+
)
101+
}
102+
} else if (exec?.status === 'cancelled') {
103+
displayContent = (
104+
<span className='block overflow-clip text-ellipsis text-[var(--text-tertiary)]'>
105+
Cancelled
106+
</span>
107+
)
108+
} else {
109+
displayContent = <span className='text-[var(--text-tertiary)]'></span>
110+
}
111+
return displayContent
112+
}
113+
if (column.type === 'boolean') {
114+
displayContent = (
115+
<div
116+
className={cn('flex min-h-[20px] items-center justify-center', isEditing && 'invisible')}
117+
>
118+
<Checkbox size='sm' checked={Boolean(value)} className='pointer-events-none' />
119+
</div>
120+
)
121+
} else if (!isNull && column.type === 'json') {
122+
displayContent = (
123+
<span
124+
className={cn(
125+
'block overflow-clip text-ellipsis text-[var(--text-primary)]',
126+
isEditing && 'invisible'
127+
)}
128+
>
129+
{JSON.stringify(value)}
130+
</span>
131+
)
132+
} else if (!isNull && column.type === 'date') {
133+
displayContent = (
134+
<span className={cn('text-[var(--text-primary)]', isEditing && 'invisible')}>
135+
{storageToDisplay(String(value))}
136+
</span>
137+
)
138+
} else if (!isNull) {
139+
displayContent = (
140+
<span
141+
className={cn(
142+
'block overflow-clip text-ellipsis text-[var(--text-primary)]',
143+
isEditing && 'invisible'
144+
)}
145+
>
146+
{String(value)}
147+
</span>
148+
)
149+
}
150+
151+
return (
152+
<>
153+
{isEditing && (
154+
<div className='absolute inset-0 z-10 flex items-start px-0'>
155+
<InlineEditor
156+
value={value}
157+
column={column}
158+
initialCharacter={initialCharacter ?? undefined}
159+
onSave={onSave}
160+
onCancel={onCancel}
161+
/>
162+
</div>
163+
)}
164+
{displayContent}
165+
</>
166+
)
167+
}

0 commit comments

Comments
 (0)