Skip to content

Commit 04c9204

Browse files
committed
fix(tables): keep column rename migration atomic
1 parent 242207e commit 04c9204

3 files changed

Lines changed: 28 additions & 48 deletions

File tree

‎apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/column-config-sidebar/column-config-sidebar.test.tsx‎

Lines changed: 4 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -103,12 +103,6 @@ function findButton(label: string): HTMLButtonElement | undefined {
103103
)
104104
}
105105

106-
function setInputValue(input: HTMLInputElement, value: string): void {
107-
const valueSetter = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value')?.set
108-
valueSetter?.call(input, value)
109-
input.dispatchEvent(new Event('input', { bubbles: true }))
110-
}
111-
112106
beforeEach(() => {
113107
globalThis.IS_REACT_ACT_ENVIRONMENT = true
114108
container = document.createElement('div')
@@ -196,8 +190,7 @@ describe('ColumnConfigSidebar', () => {
196190
expect(mockUpdateColumn).not.toHaveBeenCalled()
197191
})
198192

199-
it('edits a Reference column name and target table together', async () => {
200-
const onColumnRename = vi.fn()
193+
it('edits Reference configuration without exposing column renaming', async () => {
201194
await act(async () => {
202195
root.render(
203196
<ColumnConfigSidebar
@@ -213,27 +206,21 @@ describe('ColumnConfigSidebar', () => {
213206
tableRowTtlEnabled={false}
214207
workspaceId='workspace-1'
215208
tableId='table-current'
216-
onColumnRename={onColumnRename}
217209
referenceColumnsEnabled
218210
/>
219211
)
220212
})
221213

222-
const nameInput = container.querySelector<HTMLInputElement>('#column-sidebar-name')
223-
expect(nameInput?.value).toBe('Related row')
214+
expect(container).not.toHaveTextContent('Column name')
215+
expect(container.querySelector('#column-sidebar-name')).toBeNull()
224216

225-
act(() => setInputValue(nameInput!, 'Renamed relation'))
226217
act(() => findCombobox('Select table')?.onChange?.('table-customers'))
227218
await act(async () => findButton('Save')?.click())
228219

229220
expect(mockUpdateColumn).toHaveBeenCalledWith({
230221
columnName: 'col-reference',
231-
updates: {
232-
name: 'Renamed relation',
233-
referenceTableId: 'table-customers',
234-
},
222+
updates: { referenceTableId: 'table-customers' },
235223
})
236-
expect(onColumnRename).toHaveBeenCalledWith('col-reference', 'Renamed relation')
237224
})
238225

239226
it('keeps an existing Reference column visible but not retargetable when disabled', async () => {

‎apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/column-config-sidebar/column-config-sidebar.tsx‎

Lines changed: 21 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -77,9 +77,6 @@ interface ColumnConfigSidebarProps {
7777
readOnly?: boolean
7878
/** Why saving is unavailable; surfaced on the disabled Save button. */
7979
readOnlyReason?: string
80-
/** Notify parent of a rename so it can rewrite local `columnOrder` /
81-
* `columnWidths` keys that reference the old name. */
82-
onColumnRename?: (oldName: string, newName: string) => void
8380
}
8481

8582
/**
@@ -132,7 +129,6 @@ function ColumnConfigBody({
132129
tableId,
133130
readOnly,
134131
readOnlyReason,
135-
onColumnRename,
136132
}: ColumnConfigBodyProps) {
137133
const updateColumn = useUpdateColumn({ workspaceId, tableId })
138134
const addColumn = useAddTableColumn({ workspaceId, tableId })
@@ -210,7 +206,7 @@ function ColumnConfigBody({
210206
async function handleSave() {
211207
// Belt and braces: the button is disabled, and the server refuses too.
212208
if (readOnly) return
213-
if (!trimmedName) {
209+
if (config.mode === 'create' && !trimmedName) {
214210
setShowValidation(true)
215211
return
216212
}
@@ -242,7 +238,6 @@ function ColumnConfigBody({
242238
return
243239
}
244240

245-
const renamed = trimmedName !== (existingColumn?.name ?? config.columnName)
246241
const typeChanged = !!existingColumn && existingColumn.type !== typeInput
247242
const uniqueChanged =
248243
supportsUnique && !!existingColumn && !!existingColumn.unique !== uniqueInput
@@ -256,15 +251,13 @@ function ColumnConfigBody({
256251
wantsReference && existingColumn?.referenceTableId !== referenceTableInput
257252

258253
const updates: {
259-
name?: string
260254
type?: ColumnDefinition['type']
261255
unique?: boolean
262256
options?: SelectOption[]
263257
multiple?: boolean
264258
currencyCode?: string
265259
referenceTableId?: string
266260
} = {
267-
...(renamed ? { name: trimmedName } : {}),
268261
...(typeChanged ? { type: typeInput } : {}),
269262
...(uniqueChanged ? { unique: uniqueInput } : {}),
270263
...(uniqueCleared ? { unique: false } : {}),
@@ -283,8 +276,7 @@ function ColumnConfigBody({
283276
}
284277

285278
await updateColumn.mutateAsync({ columnName: config.columnName, updates })
286-
if (renamed) onColumnRename?.(config.columnName, trimmedName)
287-
toast.success(`Saved "${trimmedName}"`)
279+
toast.success(`Saved "${existingColumn?.name ?? config.columnName}"`)
288280
onClose()
289281
} catch (err) {
290282
if (isValidationError(err)) {
@@ -321,23 +313,25 @@ function ColumnConfigBody({
321313
including the comboboxes' trigger buttons; `contents` keeps the
322314
existing layout. Values stay readable and selectable. */}
323315
<fieldset disabled={readOnly} className='contents'>
324-
<div className='flex flex-col gap-[9.5px]'>
325-
<RequiredLabel htmlFor='column-sidebar-name'>Column name</RequiredLabel>
326-
<ChipInput
327-
id='column-sidebar-name'
328-
value={nameInput}
329-
onChange={(e) => {
330-
setNameInput(e.target.value)
331-
if (nameError) setNameError(null)
332-
}}
333-
spellCheck={false}
334-
autoComplete='off'
335-
error={Boolean((showValidation && !trimmedName) || nameError)}
336-
aria-invalid={(showValidation && !trimmedName) || nameError ? true : undefined}
337-
/>
338-
{showValidation && !trimmedName && <FieldError message='Column name is required' />}
339-
{nameError && !(showValidation && !trimmedName) && <FieldError message={nameError} />}
340-
</div>
316+
{config.mode === 'create' && (
317+
<div className='flex flex-col gap-[9.5px]'>
318+
<RequiredLabel htmlFor='column-sidebar-name'>Column name</RequiredLabel>
319+
<ChipInput
320+
id='column-sidebar-name'
321+
value={nameInput}
322+
onChange={(e) => {
323+
setNameInput(e.target.value)
324+
if (nameError) setNameError(null)
325+
}}
326+
spellCheck={false}
327+
autoComplete='off'
328+
error={Boolean((showValidation && !trimmedName) || nameError)}
329+
aria-invalid={(showValidation && !trimmedName) || nameError ? true : undefined}
330+
/>
331+
{showValidation && !trimmedName && <FieldError message='Column name is required' />}
332+
{nameError && !(showValidation && !trimmedName) && <FieldError message={nameError} />}
333+
</div>
334+
)}
341335

342336
{config.mode === 'edit' && (
343337
<>

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

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -328,9 +328,9 @@ export function Table({
328328
}, [])
329329

330330
/**
331-
* Sink populated by the grid: invoked from sidebar `onColumnRename` so the
332-
* grid can rewrite its local `columnWidths` / `columnOrder` keys after a
333-
* rename. The grid's render assigns to `current`; the wrapper forwards calls.
331+
* Sink populated by the grid: invoked from the workflow sidebar after a
332+
* rename so the grid can rewrite its local `columnWidths` / `columnOrder`
333+
* keys. The grid's render assigns to `current`; the wrapper forwards calls.
334334
*/
335335
const columnRenameSinkRef = useRef<((oldName: string, newName: string) => void) | null>(null)
336336
const onColumnRename = (oldName: string, newName: string) => {
@@ -1737,7 +1737,6 @@ export function Table({
17371737
? LOCK_TOOLTIPS.schema
17381738
: 'You don’t have permission to change columns.'
17391739
}
1740-
onColumnRename={onColumnRename}
17411740
/>
17421741
<EnrichmentsSidebar
17431742
open={slideout.kind === 'enrichments'}

0 commit comments

Comments
 (0)