mirror of
https://github.com/lingble/chatwoot.git
synced 2025-11-01 19:48:08 +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 => {
|
const setArrayValues = item => {
|
||||||
return item.values[0]?.id ? item.values.map(val => val.id) : item.values;
|
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 => {
|
const generatePayload = data => {
|
||||||
// Make a copy of data to avoid vue data reactivity issues
|
// Make a copy of data to avoid vue data reactivity issues
|
||||||
const filters = JSON.parse(JSON.stringify(data));
|
const filters = JSON.parse(JSON.stringify(data));
|
||||||
let payload = filters.map(item => {
|
let payload = filters.map(item => {
|
||||||
if (Array.isArray(item.values)) {
|
// If item key is content, we will split it using comma and return as array
|
||||||
item.values = setArrayValues(item);
|
// FIX ME: Make this generic option instead of using the key directly here
|
||||||
} else if (typeof item.values === 'object') {
|
item.values = generateValues(item);
|
||||||
item.values = [item.values.id];
|
|
||||||
} else if (!item.values) {
|
|
||||||
item.values = [];
|
|
||||||
} else {
|
|
||||||
item.values = [item.values];
|
|
||||||
}
|
|
||||||
return item;
|
return item;
|
||||||
});
|
});
|
||||||
|
|
||||||
// For every query added, the query_operator is set default to and so the
|
// 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.
|
// last query will have an extra query_operator, this would break the api.
|
||||||
// Setting this to null for all query payload
|
// Setting this to null for all query payload
|
||||||
|
|||||||
@@ -199,6 +199,12 @@ export default {
|
|||||||
values: condition.values[0],
|
values: condition.values[0],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
if (inputType === 'comma_separated_plain_text') {
|
||||||
|
return {
|
||||||
|
...condition,
|
||||||
|
values: condition.values.join(','),
|
||||||
|
};
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
...condition,
|
...condition,
|
||||||
query_operator: condition.query_operator || 'and',
|
query_operator: condition.query_operator || 'and',
|
||||||
|
|||||||
@@ -52,7 +52,7 @@
|
|||||||
{{ $t('AUTOMATION.ADD.FORM.CONDITIONS.LABEL') }}
|
{{ $t('AUTOMATION.ADD.FORM.CONDITIONS.LABEL') }}
|
||||||
</label>
|
</label>
|
||||||
<div
|
<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
|
<filter-input-box
|
||||||
v-for="(condition, i) in automation.conditions"
|
v-for="(condition, i) in automation.conditions"
|
||||||
@@ -94,7 +94,7 @@
|
|||||||
{{ $t('AUTOMATION.ADD.FORM.ACTIONS.LABEL') }}
|
{{ $t('AUTOMATION.ADD.FORM.ACTIONS.LABEL') }}
|
||||||
</label>
|
</label>
|
||||||
<div
|
<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
|
<automation-action-input
|
||||||
v-for="(action, i) in automation.actions"
|
v-for="(action, i) in automation.actions"
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ export const AUTOMATIONS = {
|
|||||||
key: 'content',
|
key: 'content',
|
||||||
name: 'Message Content',
|
name: 'Message Content',
|
||||||
attributeI18nKey: 'MESSAGE_CONTAINS',
|
attributeI18nKey: 'MESSAGE_CONTAINS',
|
||||||
inputType: 'plain_text',
|
inputType: 'comma_separated_plain_text',
|
||||||
filterOperators: OPERATOR_TYPES_2,
|
filterOperators: OPERATOR_TYPES_2,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -23,8 +23,8 @@ class FilterService
|
|||||||
@filter_values["value_#{current_index}"] = filter_values(query_hash)
|
@filter_values["value_#{current_index}"] = filter_values(query_hash)
|
||||||
equals_to_filter_string(query_hash[:filter_operator], current_index)
|
equals_to_filter_string(query_hash[:filter_operator], current_index)
|
||||||
when 'contains', 'does_not_contain'
|
when 'contains', 'does_not_contain'
|
||||||
@filter_values["value_#{current_index}"] = "%#{string_filter_values(query_hash)}%"
|
@filter_values["value_#{current_index}"] = values_for_ilike(query_hash)
|
||||||
like_filter_string(query_hash[:filter_operator], current_index)
|
ilike_filter_string(query_hash[:filter_operator], current_index)
|
||||||
when 'is_present'
|
when 'is_present'
|
||||||
@filter_values["value_#{current_index}"] = 'IS NOT NULL'
|
@filter_values["value_#{current_index}"] = 'IS NOT NULL'
|
||||||
when 'is_not_present'
|
when 'is_not_present'
|
||||||
@@ -47,8 +47,6 @@ class FilterService
|
|||||||
query_hash['values'].map { |x| Conversation.statuses[x.to_sym] }
|
query_hash['values'].map { |x| Conversation.statuses[x.to_sym] }
|
||||||
when 'message_type'
|
when 'message_type'
|
||||||
query_hash['values'].map { |x| Message.message_types[x.to_sym] }
|
query_hash['values'].map { |x| Message.message_types[x.to_sym] }
|
||||||
when 'content'
|
|
||||||
string_filter_values(query_hash)
|
|
||||||
else
|
else
|
||||||
case_insensitive_values(query_hash)
|
case_insensitive_values(query_hash)
|
||||||
end
|
end
|
||||||
@@ -62,6 +60,15 @@ class FilterService
|
|||||||
end
|
end
|
||||||
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)
|
def string_filter_values(query_hash)
|
||||||
return query_hash['values'][0].downcase if query_hash['values'].is_a?(Array)
|
return query_hash['values'][0].downcase if query_hash['values'].is_a?(Array)
|
||||||
|
|
||||||
@@ -149,6 +156,12 @@ class FilterService
|
|||||||
"NOT IN (:value_#{current_index})"
|
"NOT IN (:value_#{current_index})"
|
||||||
end
|
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)
|
def like_filter_string(filter_operator, current_index)
|
||||||
return "LIKE :value_#{current_index}" if %w[contains starts_with].include?(filter_operator)
|
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!(:campaign_2) { create(:campaign, title: 'Campaign', account: account) }
|
||||||
let!(:inbox) { create(:inbox, account: account, enable_auto_assignment: false) }
|
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!(:user_2_assigned_conversation) { create(:conversation, account: account, inbox: inbox, assignee: user_2) }
|
||||||
let!(:en_conversation_1) do
|
let!(:en_conversation_1) do
|
||||||
create(:conversation, account: account, inbox: inbox, assignee: user_1, campaign_id: campaign_1.id,
|
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
|
params[:payload] = payload
|
||||||
result = filter_service.new(params, user_1).perform
|
result = filter_service.new(params, user_1).perform
|
||||||
conversations = Conversation.where("additional_attributes ->> 'browser_language' IN (?) AND status IN (?)", ['en'], [1, 2])
|
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
|
end
|
||||||
|
|
||||||
it 'filter conversations by additional_attributes and status with pagination' do
|
it 'filter conversations by additional_attributes and status with pagination' do
|
||||||
@@ -83,7 +82,45 @@ describe Conversations::FilterService do
|
|||||||
params[:page] = 2
|
params[:page] = 2
|
||||||
result = filter_service.new(params, user_1).perform
|
result = filter_service.new(params, user_1).perform
|
||||||
conversations = Conversation.where("additional_attributes ->> 'browser_language' IN (?) AND status IN (?)", ['en'], [1, 2])
|
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
|
end
|
||||||
|
|
||||||
it 'filter conversations by additional_attributes with NOT_IN filter' do
|
it 'filter conversations by additional_attributes with NOT_IN filter' do
|
||||||
@@ -98,7 +135,7 @@ describe Conversations::FilterService do
|
|||||||
end
|
end
|
||||||
|
|
||||||
it 'filter conversations by tags' do
|
it 'filter conversations by tags' do
|
||||||
unassigned_conversation.update_labels('support')
|
user_2_assigned_conversation.update_labels('support')
|
||||||
params[:payload] = [
|
params[:payload] = [
|
||||||
{
|
{
|
||||||
attribute_key: 'assignee_id',
|
attribute_key: 'assignee_id',
|
||||||
@@ -119,7 +156,7 @@ describe Conversations::FilterService do
|
|||||||
}.with_indifferent_access
|
}.with_indifferent_access
|
||||||
]
|
]
|
||||||
result = filter_service.new(params, user_1).perform
|
result = filter_service.new(params, user_1).perform
|
||||||
expect(result.length).to be 2
|
expect(result[:count][:all_count]).to be 1
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'filter conversations by is_present filter_operator' do
|
it 'filter conversations by is_present filter_operator' do
|
||||||
|
|||||||
Reference in New Issue
Block a user