fix(filters): support project filter in parentheses (#1647)

The filter regex pattern was not matching values inside parentheses correctly.
The lookahead pattern only allowed `&&`, `||`, or end-of-string after filter
values, but when filters are wrapped in parentheses like `( project = Filtertest )`,
the closing `)` appears after the value.

Fixed by adding `\)` to the lookahead pattern so it correctly handles closing
parentheses. This allows the project filter (and other filters) to work
properly when nested in parentheses.

- Added tests for project filters in parentheses (both frontend and backend)
- Backend tests confirm the backend already handled this correctly
- Frontend regex pattern now matches the backend behavior

Fixes #1645
This commit is contained in:
kolaente
2025-10-13 11:10:22 +02:00
committed by GitHub
parent 4383948275
commit c8837aeaeb
3 changed files with 48 additions and 1 deletions

View File

@@ -187,6 +187,26 @@ describe('Filter Transformation', () => {
expect(transformed).toBe('project = 1')
})
it('should correctly resolve project in parentheses', () => {
const transformed = transformFilterStringForApi(
'( project = Filtertest )',
nullTitleToIdResolver,
(title: string) => title === 'Filtertest' ? 123 : null,
)
expect(transformed).toBe('( project = 123 )')
})
it('should correctly resolve project with OR in parentheses', () => {
const transformed = transformFilterStringForApi(
'( labels = label || project = Filtertest )',
(title: string) => title === 'label' ? 456 : null,
(title: string) => title === 'Filtertest' ? 123 : null,
)
expect(transformed).toBe('( labels = 456 || project = 123 )')
})
})
describe('Special Characters', () => {

View File

@@ -81,7 +81,7 @@ export function hasFilterQuery(filter: string): boolean {
}
export function getFilterFieldRegexPattern(field: string): RegExp {
return new RegExp('\\b(' + field + ')\\s*' + FILTER_OPERATORS_REGEX + '\\s*(?:(["\'])((?:\\\\.|(?!\\3)[^\\\\])*?)\\3|([^&|()<]+?))(?=\\s*(?:&&|\\||$))', 'g')
return new RegExp('\\b(' + field + ')\\s*' + FILTER_OPERATORS_REGEX + '\\s*(?:(["\'])((?:\\\\.|(?!\\3)[^\\\\])*?)\\3|([^&|()<]+?))(?=\\s*(?:&&|\\||\\)|$))', 'g')
}
export function transformFilterStringForApi(

View File

@@ -284,4 +284,31 @@ func TestParseFilter(t *testing.T) {
assert.Equal(t, 0, date.Year())
}
})
t.Run("project with parentheses", func(t *testing.T) {
result, err := getTaskFiltersFromFilterString("( project = 1 )", "UTC")
require.NoError(t, err)
require.Len(t, result, 1)
require.Len(t, result[0].value, 1)
firstSet := result[0].value.([]*taskFilter)
assert.Equal(t, "project_id", firstSet[0].field)
assert.Equal(t, taskFilterComparatorEquals, firstSet[0].comparator)
assert.Equal(t, int64(1), firstSet[0].value)
})
t.Run("project with OR in parentheses", func(t *testing.T) {
result, err := getTaskFiltersFromFilterString("( done = false || project = 1 )", "UTC")
require.NoError(t, err)
require.Len(t, result, 1)
require.Len(t, result[0].value, 2)
firstSet := result[0].value.([]*taskFilter)
assert.Equal(t, "done", firstSet[0].field)
assert.Equal(t, taskFilterComparatorEquals, firstSet[0].comparator)
assert.Equal(t, false, firstSet[0].value)
assert.Equal(t, "project_id", firstSet[1].field)
assert.Equal(t, taskFilterComparatorEquals, firstSet[1].comparator)
assert.Equal(t, int64(1), firstSet[1].value)
})
}