From 315896624152b001c7e20adb8bbe4150f6be1127 Mon Sep 17 00:00:00 2001 From: Tejaswini Chile Date: Tue, 29 Mar 2022 17:29:34 +0530 Subject: [PATCH] Feat: automation rule based on contact conditions (#4230) --- .../accounts/automation_rules_controller.rb | 1 + .../conditions_filter_service.rb | 28 +++-- app/services/contacts/filter_service.rb | 16 +-- app/services/conversations/filter_service.rb | 18 +-- app/services/filter_service.rb | 20 +++- lib/filters/filter_keys.json | 7 ++ .../automation_rule_listener_spec.rb | 109 ++++++++++++++++++ 7 files changed, 158 insertions(+), 41 deletions(-) diff --git a/app/controllers/api/v1/accounts/automation_rules_controller.rb b/app/controllers/api/v1/accounts/automation_rules_controller.rb index 2dc71bcec..e1e5ddbad 100644 --- a/app/controllers/api/v1/accounts/automation_rules_controller.rb +++ b/app/controllers/api/v1/accounts/automation_rules_controller.rb @@ -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 diff --git a/app/services/automation_rules/conditions_filter_service.rb b/app/services/automation_rules/conditions_filter_service.rb index 117487b72..46988771d 100644 --- a/app/services/automation_rules/conditions_filter_service.rb +++ b/app/services/automation_rules/conditions_filter_service.rb @@ -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 diff --git a/app/services/contacts/filter_service.rb b/app/services/contacts/filter_service.rb index 81946435e..22ed9891b 100644 --- a/app/services/contacts/filter_service.rb +++ b/app/services/contacts/filter_service.rb @@ -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 diff --git a/app/services/conversations/filter_service.rb b/app/services/conversations/filter_service.rb index a5773a228..e52fd279d 100644 --- a/app/services/conversations/filter_service.rb +++ b/app/services/conversations/filter_service.rb @@ -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 diff --git a/app/services/filter_service.rb b/app/services/filter_service.rb index e0167b5af..0031d828d 100644 --- a/app/services/filter_service.rb +++ b/app/services/filter_service.rb @@ -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 diff --git a/lib/filters/filter_keys.json b/lib/filters/filter_keys.json index 2736473b7..3d662d19b 100644 --- a/lib/filters/filter_keys.json +++ b/lib/filters/filter_keys.json @@ -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", diff --git a/spec/listeners/automation_rule_listener_spec.rb b/spec/listeners/automation_rule_listener_spec.rb index 66a9a68ac..bf015fab3 100644 --- a/spec/listeners/automation_rule_listener_spec.rb +++ b/spec/listeners/automation_rule_listener_spec.rb @@ -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!(