diff --git a/packages/ui-patterns/src/FilterBar/FilterBar.test.tsx b/packages/ui-patterns/src/FilterBar/FilterBar.test.tsx
index eb6dcf8fea2..94732014676 100644
--- a/packages/ui-patterns/src/FilterBar/FilterBar.test.tsx
+++ b/packages/ui-patterns/src/FilterBar/FilterBar.test.tsx
@@ -521,4 +521,69 @@ describe('FilterBar', () => {
expect(screen.getByDisplayValue('active')).toBeInTheDocument()
expect(screen.queryByText('AND')).not.toBeInTheDocument()
})
+
+ it('selects a column named "group" without creating a nested group', async () => {
+ const user = userEvent.setup()
+ const propertiesWithGroup: FilterProperty[] = [
+ {
+ label: 'group',
+ name: 'group',
+ type: 'string',
+ operators: ['=', '!=', 'CONTAINS'],
+ },
+ {
+ label: 'Name',
+ name: 'name',
+ type: 'string',
+ operators: ['=', '!='],
+ },
+ ]
+
+ let currentFilters: FilterGroup = initialFilters
+ const handleFilterChange = vi.fn((filters) => {
+ currentFilters = filters
+ })
+
+ const { rerender } = render(
+
+ )
+
+ const freeform = screen.getByPlaceholderText('Filter by group, Name')
+ await user.click(freeform)
+
+ expect(await screen.findByText('group')).toBeInTheDocument()
+
+ await user.click(screen.getByText('group'))
+
+ await waitFor(() => {
+ expect(handleFilterChange).toHaveBeenCalled()
+ })
+
+ rerender(
+
+ )
+
+ // Should create a filter condition for the "group" column, NOT a nested filter group
+ expect(currentFilters.conditions).toHaveLength(1)
+ const condition = currentFilters.conditions[0]
+ expect('logicalOperator' in condition).toBe(false)
+ expect((condition as { propertyName: string }).propertyName).toBe('group')
+
+ // Should show the operator input for the "group" column
+ await waitFor(() => {
+ expect(screen.getByLabelText('Operator for group')).toBeInTheDocument()
+ })
+ })
})
diff --git a/packages/ui-patterns/src/FilterBar/menuItems.test.ts b/packages/ui-patterns/src/FilterBar/menuItems.test.ts
index 73e73972b64..60be56a664c 100644
--- a/packages/ui-patterns/src/FilterBar/menuItems.test.ts
+++ b/packages/ui-patterns/src/FilterBar/menuItems.test.ts
@@ -1,6 +1,6 @@
import { describe, expect, it } from 'vitest'
-import { buildOperatorItems, buildValueItems } from './menuItems'
+import { buildOperatorItems, buildPropertyItems, buildValueItems } from './menuItems'
import { FilterGroup, FilterProperty } from './types'
const stringProperty: FilterProperty = {
@@ -146,3 +146,63 @@ describe('buildValueItems', () => {
])
})
})
+
+describe('buildPropertyItems', () => {
+ const properties: FilterProperty[] = [
+ { label: 'Name', name: 'name', type: 'string', operators: ['='] },
+ { label: 'Status', name: 'status', type: 'string', operators: ['='] },
+ ]
+
+ it('returns property items matching the input', () => {
+ const items = buildPropertyItems({
+ filterProperties: properties,
+ inputValue: 'nam',
+ })
+
+ expect(items).toEqual([{ value: 'name', label: 'Name' }])
+ })
+
+ it('uses a sentinel value for the "New Group" action when supportsOperators is true', () => {
+ const items = buildPropertyItems({
+ filterProperties: properties,
+ inputValue: '',
+ supportsOperators: true,
+ })
+
+ const groupItem = items.find((item) => item.label === 'New Group')
+ expect(groupItem).toBeDefined()
+ expect(groupItem?.value).toBe('__new_group__')
+ })
+
+ it('does not include "New Group" when supportsOperators is false', () => {
+ const items = buildPropertyItems({
+ filterProperties: properties,
+ inputValue: '',
+ supportsOperators: false,
+ })
+
+ expect(items.find((item) => item.label === 'New Group')).toBeUndefined()
+ })
+
+ it('does not collide with a property named "group"', () => {
+ const propertiesWithGroup: FilterProperty[] = [
+ { label: 'group', name: 'group', type: 'string', operators: ['='] },
+ { label: 'Name', name: 'name', type: 'string', operators: ['='] },
+ ]
+
+ const items = buildPropertyItems({
+ filterProperties: propertiesWithGroup,
+ inputValue: '',
+ supportsOperators: true,
+ })
+
+ const groupPropertyItem = items.find((item) => item.label === 'group')
+ const newGroupItem = items.find((item) => item.label === 'New Group')
+
+ expect(groupPropertyItem).toBeDefined()
+ expect(groupPropertyItem?.value).toBe('group')
+ expect(newGroupItem).toBeDefined()
+ expect(newGroupItem?.value).toBe('__new_group__')
+ expect(groupPropertyItem?.value).not.toBe(newGroupItem?.value)
+ })
+})
diff --git a/packages/ui-patterns/src/FilterBar/menuItems.ts b/packages/ui-patterns/src/FilterBar/menuItems.ts
index bb58c84f5fe..31edfd651b4 100644
--- a/packages/ui-patterns/src/FilterBar/menuItems.ts
+++ b/packages/ui-patterns/src/FilterBar/menuItems.ts
@@ -94,7 +94,7 @@ export function buildPropertyItems(params: {
)
if (supportsOperators) {
- items.push({ value: 'group', label: 'New Group' })
+ items.push({ value: '__new_group__', label: 'New Group' })
}
if (actions && trimmedInput.length > 0) {
diff --git a/packages/ui-patterns/src/FilterBar/useCommandHandling.ts b/packages/ui-patterns/src/FilterBar/useCommandHandling.ts
index 61119c48a9a..2adbce682ac 100644
--- a/packages/ui-patterns/src/FilterBar/useCommandHandling.ts
+++ b/packages/ui-patterns/src/FilterBar/useCommandHandling.ts
@@ -175,7 +175,7 @@ export function useCommandHandling({
return
}
- if (item.value === 'group') {
+ if (item.value === '__new_group__') {
handleGroupCommand()
return
}
diff --git a/packages/ui-patterns/src/FilterBar/useCommandMenu.ts b/packages/ui-patterns/src/FilterBar/useCommandMenu.ts
index 7330c86905b..81b285f7040 100644
--- a/packages/ui-patterns/src/FilterBar/useCommandMenu.ts
+++ b/packages/ui-patterns/src/FilterBar/useCommandMenu.ts
@@ -52,7 +52,7 @@ export function useCommandMenu({
if (supportsOperators) {
items.push({
- value: 'group',
+ value: '__new_group__',
label: 'New Group',
})
}