Feat: automation rule based on contact conditions (#4230)
This commit is contained in:
parent
c674393c02
commit
3158966241
7 changed files with 158 additions and 41 deletions
|
@ -8,6 +8,7 @@ class Api::V1::Accounts::AutomationRulesController < Api::V1::Accounts::BaseCont
|
|||
|
||||
def create
|
||||
@automation_rule = Current.account.automation_rules.create(automation_rules_permit)
|
||||
@automation_rule.update(actions: params[:actions])
|
||||
end
|
||||
|
||||
def show; end
|
||||
|
|
|
@ -1,20 +1,33 @@
|
|||
require 'json'
|
||||
|
||||
class AutomationRules::ConditionsFilterService < FilterService
|
||||
ATTRIBUTE_MODEL = 'contact_attribute'.freeze
|
||||
|
||||
def initialize(rule, conversation = nil)
|
||||
super([], nil)
|
||||
@rule = rule
|
||||
@conversation = conversation
|
||||
@account = conversation.account
|
||||
file = File.read('./lib/filters/filter_keys.json')
|
||||
@filters = JSON.parse(file)
|
||||
end
|
||||
|
||||
def perform
|
||||
conversation_filters = @filters['conversations']
|
||||
contact_filters = @filters['contacts']
|
||||
|
||||
@rule.conditions.each_with_index do |query_hash, current_index|
|
||||
current_filter = conversation_filters[query_hash['attribute_key']]
|
||||
@query_string += conversation_query_string(current_filter, query_hash.with_indifferent_access, current_index)
|
||||
conversation_filter = conversation_filters[query_hash['attribute_key']]
|
||||
contact_filter = contact_filters[query_hash['attribute_key']]
|
||||
|
||||
if conversation_filter
|
||||
@query_string += conversation_query_string('conversations', conversation_filter, query_hash.with_indifferent_access, current_index)
|
||||
elsif contact_filter
|
||||
@query_string += conversation_query_string('contacts', contact_filter, query_hash.with_indifferent_access, current_index)
|
||||
elsif custom_attribute(query_hash['attribute_key'], @account)
|
||||
# send table name according to attribute key right now we are supporting contact based custom attribute filter
|
||||
@query_string += custom_attribute_query(query_hash.with_indifferent_access, 'contacts', current_index)
|
||||
end
|
||||
end
|
||||
|
||||
records = base_relation.where(@query_string, @filter_values.with_indifferent_access)
|
||||
|
@ -57,25 +70,26 @@ class AutomationRules::ConditionsFilterService < FilterService
|
|||
records.any?
|
||||
end
|
||||
|
||||
def conversation_query_string(current_filter, query_hash, current_index)
|
||||
def conversation_query_string(table_name, current_filter, query_hash, current_index)
|
||||
attribute_key = query_hash['attribute_key']
|
||||
query_operator = query_hash['query_operator']
|
||||
|
||||
filter_operator_value = filter_operation(query_hash, current_index)
|
||||
|
||||
case current_filter['attribute_type']
|
||||
when 'additional_attributes'
|
||||
" conversations.additional_attributes ->> '#{attribute_key}' #{filter_operator_value} #{query_operator} "
|
||||
" #{table_name}.additional_attributes ->> '#{attribute_key}' #{filter_operator_value} #{query_operator} "
|
||||
when 'standard'
|
||||
if attribute_key == 'labels'
|
||||
" tags.id #{filter_operator_value} #{query_operator} "
|
||||
else
|
||||
" conversations.#{attribute_key} #{filter_operator_value} #{query_operator} "
|
||||
" #{table_name}.#{attribute_key} #{filter_operator_value} #{query_operator} "
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def base_relation
|
||||
Conversation.where(id: @conversation.id)
|
||||
Conversation.where(id: @conversation.id).joins('LEFT OUTER JOIN contacts on conversations.contact_id = contacts.id')
|
||||
end
|
||||
end
|
||||
|
|
|
@ -26,7 +26,7 @@ class Contacts::FilterService < FilterService
|
|||
query_operator = query_hash[:query_operator]
|
||||
filter_operator_value = filter_operation(query_hash, current_index)
|
||||
|
||||
return custom_attribute_query(query_hash, current_index) if current_filter.nil?
|
||||
return custom_attribute_query(query_hash, 'contacts', current_index) if current_filter.nil?
|
||||
|
||||
case current_filter['attribute_type']
|
||||
when 'additional_attributes'
|
||||
|
@ -64,18 +64,4 @@ class Contacts::FilterService < FilterService
|
|||
|
||||
"!= :value_#{current_index}"
|
||||
end
|
||||
|
||||
def custom_attribute_query(query_hash, current_index)
|
||||
attribute_key = query_hash[:attribute_key]
|
||||
query_operator = query_hash[:query_operator]
|
||||
attribute_type = custom_attribute(attribute_key).try(:attribute_display_type)
|
||||
filter_operator_value = filter_operation(query_hash, current_index)
|
||||
attribute_data_type = self.class::ATTRIBUTE_TYPES[attribute_type]
|
||||
|
||||
if custom_attribute(attribute_key)
|
||||
" LOWER(contacts.custom_attributes ->> '#{attribute_key}')::#{attribute_data_type} #{filter_operator_value} #{query_operator} "
|
||||
else
|
||||
' '
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -32,7 +32,7 @@ class Conversations::FilterService < FilterService
|
|||
query_operator = query_hash[:query_operator]
|
||||
filter_operator_value = filter_operation(query_hash, current_index)
|
||||
|
||||
return custom_attribute_query(query_hash, current_index) if current_filter.nil?
|
||||
return custom_attribute_query(query_hash, 'conversations', current_index) if current_filter.nil?
|
||||
|
||||
case current_filter['attribute_type']
|
||||
when 'additional_attributes'
|
||||
|
@ -62,20 +62,4 @@ class Conversations::FilterService < FilterService
|
|||
)
|
||||
@conversations.latest.page(current_page)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def custom_attribute_query(query_hash, current_index)
|
||||
attribute_key = query_hash[:attribute_key]
|
||||
query_operator = query_hash[:query_operator]
|
||||
attribute_type = custom_attribute(attribute_key).try(:attribute_display_type)
|
||||
filter_operator_value = filter_operation(query_hash, current_index)
|
||||
attribute_data_type = self.class::ATTRIBUTE_TYPES[attribute_type]
|
||||
|
||||
if custom_attribute(attribute_key)
|
||||
" LOWER(conversations.custom_attributes ->> '#{attribute_key}')::#{attribute_data_type} #{filter_operator_value} #{query_operator} "
|
||||
else
|
||||
' '
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -87,10 +87,26 @@ class FilterService
|
|||
]
|
||||
end
|
||||
|
||||
def custom_attribute_query(query_hash, table_name, current_index)
|
||||
attribute_key = query_hash[:attribute_key]
|
||||
query_operator = query_hash[:query_operator]
|
||||
|
||||
attribute_type = custom_attribute(attribute_key, @account).try(:attribute_display_type)
|
||||
filter_operator_value = filter_operation(query_hash, current_index)
|
||||
attribute_data_type = self.class::ATTRIBUTE_TYPES[attribute_type]
|
||||
|
||||
if custom_attribute(attribute_key, @account)
|
||||
" LOWER(#{table_name}.custom_attributes ->> '#{attribute_key}')::#{attribute_data_type} #{filter_operator_value} #{query_operator} "
|
||||
else
|
||||
' '
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def custom_attribute(attribute_key)
|
||||
@custom_attribute = Current.account.custom_attribute_definitions.where(
|
||||
def custom_attribute(attribute_key, account = nil)
|
||||
current_account = account || Current.account
|
||||
@custom_attribute = current_account.custom_attribute_definitions.where(
|
||||
attribute_model: self.class::ATTRIBUTE_MODEL
|
||||
).find_by(attribute_key: attribute_key)
|
||||
end
|
||||
|
|
|
@ -142,6 +142,13 @@
|
|||
"filter_operators": [ "equal_to", "not_equal_to", "contains", "does_not_contain" ],
|
||||
"attribute_type": "additional_attributes"
|
||||
},
|
||||
"company": {
|
||||
"attribute_name": "Company",
|
||||
"input_type": "textbox",
|
||||
"data_type": "text",
|
||||
"filter_operators": [ "equal_to", "not_equal_to", "contains", "does_not_contain" ],
|
||||
"attribute_type": "additional_attributes"
|
||||
},
|
||||
"labels": {
|
||||
"attribute_name": "Labels",
|
||||
"input_type": "tags",
|
||||
|
|
|
@ -15,6 +15,12 @@ describe AutomationRuleListener do
|
|||
end
|
||||
|
||||
before do
|
||||
create(:custom_attribute_definition,
|
||||
attribute_key: 'customer_type',
|
||||
account: account,
|
||||
attribute_model: 'contact_attribute',
|
||||
attribute_display_type: 'list',
|
||||
attribute_values: %w[regular platinum gold])
|
||||
create(:team_member, user: user_1, team: team)
|
||||
create(:team_member, user: user_2, team: team)
|
||||
create(:account_user, user: user_2, account: account)
|
||||
|
@ -77,6 +83,7 @@ describe AutomationRuleListener do
|
|||
listener.conversation_status_changed(event)
|
||||
|
||||
conversation.reload
|
||||
|
||||
expect(conversation.labels.pluck(:name)).to contain_exactly('support', 'priority_customer')
|
||||
end
|
||||
|
||||
|
@ -146,6 +153,108 @@ describe AutomationRuleListener do
|
|||
end
|
||||
end
|
||||
|
||||
describe '#conversation_updated with contacts attributes' do
|
||||
before do
|
||||
conversation.contact.update!(custom_attributes: { customer_type: 'platinum', signed_in_at: '2022-01-19' },
|
||||
additional_attributes: { 'company': 'Marvel' })
|
||||
|
||||
automation_rule.update!(
|
||||
event_name: 'conversation_updated',
|
||||
name: 'Call actions conversation updated',
|
||||
description: 'Add labels, assign team after conversation updated',
|
||||
conditions: [
|
||||
{
|
||||
attribute_key: 'company',
|
||||
filter_operator: 'equal_to',
|
||||
values: ['Marvel'],
|
||||
query_operator: 'AND'
|
||||
}.with_indifferent_access,
|
||||
{
|
||||
attribute_key: 'customer_type',
|
||||
filter_operator: 'equal_to',
|
||||
values: ['platinum'],
|
||||
query_operator: nil
|
||||
}.with_indifferent_access
|
||||
]
|
||||
)
|
||||
end
|
||||
|
||||
let!(:event) do
|
||||
Events::Base.new('conversation_updated', Time.zone.now, { conversation: conversation })
|
||||
end
|
||||
|
||||
context 'when rule matches with additional_attributes and custom_attributes' do
|
||||
it 'triggers automation rule to assign team' do
|
||||
expect(conversation.team_id).not_to eq(team.id)
|
||||
|
||||
listener.conversation_updated(event)
|
||||
|
||||
conversation.reload
|
||||
expect(conversation.team_id).to eq(team.id)
|
||||
end
|
||||
|
||||
it 'triggers automation rule to add label and assign best agents' do
|
||||
expect(conversation.labels).to eq([])
|
||||
expect(conversation.assignee).to be_nil
|
||||
|
||||
listener.conversation_updated(event)
|
||||
|
||||
conversation.reload
|
||||
expect(conversation.labels.pluck(:name)).to contain_exactly('support', 'priority_customer')
|
||||
expect(conversation.assignee).to eq(user_1)
|
||||
end
|
||||
|
||||
it 'triggers automation rule send email transcript to the mentioned email' do
|
||||
mailer = double
|
||||
|
||||
expect(TeamNotifications::AutomationNotificationMailer).to receive(:conversation_updated)
|
||||
|
||||
listener.conversation_updated(event)
|
||||
|
||||
conversation.reload
|
||||
|
||||
allow(mailer).to receive(:conversation_transcript)
|
||||
end
|
||||
|
||||
it 'triggers automation rule send message to the contacts' do
|
||||
expect(conversation.messages).to be_empty
|
||||
|
||||
expect(TeamNotifications::AutomationNotificationMailer).to receive(:conversation_updated)
|
||||
|
||||
listener.conversation_updated(event)
|
||||
|
||||
conversation.reload
|
||||
|
||||
expect(conversation.messages.last.content).to eq('Send this message.')
|
||||
end
|
||||
|
||||
it 'triggers automation_rule with contact standard attributes' do
|
||||
automation_rule.update!(
|
||||
conditions: [
|
||||
{
|
||||
attribute_key: 'email',
|
||||
filter_operator: 'contains',
|
||||
values: ['example.com'],
|
||||
query_operator: 'AND'
|
||||
}.with_indifferent_access,
|
||||
{
|
||||
attribute_key: 'customer_type',
|
||||
filter_operator: 'equal_to',
|
||||
values: ['platinum'],
|
||||
query_operator: nil
|
||||
}.with_indifferent_access
|
||||
]
|
||||
)
|
||||
expect(conversation.team_id).not_to eq(team.id)
|
||||
|
||||
listener.conversation_updated(event)
|
||||
|
||||
conversation.reload
|
||||
expect(conversation.team_id).to eq(team.id)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#conversation_updated' do
|
||||
before do
|
||||
automation_rule.update!(
|
||||
|
|
Loading…
Reference in a new issue