Skip to content

Commit d93f6d4

Browse files
talissoncostakyle-ssgclaude
authored
fix: trim whitespace in search filters to prevent API 400 errors (#6934)
Co-authored-by: kyle-ssg <kyle@solidstategroup.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent c7f9f29 commit d93f6d4

3 files changed

Lines changed: 41 additions & 1 deletion

File tree

frontend/common/utils/__tests__/featureFilterParams.test.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
buildApiFilterParams,
99
getFiltersFromParams,
1010
hasActiveFilters,
11+
normaliseFilters,
1112
} from 'common/utils/featureFilterParams'
1213
import { SortOrder } from 'common/types/requests'
1314
import { TagStrategy } from 'common/types/responses'
@@ -187,6 +188,24 @@ describe('featureFilterParams', () => {
187188
})
188189
})
189190

191+
describe('normaliseFilters', () => {
192+
it.each`
193+
field | input | expected
194+
${'search'} | ${' '} | ${null}
195+
${'search'} | ${''} | ${null}
196+
${'search'} | ${null} | ${null}
197+
${'search'} | ${' hello '} | ${'hello'}
198+
${'value_search'} | ${' '} | ${null}
199+
${'value_search'} | ${' test value '} | ${'test value'}
200+
`(
201+
'normalises $field "$input" to $expected',
202+
({ expected, field, input }) => {
203+
const result = normaliseFilters({ [field]: input })
204+
expect(result[field as keyof typeof result]).toBe(expected)
205+
},
206+
)
207+
})
208+
190209
describe('hasActiveFilters', () => {
191210
it('returns false for default filters', () => {
192211
expect(hasActiveFilters(createDefaultFilters())).toBe(false)

frontend/common/utils/featureFilterParams.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,20 @@ function parsePageNumber(value: string | string[] | undefined): number {
6767
return 1
6868
}
6969

70+
/** Normalises filter updates by trimming string fields to prevent whitespace-only API requests. */
71+
export function normaliseFilters(
72+
filters: Partial<FilterState>,
73+
): Partial<FilterState> {
74+
const result = { ...filters }
75+
if ('search' in result) {
76+
result.search = result.search?.trim() || null
77+
}
78+
if ('value_search' in result) {
79+
result.value_search = result.value_search?.trim() || null
80+
}
81+
return result
82+
}
83+
7084
/** Check if any filters are currently active. */
7185
export function hasActiveFilters(filters: FilterState): boolean {
7286
return !!(

frontend/web/components/pages/features/hooks/useFeatureFilters.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ import {
55
hasActiveFilters,
66
buildUrlParams,
77
getFiltersFromParams,
8+
normaliseFilters,
89
} from 'common/utils/featureFilterParams'
10+
import { isEqual } from 'lodash'
911
import Utils from 'common/utils/utils'
1012

1113
/**
@@ -43,7 +45,12 @@ export function useFeatureFilters(history: History): {
4345
}, [updateURLParams])
4446

4547
const handleFilterChange = (updates: Partial<FilterState>) => {
46-
setFilters((prev) => ({ ...prev, ...updates }))
48+
const normalised = normaliseFilters(updates)
49+
setFilters((prev) => {
50+
const next = { ...prev, ...normalised }
51+
if (isEqual(prev, next)) return prev
52+
return next
53+
})
4754
setPage(1)
4855
}
4956

0 commit comments

Comments
 (0)