Skip to content

Commit fb0ff77

Browse files
committed
fix(tables): preserve OR boundaries when rules drop
1 parent 299f8ea commit fb0ff77

2 files changed

Lines changed: 66 additions & 3 deletions

File tree

apps/sim/app/workspace/[workspaceId]/tables/[tableId]/components/table-filter/table-filter.test.tsx

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,4 +235,59 @@ describe('TableFilter', () => {
235235
],
236236
})
237237
})
238+
239+
it('preserves an OR boundary when its first rule is cleared', () => {
240+
const onChange = vi.fn()
241+
renderFilter(onChange, {
242+
any: [
243+
{ all: [{ field: 'col-name', op: 'eq', value: 'Ada' }] },
244+
{
245+
all: [
246+
{ field: 'col-name', op: 'eq', value: 'Grace' },
247+
{ field: 'col-name', op: 'eq', value: 'Linus' },
248+
],
249+
},
250+
],
251+
})
252+
253+
const inputs = container.querySelectorAll<HTMLInputElement>(
254+
'input[placeholder="Enter a value"]'
255+
)
256+
act(() => typeInto(inputs[1], ''))
257+
act(() => inputs[1]?.dispatchEvent(new FocusEvent('focusout', { bubbles: true })))
258+
259+
expect(onChange).toHaveBeenCalledWith({
260+
any: [
261+
{ all: [{ field: 'col-name', op: 'eq', value: 'Ada' }] },
262+
{ all: [{ field: 'col-name', op: 'eq', value: 'Linus' }] },
263+
],
264+
})
265+
})
266+
267+
it('preserves an OR boundary when its first rule is removed', () => {
268+
const onChange = vi.fn()
269+
renderFilter(onChange, {
270+
any: [
271+
{ all: [{ field: 'col-name', op: 'eq', value: 'Ada' }] },
272+
{
273+
all: [
274+
{ field: 'col-name', op: 'eq', value: 'Grace' },
275+
{ field: 'col-name', op: 'eq', value: 'Linus' },
276+
],
277+
},
278+
],
279+
})
280+
281+
const removeButtons = container.querySelectorAll<HTMLButtonElement>(
282+
'button[aria-label="Remove filter"]'
283+
)
284+
act(() => removeButtons[1]?.click())
285+
286+
expect(onChange).toHaveBeenCalledWith({
287+
any: [
288+
{ all: [{ field: 'col-name', op: 'eq', value: 'Ada' }] },
289+
{ all: [{ field: 'col-name', op: 'eq', value: 'Linus' }] },
290+
],
291+
})
292+
})
238293
})

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

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,12 @@ function selectFilterOperators(column: ColumnDefinition | undefined): Set<string
3030

3131
function toAppliedPredicate(
3232
rules: FilterRule[],
33-
columns: ColumnDefinition[]
33+
columns: ColumnDefinition[],
34+
preserveIncompleteBoundaries = false
3435
): TablePredicate | null {
35-
const validRules = rules.filter(isCompleteRule)
36+
const validRules = preserveIncompleteBoundaries
37+
? rules.map((rule) => (isCompleteRule(rule) ? rule : { ...rule, column: '' }))
38+
: rules.filter(isCompleteRule)
3639
return filterRulesToPredicate(validRules, columns)
3740
}
3841

@@ -80,7 +83,7 @@ export function TableFilter({
8083
const deferredRule = nextRules.find((rule) => rule.id === deferIncompleteRuleId)
8184
if (deferredRule && !isCompleteRule(deferredRule)) return
8285

83-
const nextFilter = toAppliedPredicate(nextRules, columns)
86+
const nextFilter = toAppliedPredicate(nextRules, columns, true)
8487
const signature = JSON.stringify(nextFilter)
8588
if (signature === lastAppliedFilterRef.current) return
8689
lastAppliedFilterRef.current = signature
@@ -121,7 +124,12 @@ export function TableFilter({
121124
return
122125
}
123126
applyRules((current) => {
127+
const removedIndex = current.findIndex((rule) => rule.id === id)
128+
const removedRule = current[removedIndex]
124129
const next = current.filter((rule) => rule.id !== id)
130+
if (removedRule?.logicalOperator === 'or' && removedIndex < next.length) {
131+
next[removedIndex] = { ...next[removedIndex], logicalOperator: 'or' }
132+
}
125133
return next.length > 0 ? next : [createRule(columns)]
126134
})
127135
},

0 commit comments

Comments
 (0)