Skip to content

Commit 39be514

Browse files
waleedlatif1claude
andcommitted
fix(tables): toast on delete-column failure, case-insensitive row cleanup, rename row-data keys
Address greptile review: - useDeleteColumn now toasts on non-validation errors so users see when the column "snaps back" after a server/network failure - Row data cleanup matches keys case-insensitively in both useDeleteColumn and useUpdateColumn so a column stored as "Age" is cleaned even when the request uses "age" - useUpdateColumn now migrates row-data keys when updates.name is set, preventing blank cells during the server round-trip on a rename Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent b4ae6bc commit 39be514

2 files changed

Lines changed: 70 additions & 5 deletions

File tree

apps/sim/hooks/queries/tables.test.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,4 +204,48 @@ describe('useUpdateColumn optimistic update', () => {
204204

205205
expect(getCache(tableKeys.detail(TABLE_ID))).toEqual(original)
206206
})
207+
208+
it('renames the corresponding row-data key when updates.name is set', async () => {
209+
setCache(tableKeys.detail(TABLE_ID), {
210+
id: TABLE_ID,
211+
schema: { columns: [{ name: 'age', type: 'number' }] },
212+
})
213+
setCache(tableKeys.rowsRoot(TABLE_ID), {
214+
rows: [
215+
{ id: 'r1', data: { age: 30 } },
216+
{ id: 'r2', data: { age: 40 } },
217+
],
218+
totalCount: 2,
219+
})
220+
221+
const hook = useUpdateColumn({ workspaceId: WORKSPACE_ID, tableId: TABLE_ID })
222+
await hook.onMutate?.({ columnName: 'age', updates: { name: 'years' } })
223+
224+
const rows = getCache<{ rows: Array<{ data: Record<string, unknown> }> }>(
225+
tableKeys.rowsRoot(TABLE_ID)
226+
)
227+
expect(rows?.rows[0]?.data).toEqual({ years: 30 })
228+
expect(rows?.rows[1]?.data).toEqual({ years: 40 })
229+
})
230+
})
231+
232+
describe('useDeleteColumn case-insensitive row cleanup', () => {
233+
it('strips the row data key even when stored casing differs from the requested name', async () => {
234+
setCache(tableKeys.detail(TABLE_ID), {
235+
id: TABLE_ID,
236+
schema: { columns: [{ name: 'Age', type: 'number' }] },
237+
})
238+
setCache(tableKeys.rowsRoot(TABLE_ID), {
239+
rows: [{ id: 'r1', data: { Age: 30, name: 'a' } }],
240+
totalCount: 1,
241+
})
242+
243+
const hook = useDeleteColumn({ workspaceId: WORKSPACE_ID, tableId: TABLE_ID })
244+
await hook.onMutate?.('age')
245+
246+
const rows = getCache<{ rows: Array<{ data: Record<string, unknown> }> }>(
247+
tableKeys.rowsRoot(TABLE_ID)
248+
)
249+
expect(rows?.rows[0]?.data).toEqual({ name: 'a' })
250+
})
207251
})

apps/sim/hooks/queries/tables.ts

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -885,12 +885,30 @@ export function useUpdateColumn({ workspaceId, tableId }: RowMutationContext) {
885885
schema: { ...previousDetail.schema, columns: nextColumns },
886886
})
887887
}
888-
return { previousDetail }
888+
889+
const newName = (updates as { name?: string }).name
890+
const rowSnapshots =
891+
typeof newName === 'string' && newName.length > 0 && newName !== columnName
892+
? await snapshotAndMutateRows(queryClient, tableId, (row) => {
893+
const lower = columnName.toLowerCase()
894+
const matchKey = Object.keys(row.data).find((k) => k.toLowerCase() === lower)
895+
if (!matchKey) return null
896+
const { [matchKey]: value, ...rest } = row.data
897+
return { ...row, data: { ...rest, [newName]: value } }
898+
})
899+
: []
900+
901+
return { previousDetail, rowSnapshots }
889902
},
890903
onError: (error, _vars, context) => {
891904
if (context?.previousDetail) {
892905
queryClient.setQueryData(tableKeys.detail(tableId), context.previousDetail)
893906
}
907+
if (context?.rowSnapshots) {
908+
for (const [key, data] of context.rowSnapshots) {
909+
queryClient.setQueryData(key, data)
910+
}
911+
}
894912
// Validation errors are surfaced as inline FieldErrors by the caller.
895913
if (isValidationError(error)) return
896914
toast.error(error.message, { duration: 5000 })
@@ -1173,9 +1191,9 @@ export function useDeleteColumn({ workspaceId, tableId }: RowMutationContext) {
11731191
onMutate: async (columnName) => {
11741192
await queryClient.cancelQueries({ queryKey: tableKeys.detail(tableId) })
11751193

1194+
const lower = columnName.toLowerCase()
11761195
const previousDetail = queryClient.getQueryData<TableDefinition>(tableKeys.detail(tableId))
11771196
if (previousDetail) {
1178-
const lower = columnName.toLowerCase()
11791197
const nextColumns = previousDetail.schema.columns.filter(
11801198
(c) => c.name.toLowerCase() !== lower
11811199
)
@@ -1196,14 +1214,15 @@ export function useDeleteColumn({ workspaceId, tableId }: RowMutationContext) {
11961214
}
11971215

11981216
const rowSnapshots = await snapshotAndMutateRows(queryClient, tableId, (row) => {
1199-
if (!(columnName in row.data)) return null
1200-
const { [columnName]: _removed, ...rest } = row.data
1217+
const matchKey = Object.keys(row.data).find((k) => k.toLowerCase() === lower)
1218+
if (!matchKey) return null
1219+
const { [matchKey]: _removed, ...rest } = row.data
12011220
return { ...row, data: rest }
12021221
})
12031222

12041223
return { previousDetail, rowSnapshots }
12051224
},
1206-
onError: (_err, _columnName, context) => {
1225+
onError: (error, _columnName, context) => {
12071226
if (context?.previousDetail) {
12081227
queryClient.setQueryData(tableKeys.detail(tableId), context.previousDetail)
12091228
}
@@ -1212,6 +1231,8 @@ export function useDeleteColumn({ workspaceId, tableId }: RowMutationContext) {
12121231
queryClient.setQueryData(key, data)
12131232
}
12141233
}
1234+
if (isValidationError(error)) return
1235+
toast.error(error.message, { duration: 5000 })
12151236
},
12161237
onSettled: () => {
12171238
invalidateTableSchema(queryClient, tableId)

0 commit comments

Comments
 (0)