Skip to content

Commit d43f163

Browse files
authored
fix(tables): show rejected filter errors in the grid and fill imports to the row limit (#9881)
* fix(tables): show rejected filter errors in the grid and fill imports to the row limit * fix(tables): use caption size for the grid error and drop the redundant mock reset
1 parent 9c3477d commit d43f163

4 files changed

Lines changed: 275 additions & 110 deletions

File tree

‎apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/table-grid/table-grid.tsx‎

Lines changed: 109 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -590,6 +590,7 @@ export function TableGrid({
590590
rows,
591591
rowTotal,
592592
isLoadingRows,
593+
rowsError,
593594
fetchNextPage,
594595
hasNextPage,
595596
isFetchingNextPage,
@@ -4973,104 +4974,114 @@ export function TableGrid({
49734974
)}
49744975
</thead>
49754976
<tbody ref={tbodyRef}>
4976-
{isLoadingTable || isLoadingRows
4977-
? null
4978-
: (() => {
4979-
const virtualItems = rowVirtualizer.getVirtualItems()
4980-
// `item.start`/`item.end` include `scrollMargin` (the sticky-header
4981-
// offset) but `getTotalSize()` already nets it out, so both spacer
4982-
// heights are computed relative to `scrollMargin`.
4983-
const scrollMargin = rowVirtualizer.options.scrollMargin
4984-
const paddingTop =
4985-
virtualItems.length > 0 ? virtualItems[0].start - scrollMargin : 0
4986-
const paddingBottom =
4987-
virtualItems.length > 0
4988-
? rowVirtualizer.getTotalSize() -
4989-
(virtualItems[virtualItems.length - 1].end - scrollMargin)
4990-
: 0
4991-
return (
4992-
<>
4993-
{paddingTop > 0 && (
4994-
<tr aria-hidden>
4995-
<td
4996-
colSpan={displayColumns.length + 1}
4997-
style={{ height: paddingTop }}
4998-
/>
4999-
</tr>
5000-
)}
5001-
{virtualItems.map((virtualRow) => {
5002-
const index = virtualRow.index
5003-
const row = rows[index]
5004-
if (!row) return null
5005-
return (
5006-
<DataRow
5007-
key={row.id}
5008-
row={row}
5009-
columns={displayColumns}
5010-
workspaceId={workspaceId}
5011-
timezoneStatus={timezoneState.status}
5012-
rowIndex={index}
5013-
isFirstRow={index === 0}
5014-
editingColumnName={
5015-
editingCell?.rowId === row.id ? editingCell.columnName : null
5016-
}
5017-
initialCharacter={
5018-
editingCell?.rowId === row.id ? initialCharacter : null
5019-
}
5020-
editorsReadOnly={Boolean(locks?.updateLocked)}
5021-
pendingCellValue={
5022-
pendingUpdate && pendingUpdate.rowId === row.id
5023-
? pendingUpdate.data
5024-
: null
5025-
}
5026-
normalizedSelection={normalizedSelection}
5027-
onClick={handleCellClick}
5028-
onDoubleClick={handleCellDoubleClick}
5029-
onSave={handleInlineSave}
5030-
onCancel={handleInlineCancel}
5031-
onContextMenu={handleRowContextMenu}
5032-
onCellMouseDown={handleCellMouseDown}
5033-
onCellMouseEnter={handleCellMouseEnter}
5034-
isRowChecked={rowSelectionIncludes(rowSelection, row.id)}
5035-
onRowToggle={handleRowToggle}
5036-
onRowMouseDown={handleRowMouseDown}
5037-
onRowMouseEnter={handleRowMouseEnter}
5038-
runningCount={runningByRowId[row.id] ?? 0}
5039-
hasWorkflowColumns={hasWorkflowColumns}
5040-
numRegionWidth={numRegionWidth}
5041-
onStopRow={onStopRow}
5042-
onRunRow={onRunRow}
5043-
workflowGroups={tableWorkflowGroups}
5044-
activeDispatches={activeDispatches}
5045-
pinnedOffsets={pinnedOffsets.size > 0 ? pinnedOffsets : undefined}
5046-
lastPinnedColKey={lastPinnedColKey}
5047-
findMatchColumns={findMatchColumnsByRowId.get(row.id)}
5048-
/>
5049-
)
5050-
})}
5051-
{paddingBottom > 0 && (
5052-
<tr aria-hidden>
5053-
<td
5054-
colSpan={displayColumns.length + 1}
5055-
style={{ height: paddingBottom }}
5056-
/>
5057-
</tr>
5058-
)}
5059-
{isFetchingNextPage && (
5060-
<tr>
5061-
<td colSpan={displayColumns.length + 1} className='h-[35px] p-0'>
5062-
<div className='flex items-center justify-center'>
5063-
<Loader
5064-
animate
5065-
className='size-[14px] shrink-0 text-[var(--text-tertiary)]'
5066-
/>
5067-
</div>
5068-
</td>
5069-
</tr>
5070-
)}
5071-
</>
5072-
)
5073-
})()}
4977+
{isLoadingTable || isLoadingRows ? null : rowsError ? (
4978+
<tr>
4979+
<td
4980+
colSpan={displayColumns.length + 1}
4981+
role='alert'
4982+
className='px-4 py-3 text-[var(--text-error)] text-caption'
4983+
>
4984+
{rowsError.message}
4985+
</td>
4986+
</tr>
4987+
) : (
4988+
(() => {
4989+
const virtualItems = rowVirtualizer.getVirtualItems()
4990+
// `item.start`/`item.end` include `scrollMargin` (the sticky-header
4991+
// offset) but `getTotalSize()` already nets it out, so both spacer
4992+
// heights are computed relative to `scrollMargin`.
4993+
const scrollMargin = rowVirtualizer.options.scrollMargin
4994+
const paddingTop =
4995+
virtualItems.length > 0 ? virtualItems[0].start - scrollMargin : 0
4996+
const paddingBottom =
4997+
virtualItems.length > 0
4998+
? rowVirtualizer.getTotalSize() -
4999+
(virtualItems[virtualItems.length - 1].end - scrollMargin)
5000+
: 0
5001+
return (
5002+
<>
5003+
{paddingTop > 0 && (
5004+
<tr aria-hidden>
5005+
<td
5006+
colSpan={displayColumns.length + 1}
5007+
style={{ height: paddingTop }}
5008+
/>
5009+
</tr>
5010+
)}
5011+
{virtualItems.map((virtualRow) => {
5012+
const index = virtualRow.index
5013+
const row = rows[index]
5014+
if (!row) return null
5015+
return (
5016+
<DataRow
5017+
key={row.id}
5018+
row={row}
5019+
columns={displayColumns}
5020+
workspaceId={workspaceId}
5021+
timezoneStatus={timezoneState.status}
5022+
rowIndex={index}
5023+
isFirstRow={index === 0}
5024+
editingColumnName={
5025+
editingCell?.rowId === row.id ? editingCell.columnName : null
5026+
}
5027+
initialCharacter={
5028+
editingCell?.rowId === row.id ? initialCharacter : null
5029+
}
5030+
editorsReadOnly={Boolean(locks?.updateLocked)}
5031+
pendingCellValue={
5032+
pendingUpdate && pendingUpdate.rowId === row.id
5033+
? pendingUpdate.data
5034+
: null
5035+
}
5036+
normalizedSelection={normalizedSelection}
5037+
onClick={handleCellClick}
5038+
onDoubleClick={handleCellDoubleClick}
5039+
onSave={handleInlineSave}
5040+
onCancel={handleInlineCancel}
5041+
onContextMenu={handleRowContextMenu}
5042+
onCellMouseDown={handleCellMouseDown}
5043+
onCellMouseEnter={handleCellMouseEnter}
5044+
isRowChecked={rowSelectionIncludes(rowSelection, row.id)}
5045+
onRowToggle={handleRowToggle}
5046+
onRowMouseDown={handleRowMouseDown}
5047+
onRowMouseEnter={handleRowMouseEnter}
5048+
runningCount={runningByRowId[row.id] ?? 0}
5049+
hasWorkflowColumns={hasWorkflowColumns}
5050+
numRegionWidth={numRegionWidth}
5051+
onStopRow={onStopRow}
5052+
onRunRow={onRunRow}
5053+
workflowGroups={tableWorkflowGroups}
5054+
activeDispatches={activeDispatches}
5055+
pinnedOffsets={pinnedOffsets.size > 0 ? pinnedOffsets : undefined}
5056+
lastPinnedColKey={lastPinnedColKey}
5057+
findMatchColumns={findMatchColumnsByRowId.get(row.id)}
5058+
/>
5059+
)
5060+
})}
5061+
{paddingBottom > 0 && (
5062+
<tr aria-hidden>
5063+
<td
5064+
colSpan={displayColumns.length + 1}
5065+
style={{ height: paddingBottom }}
5066+
/>
5067+
</tr>
5068+
)}
5069+
{isFetchingNextPage && (
5070+
<tr>
5071+
<td colSpan={displayColumns.length + 1} className='h-[35px] p-0'>
5072+
<div className='flex items-center justify-center'>
5073+
<Loader
5074+
animate
5075+
className='size-[14px] shrink-0 text-[var(--text-tertiary)]'
5076+
/>
5077+
</div>
5078+
</td>
5079+
</tr>
5080+
)}
5081+
</>
5082+
)
5083+
})()
5084+
)}
50745085
</tbody>
50755086
</table>
50765087
{remoteSelections.length > 0 && (

‎apps/sim/app/workspace/[workspaceId]/tables/[tableId]/hooks/use-table.ts‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,12 @@ export interface UseTableReturn {
5454
*/
5555
filter: TablePredicate | null
5656
isLoadingRows: boolean
57+
/**
58+
* Why the first page of rows failed to load (e.g. a filter the server
59+
* rejects), or null. Without it a rejected query is indistinguishable from
60+
* an empty result.
61+
*/
62+
rowsError: Error | null
5763
refetchRows: () => void
5864
/**
5965
* The resolved value's `hasNextPage` reflects the post-fetch cache state —
@@ -106,6 +112,7 @@ export function useTable({ workspaceId, tableId, queryOptions }: UseTableParams)
106112
const {
107113
data: rowsData,
108114
isLoading: isLoadingRows,
115+
error: rowsQueryError,
109116
refetch,
110117
fetchNextPage,
111118
hasNextPage,
@@ -124,6 +131,10 @@ export function useTable({ workspaceId, tableId, queryOptions }: UseTableParams)
124131
[rowsData?.pages]
125132
)
126133

134+
// Only a failed first page leaves the grid with nothing to show; a failed
135+
// next page or background refetch keeps the rows already loaded.
136+
const rowsError = rowsData === undefined ? rowsQueryError : null
137+
127138
// Server-side COUNT(*) for the active filter (page 0 only). Null until the first page lands;
128139
// callers fall back to the table's unfiltered `rowCount`. This is the true "select all" total —
129140
// it reflects the filter, unlike `tableData.rowCount`.
@@ -264,6 +275,7 @@ export function useTable({ workspaceId, tableId, queryOptions }: UseTableParams)
264275
rowTotal,
265276
filter,
266277
isLoadingRows,
278+
rowsError,
267279
refetchRows,
268280
fetchNextPage: fetchNextPageWrapped,
269281
hasNextPage: Boolean(hasNextPage),

0 commit comments

Comments
 (0)