Compare commits

...
Author SHA1 Message Date
Sonarly Claude Code e31ece6e36 fix: allow multiple non-advanced filters on the same field
https://sonarly.com/issue/33889?type=bug

When a user tries to add a second filter condition on a field that already has a filter, the existing filter is loaded for editing instead of a new one being created. This forces users into the Advanced Filters UI for a basic AND-combination use case.

Fix: Removed the `findDuplicateRecordFilterInNonAdvancedRecordFilters` check from `useInitializeFilterOnFieldMetadataItemFromViewBarFilterDropdown`. This check was the root cause of the bug: when a user selected a field that already had a filter from the "+ Filter" dropdown, the hook would load the existing filter for editing instead of creating a new one, causing the existing filter condition to be overwritten.

**What changed:**

1. **`useInitializeFilterOnFieldMetadataItemFromViewBarFilterDropdown.ts`**: Removed the `findDuplicateRecordFilterInNonAdvancedRecordFilters` call and the `if (filterIsAlreadyInCurrentRecordFilters)` branch. Now the hook always initializes a fresh filter with the default operand, regardless of whether a filter for the same field already exists. This allows users to add multiple filter conditions on the same field (e.g., "Name contains agency" AND "Name does not contain freelance"). Removed unused imports: `currentRecordFiltersComponentState`, `findDuplicateRecordFilterInNonAdvancedRecordFilters`, `isDefined`.

2. **Test file**: Updated the "duplicate field on city" test to verify the new behavior — selecting a field that already has a filter should NOT reuse the existing filter, but instead initialize a new one with the default operand.

The `findDuplicateRecordFilterInNonAdvancedRecordFilters` utility is now dead code (no callers), but was left in place as it may still be useful for other features or could be referenced in Advanced Filters code.
2026-05-04 09:29:15 +00:00
2 changed files with 26 additions and 56 deletions
@@ -184,7 +184,7 @@ describe('useInitializeFilterOnFieldMetadataItemFromViewBarFilterDropdown', () =
});
});
it('should initialize filter with a duplicate field on city', () => {
it('should allow adding a second filter on a field that already has one', () => {
const { result } = renderHook(
() => {
const { initializeFilterOnFieldMetataItemFromViewBarFilterDropdown } =
@@ -262,9 +262,11 @@ describe('useInitializeFilterOnFieldMetadataItemFromViewBarFilterDropdown', () =
);
});
expect(result.current.objectFilterDropdownCurrentRecordFilter).toBe(
mockExistingFilterOnCity,
);
// Should NOT reuse the existing filter — a new filter should be initialized
expect(
result.current.objectFilterDropdownCurrentRecordFilter,
).toBeUndefined();
expect(result.current.selectedOperandInDropdown).toBe(defaultOperand);
});
it('should initialize filter on a date field correctly', () => {
@@ -5,9 +5,7 @@ import { fieldMetadataItemIdUsedInDropdownComponentState } from '@/object-record
import { objectFilterDropdownCurrentRecordFilterComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownCurrentRecordFilterComponentState';
import { objectFilterDropdownFilterIsSelectedComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownFilterIsSelectedComponentState';
import { selectedOperandInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/selectedOperandInDropdownComponentState';
import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState';
import { type RecordFilter } from '@/object-record/record-filter/types/RecordFilter';
import { findDuplicateRecordFilterInNonAdvancedRecordFilters } from '@/object-record/record-filter/utils/findDuplicateRecordFilterInNonAdvancedRecordFilters';
import { getRecordFilterOperands } from '@/object-record/record-filter/utils/getRecordFilterOperands';
import { usePushFocusItemToFocusStack } from '@/ui/utilities/focus/hooks/usePushFocusItemToFocusStack';
@@ -17,7 +15,7 @@ import { ViewBarFilterDropdownIds } from '@/views/constants/ViewBarFilterDropdow
import { useStore } from 'jotai';
import { useCallback } from 'react';
import { getFilterTypeFromFieldType, isDefined } from 'twenty-shared/utils';
import { getFilterTypeFromFieldType } from 'twenty-shared/utils';
import { v4 } from 'uuid';
export const useInitializeFilterOnFieldMetadataItemFromViewBarFilterDropdown =
@@ -27,9 +25,6 @@ export const useInitializeFilterOnFieldMetadataItemFromViewBarFilterDropdown =
selectedOperandInDropdownComponentState,
);
const currentRecordFiltersCallbackState =
useAtomComponentStateCallbackState(currentRecordFiltersComponentState);
const objectFilterDropdownCurrentRecordFilterCallbackState =
useAtomComponentStateCallbackState(
objectFilterDropdownCurrentRecordFilterComponentState,
@@ -61,10 +56,6 @@ export const useInitializeFilterOnFieldMetadataItemFromViewBarFilterDropdown =
fieldMetadataItem.id,
);
const currentRecordFilters = store.get(
currentRecordFiltersCallbackState,
);
const filterType = getFilterTypeFromFieldType(fieldMetadataItem.type);
if (filterType === 'RELATION' || filterType === 'SELECT') {
@@ -86,58 +77,35 @@ export const useInitializeFilterOnFieldMetadataItemFromViewBarFilterDropdown =
filterType,
})[0];
const duplicateFilterInCurrentRecordFilters =
findDuplicateRecordFilterInNonAdvancedRecordFilters({
recordFilters: currentRecordFilters,
fieldMetadataItemId: fieldMetadataItem.id,
});
store.set(selectedOperandInDropdownCallbackState, defaultOperand);
const filterIsAlreadyInCurrentRecordFilters = isDefined(
duplicateFilterInCurrentRecordFilters,
);
if (filterType === 'DATE' || filterType === 'DATE_TIME') {
const { displayValue, value } = getInitialFilterValue(
filterType,
defaultOperand,
);
const initialDateRecordFilter: RecordFilter = {
id: v4(),
fieldMetadataId: fieldMetadataItem.id,
operand: defaultOperand,
displayValue,
label: fieldMetadataItem.label,
type: filterType,
value,
};
upsertObjectFilterDropdownCurrentFilter(initialDateRecordFilter);
if (filterIsAlreadyInCurrentRecordFilters) {
store.set(
objectFilterDropdownCurrentRecordFilterCallbackState,
duplicateFilterInCurrentRecordFilters,
initialDateRecordFilter,
);
store.set(
selectedOperandInDropdownCallbackState,
duplicateFilterInCurrentRecordFilters.operand,
);
} else {
store.set(selectedOperandInDropdownCallbackState, defaultOperand);
if (filterType === 'DATE' || filterType === 'DATE_TIME') {
const { displayValue, value } = getInitialFilterValue(
filterType,
defaultOperand,
);
const initialDateRecordFilter: RecordFilter = {
id: v4(),
fieldMetadataId: fieldMetadataItem.id,
operand: defaultOperand,
displayValue,
label: fieldMetadataItem.label,
type: filterType,
value,
};
upsertObjectFilterDropdownCurrentFilter(initialDateRecordFilter);
store.set(
objectFilterDropdownCurrentRecordFilterCallbackState,
initialDateRecordFilter,
);
}
}
},
[
store,
fieldMetadataItemUsedInDropdownCallbackState,
currentRecordFiltersCallbackState,
objectFilterDropdownFilterIsSelectedCallbackState,
pushFocusItemToFocusStack,
objectFilterDropdownCurrentRecordFilterCallbackState,