mirror of
				https://github.com/lingble/chatwoot.git
				synced 2025-10-31 19:17:48 +00:00 
			
		
		
		
	chore: Support multiple values for automation message content (#7871)
Co-authored-by: Pranav Raj S <pranav@chatwoot.com> Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
This commit is contained in:
		| @@ -1,21 +1,34 @@ | ||||
| const setArrayValues = item => { | ||||
|   return item.values[0]?.id ? item.values.map(val => val.id) : item.values; | ||||
| }; | ||||
|  | ||||
| const generateValues = item => { | ||||
|   if (item.attribute_key === 'content') { | ||||
|     const values = item.values || ''; | ||||
|     return values.split(','); | ||||
|   } | ||||
|   if (Array.isArray(item.values)) { | ||||
|     return setArrayValues(item); | ||||
|   } | ||||
|   if (typeof item.values === 'object') { | ||||
|     return [item.values.id]; | ||||
|   } | ||||
|   if (!item.values) { | ||||
|     return []; | ||||
|   } | ||||
|   return [item.values]; | ||||
| }; | ||||
|  | ||||
| const generatePayload = data => { | ||||
|   // Make a copy of data to avoid vue data reactivity issues | ||||
|   const filters = JSON.parse(JSON.stringify(data)); | ||||
|   let payload = filters.map(item => { | ||||
|     if (Array.isArray(item.values)) { | ||||
|       item.values = setArrayValues(item); | ||||
|     } else if (typeof item.values === 'object') { | ||||
|       item.values = [item.values.id]; | ||||
|     } else if (!item.values) { | ||||
|       item.values = []; | ||||
|     } else { | ||||
|       item.values = [item.values]; | ||||
|     } | ||||
|     // If item key is content, we will split it using comma and return as array | ||||
|     // FIX ME: Make this generic option instead of using the key directly here | ||||
|     item.values = generateValues(item); | ||||
|     return item; | ||||
|   }); | ||||
|  | ||||
|   // For every query added, the query_operator is set default to and so the | ||||
|   // last query will have an extra query_operator, this would break the api. | ||||
|   // Setting this to null for all query payload | ||||
|   | ||||
| @@ -199,6 +199,12 @@ export default { | ||||
|             values: condition.values[0], | ||||
|           }; | ||||
|         } | ||||
|         if (inputType === 'comma_separated_plain_text') { | ||||
|           return { | ||||
|             ...condition, | ||||
|             values: condition.values.join(','), | ||||
|           }; | ||||
|         } | ||||
|         return { | ||||
|           ...condition, | ||||
|           query_operator: condition.query_operator || 'and', | ||||
|   | ||||
| @@ -52,7 +52,7 @@ | ||||
|             {{ $t('AUTOMATION.ADD.FORM.CONDITIONS.LABEL') }} | ||||
|           </label> | ||||
|           <div | ||||
|             class="w-full w-full p-4 bg-slate-25 dark:bg-slate-700 rounded-lg border border-solid border-slate-50 dark:border-slate-700 mb-4" | ||||
|             class="w-full p-4 bg-slate-25 dark:bg-slate-700 rounded-lg border border-solid border-slate-50 dark:border-slate-700 mb-4" | ||||
|           > | ||||
|             <filter-input-box | ||||
|               v-for="(condition, i) in automation.conditions" | ||||
| @@ -94,7 +94,7 @@ | ||||
|             {{ $t('AUTOMATION.ADD.FORM.ACTIONS.LABEL') }} | ||||
|           </label> | ||||
|           <div | ||||
|             class="w-full w-full p-4 bg-slate-25 dark:bg-slate-700 rounded-lg border border-solid border-slate-50 dark:border-slate-700 mb-4" | ||||
|             class="w-full p-4 bg-slate-25 dark:bg-slate-700 rounded-lg border border-solid border-slate-50 dark:border-slate-700 mb-4" | ||||
|           > | ||||
|             <automation-action-input | ||||
|               v-for="(action, i) in automation.actions" | ||||
|   | ||||
| @@ -19,7 +19,7 @@ export const AUTOMATIONS = { | ||||
|         key: 'content', | ||||
|         name: 'Message Content', | ||||
|         attributeI18nKey: 'MESSAGE_CONTAINS', | ||||
|         inputType: 'plain_text', | ||||
|         inputType: 'comma_separated_plain_text', | ||||
|         filterOperators: OPERATOR_TYPES_2, | ||||
|       }, | ||||
|       { | ||||
|   | ||||
| @@ -23,8 +23,8 @@ class FilterService | ||||
|       @filter_values["value_#{current_index}"] = filter_values(query_hash) | ||||
|       equals_to_filter_string(query_hash[:filter_operator], current_index) | ||||
|     when 'contains', 'does_not_contain' | ||||
|       @filter_values["value_#{current_index}"] = "%#{string_filter_values(query_hash)}%" | ||||
|       like_filter_string(query_hash[:filter_operator], current_index) | ||||
|       @filter_values["value_#{current_index}"] = values_for_ilike(query_hash) | ||||
|       ilike_filter_string(query_hash[:filter_operator], current_index) | ||||
|     when 'is_present' | ||||
|       @filter_values["value_#{current_index}"] = 'IS NOT NULL' | ||||
|     when 'is_not_present' | ||||
| @@ -47,8 +47,6 @@ class FilterService | ||||
|       query_hash['values'].map { |x| Conversation.statuses[x.to_sym] } | ||||
|     when 'message_type' | ||||
|       query_hash['values'].map { |x| Message.message_types[x.to_sym] } | ||||
|     when 'content' | ||||
|       string_filter_values(query_hash) | ||||
|     else | ||||
|       case_insensitive_values(query_hash) | ||||
|     end | ||||
| @@ -62,6 +60,15 @@ class FilterService | ||||
|     end | ||||
|   end | ||||
|  | ||||
|   def values_for_ilike(query_hash) | ||||
|     if query_hash['values'].is_a?(Array) | ||||
|       query_hash['values'] | ||||
|         .map { |item| "%#{item.strip}%" } | ||||
|     else | ||||
|       ["%#{query_hash['values'].strip}%"] | ||||
|     end | ||||
|   end | ||||
|  | ||||
|   def string_filter_values(query_hash) | ||||
|     return query_hash['values'][0].downcase if query_hash['values'].is_a?(Array) | ||||
|  | ||||
| @@ -149,6 +156,12 @@ class FilterService | ||||
|     "NOT IN (:value_#{current_index})" | ||||
|   end | ||||
|  | ||||
|   def ilike_filter_string(filter_operator, current_index) | ||||
|     return "ILIKE ANY (ARRAY[:value_#{current_index}])" if %w[contains].include?(filter_operator) | ||||
|  | ||||
|     "NOT ILIKE ALL (ARRAY[:value_#{current_index}])" | ||||
|   end | ||||
|  | ||||
|   def like_filter_string(filter_operator, current_index) | ||||
|     return "LIKE :value_#{current_index}" if %w[contains starts_with].include?(filter_operator) | ||||
|  | ||||
|   | ||||
| @@ -10,7 +10,6 @@ describe Conversations::FilterService do | ||||
|   let!(:campaign_2) { create(:campaign, title: 'Campaign', account: account) } | ||||
|   let!(:inbox) { create(:inbox, account: account, enable_auto_assignment: false) } | ||||
|  | ||||
|   let!(:unassigned_conversation) { create(:conversation, account: account, inbox: inbox) } | ||||
|   let!(:user_2_assigned_conversation) { create(:conversation, account: account, inbox: inbox, assignee: user_2) } | ||||
|   let!(:en_conversation_1) do | ||||
|     create(:conversation, account: account, inbox: inbox, assignee: user_1, campaign_id: campaign_1.id, | ||||
| @@ -75,7 +74,7 @@ describe Conversations::FilterService do | ||||
|         params[:payload] = payload | ||||
|         result = filter_service.new(params, user_1).perform | ||||
|         conversations = Conversation.where("additional_attributes ->> 'browser_language' IN (?) AND status IN (?)", ['en'], [1, 2]) | ||||
|         expect(result.length).to be conversations.count | ||||
|         expect(result[:count][:all_count]).to be conversations.count | ||||
|       end | ||||
|  | ||||
|       it 'filter conversations by additional_attributes and status with pagination' do | ||||
| @@ -83,7 +82,45 @@ describe Conversations::FilterService do | ||||
|         params[:page] = 2 | ||||
|         result = filter_service.new(params, user_1).perform | ||||
|         conversations = Conversation.where("additional_attributes ->> 'browser_language' IN (?) AND status IN (?)", ['en'], [1, 2]) | ||||
|         expect(result.length).to be conversations.count | ||||
|         expect(result[:count][:all_count]).to be conversations.count | ||||
|       end | ||||
|  | ||||
|       it 'filters items with contains filter_operator with values being an array' do | ||||
|         params[:payload] = [{ | ||||
|           attribute_key: 'browser_language', | ||||
|           filter_operator: 'contains', | ||||
|           values: %w[tr fr], | ||||
|           query_operator: '', | ||||
|           custom_attribute_type: '' | ||||
|         }.with_indifferent_access] | ||||
|  | ||||
|         create(:conversation, account: account, inbox: inbox, assignee: user_1, campaign_id: campaign_1.id, | ||||
|                               status: 'pending', additional_attributes: { 'browser_language': 'fr' }) | ||||
|         create(:conversation, account: account, inbox: inbox, assignee: user_1, campaign_id: campaign_1.id, | ||||
|                               status: 'pending', additional_attributes: { 'browser_language': 'tr' }) | ||||
|  | ||||
|         result = filter_service.new(params, user_1).perform | ||||
|         expect(result[:count][:all_count]).to be 2 | ||||
|       end | ||||
|  | ||||
|       it 'filters items with does not contain filter operator with values being an array' do | ||||
|         params[:payload] = [{ | ||||
|           attribute_key: 'browser_language', | ||||
|           filter_operator: 'does_not_contain', | ||||
|           values: %w[tr en], | ||||
|           query_operator: '', | ||||
|           custom_attribute_type: '' | ||||
|         }.with_indifferent_access] | ||||
|  | ||||
|         create(:conversation, account: account, inbox: inbox, assignee: user_1, campaign_id: campaign_1.id, | ||||
|                               status: 'pending', additional_attributes: { 'browser_language': 'fr' }) | ||||
|         create(:conversation, account: account, inbox: inbox, assignee: user_1, campaign_id: campaign_1.id, | ||||
|                               status: 'pending', additional_attributes: { 'browser_language': 'tr' }) | ||||
|  | ||||
|         result = filter_service.new(params, user_1).perform | ||||
|  | ||||
|         expect(result[:count][:all_count]).to be 1 | ||||
|         expect(result[:conversations].first.additional_attributes['browser_language']).to eq 'fr' | ||||
|       end | ||||
|  | ||||
|       it 'filter conversations by additional_attributes with NOT_IN filter' do | ||||
| @@ -98,7 +135,7 @@ describe Conversations::FilterService do | ||||
|       end | ||||
|  | ||||
|       it 'filter conversations by tags' do | ||||
|         unassigned_conversation.update_labels('support') | ||||
|         user_2_assigned_conversation.update_labels('support') | ||||
|         params[:payload] = [ | ||||
|           { | ||||
|             attribute_key: 'assignee_id', | ||||
| @@ -119,7 +156,7 @@ describe Conversations::FilterService do | ||||
|           }.with_indifferent_access | ||||
|         ] | ||||
|         result = filter_service.new(params, user_1).perform | ||||
|         expect(result.length).to be 2 | ||||
|         expect(result[:count][:all_count]).to be 1 | ||||
|       end | ||||
|  | ||||
|       it 'filter conversations by is_present filter_operator' do | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Sojan Jose
					Sojan Jose