fix(shared): multi-select "contains any" filter matcher should use OR semantics (#23010)

The in-memory `isMatchingMultiSelectFilter` evaluated the `containsAny`
operand with `Array.every`, which requires a record to hold **all**
selected options. But `containsAny` means "any overlap": the server
evaluates it as a Postgres array-overlap (`field::text[] &&
ARRAY[...]`), and the "Contains" UI operand for a MULTI_SELECT field
builds exactly this operand — both match on **at least one** shared
option.

So the matcher disagreed with the server. In a "Tags contains any of [A,
B]" view, an optimistic create/update of a record whose tags are just
`[A]` was treated as not matching, so it failed to appear (or was
wrongly dropped) until a refetch; `DOES_NOT_CONTAIN` (built as `not {
containsAny }`) inverted the same way. The same helper backs the
row-level-permission predicate matcher.

Switched to `Array.some` to match the OR semantics, and updated the
tests (partial-overlap, single-overlap, no-overlap, empty-array). The
sibling `isMatching*Filter` helpers were checked — this is the only
affected one.


<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/23010?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->
This commit is contained in:
Madan kumar
2026-07-21 19:49:06 +05:30
committed by GitHub
parent 024b379e1a
commit 8b0e7a93a4
2 changed files with 30 additions and 3 deletions
@@ -2,7 +2,7 @@ import { isMatchingMultiSelectFilter } from '@/utils/filter/utils/isMatchingMult
describe('isMatchingMultiSelectFilter', () => {
describe('containsAny', () => {
it('should return true when value contains all filter items', () => {
it('should return true when value contains every filter item', () => {
expect(
isMatchingMultiSelectFilter({
multiSelectFilter: { containsAny: ['A', 'B'] },
@@ -11,12 +11,39 @@ describe('isMatchingMultiSelectFilter', () => {
).toBe(true);
});
it('should return false when value does not contain all filter items', () => {
it('should return true when value contains only some of the filter items', () => {
expect(
isMatchingMultiSelectFilter({
multiSelectFilter: { containsAny: ['A', 'D'] },
value: ['A', 'B', 'C'],
}),
).toBe(true);
});
it('should return true when value shares a single option with the filter', () => {
expect(
isMatchingMultiSelectFilter({
multiSelectFilter: { containsAny: ['B'] },
value: ['A', 'B', 'C'],
}),
).toBe(true);
});
it('should return false when value shares no option with the filter', () => {
expect(
isMatchingMultiSelectFilter({
multiSelectFilter: { containsAny: ['X', 'Y'] },
value: ['A', 'B', 'C'],
}),
).toBe(false);
});
it('should return false for an empty value array', () => {
expect(
isMatchingMultiSelectFilter({
multiSelectFilter: { containsAny: ['A'] },
value: [],
}),
).toBe(false);
});
@@ -11,7 +11,7 @@ export const isMatchingMultiSelectFilter = ({
case multiSelectFilter.containsAny !== undefined: {
return (
Array.isArray(value) &&
multiSelectFilter.containsAny.every((item) => value.includes(item))
multiSelectFilter.containsAny.some((item) => value.includes(item))
);
}
case multiSelectFilter.isEmptyArray !== undefined: {