diff --git a/app/javascript/dashboard/store/modules/conversations/helpers/filterHelpers.js b/app/javascript/dashboard/store/modules/conversations/helpers/filterHelpers.js index c7060bee3..63a592375 100644 --- a/app/javascript/dashboard/store/modules/conversations/helpers/filterHelpers.js +++ b/app/javascript/dashboard/store/modules/conversations/helpers/filterHelpers.js @@ -46,8 +46,8 @@ * 2. Nested properties in additional_attributes (browser_language, referer, etc.) * 3. Nested properties in custom_attributes (conversation_type, etc.) */ -import jsonLogic from 'json-logic-js'; import { coerceToDate } from '@chatwoot/utils'; +import jsonLogic from 'json-logic-js'; /** * Gets a value from a conversation based on the attribute key @@ -121,7 +121,8 @@ const resolveValue = candidate => { * @returns {Boolean} - Returns true if the values are considered equal according to filtering rules * * This function handles various equality scenarios: - * 1. When both values are arrays: checks if all items in filterValue exist in conversationValue + * 1. When both values are arrays (e.g. labels): matches if any filter value exists in the conversation array + * (mirrors the backend SQL `tag_id IN (...)` OR semantics) * 2. When filterValue is an array but conversationValue is not: checks if conversationValue is included in filterValue * 3. Otherwise: performs strict equality comparison */ @@ -131,8 +132,9 @@ const equalTo = (filterValue, conversationValue) => { if (filterValue === 'all') return true; if (Array.isArray(conversationValue)) { - // For array values like labels, check if any of the filter values exist in the array - return filterValue.every(val => conversationValue.includes(val)); + // For array values like labels, match if any filter value is present. + // Mirrors the backend SQL `tag_id IN (...)` (OR semantics). + return filterValue.some(val => conversationValue.includes(val)); } if (!Array.isArray(conversationValue)) { diff --git a/app/javascript/dashboard/store/modules/conversations/helpers/specs/filterHelpers.spec.js b/app/javascript/dashboard/store/modules/conversations/helpers/specs/filterHelpers.spec.js index db1017407..adcf5c96f 100644 --- a/app/javascript/dashboard/store/modules/conversations/helpers/specs/filterHelpers.spec.js +++ b/app/javascript/dashboard/store/modules/conversations/helpers/specs/filterHelpers.spec.js @@ -416,6 +416,40 @@ describe('filterHelpers', () => { expect(matchesFilters(conversation, filters)).toBe(true); }); + // Multi-label equal_to uses OR semantics to mirror the backend SQL `tag_id IN (...)`: + // a conversation matches if ANY of the filter labels is on it. + it('should match conversation with equal_to operator when any of multiple filter labels is present', () => { + const conversation = { labels: ['support'] }; + const filters = [ + { + attribute_key: 'labels', + filter_operator: 'equal_to', + values: [ + { id: 'support', name: 'Support' }, + { id: 'urgent', name: 'Urgent' }, + ], + query_operator: 'and', + }, + ]; + expect(matchesFilters(conversation, filters)).toBe(true); + }); + + it('should not match conversation with equal_to operator when none of multiple filter labels is present', () => { + const conversation = { labels: ['new'] }; + const filters = [ + { + attribute_key: 'labels', + filter_operator: 'equal_to', + values: [ + { id: 'support', name: 'Support' }, + { id: 'urgent', name: 'Urgent' }, + ], + query_operator: 'and', + }, + ]; + expect(matchesFilters(conversation, filters)).toBe(false); + }); + it('should match conversation with is_present operator for labels', () => { const conversation = { labels: ['support', 'urgent', 'new'] }; const filters = [