diff --git a/Gemfile b/Gemfile index 0664e5aef..297a88497 100644 --- a/Gemfile +++ b/Gemfile @@ -42,7 +42,7 @@ gem 'down', '~> 5.0' gem 'aws-sdk-s3', require: false gem 'azure-storage-blob', require: false gem 'google-cloud-storage', require: false -gem 'image_processing' +gem 'image_processing', '~> 1.12.2' ##-- gems for database --# gem 'groupdate' diff --git a/Gemfile.lock b/Gemfile.lock index 2222403f1..eb6022ac7 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -688,7 +688,7 @@ DEPENDENCIES hairtrigger hashie html2text - image_processing + image_processing (~> 1.12.2) jbuilder json_refs json_schemer diff --git a/app/controllers/api/v1/accounts/automation_rules_controller.rb b/app/controllers/api/v1/accounts/automation_rules_controller.rb index 2dc71bcec..3971dbcaf 100644 --- a/app/controllers/api/v1/accounts/automation_rules_controller.rb +++ b/app/controllers/api/v1/accounts/automation_rules_controller.rb @@ -34,7 +34,7 @@ class Api::V1::Accounts::AutomationRulesController < Api::V1::Accounts::BaseCont params.permit( :name, :description, :event_name, :account_id, :active, conditions: [:attribute_key, :filter_operator, :query_operator, { values: [] }], - actions: [:action_name, { action_params: [] }] + actions: [:action_name, { action_params: [{}] }] ) end diff --git a/app/javascript/dashboard/routes/dashboard/settings/automation/constants.js b/app/javascript/dashboard/routes/dashboard/settings/automation/constants.js index b3b506f01..90e3a7395 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/automation/constants.js +++ b/app/javascript/dashboard/routes/dashboard/settings/automation/constants.js @@ -79,7 +79,7 @@ export const AUTOMATIONS = { // { // key: 'send_email_to_team', // name: 'Send an email to team', - // attributeI18nKey: 'SEND_MESSAGE', + // attributeI18nKey: 'SEND_EMAIL_TO_TEAM', // }, ], }, @@ -186,7 +186,7 @@ export const AUTOMATIONS = { // { // key: 'send_email_to_team', // name: 'Send an email to team', - // attributeI18nKey: 'SEND_MESSAGE', + // attributeI18nKey: 'SEND_EMAIL_TO_TEAM', // }, { key: 'assign_agent', diff --git a/app/listeners/automation_rule_listener.rb b/app/listeners/automation_rule_listener.rb index 022c49242..19fb56287 100644 --- a/app/listeners/automation_rule_listener.rb +++ b/app/listeners/automation_rule_listener.rb @@ -1,51 +1,58 @@ class AutomationRuleListener < BaseListener def conversation_updated(event_obj) conversation = event_obj.data[:conversation] - return unless rule_present?('conversation_updated', conversation) + account = conversation.account + + return unless rule_present?('conversation_updated', account) @rules.each do |rule| conditions_match = ::AutomationRules::ConditionsFilterService.new(rule, conversation).perform - AutomationRules::ActionService.new(rule, conversation).perform if conditions_match.present? + AutomationRules::ActionService.new(rule, account, conversation).perform if conditions_match.present? end end def conversation_status_changed(event_obj) conversation = event_obj.data[:conversation] - return unless rule_present?('conversation_status_changed', conversation) + account = conversation.account + + return unless rule_present?('conversation_status_changed', account) @rules.each do |rule| conditions_match = ::AutomationRules::ConditionsFilterService.new(rule, conversation).perform - AutomationRules::ActionService.new(rule, conversation).perform if conditions_match.present? + AutomationRules::ActionService.new(rule, account, conversation).perform if conditions_match.present? end end def conversation_created(event_obj) conversation = event_obj.data[:conversation] - return unless rule_present?('conversation_created', conversation) + account = conversation.account + + return unless rule_present?('conversation_created', account) @rules.each do |rule| conditions_match = ::AutomationRules::ConditionsFilterService.new(rule, conversation).perform - ::AutomationRules::ActionService.new(rule, conversation).perform if conditions_match.present? + ::AutomationRules::ActionService.new(rule, account, conversation).perform if conditions_match.present? end end def message_created(event_obj) message = event_obj.data[:message] - conversation = message.conversation - return unless rule_present?('message_created', conversation) + account = message.try(:account) + + return unless rule_present?('message_created', account) @rules.each do |rule| - conditions_match = ::AutomationRules::ConditionsFilterService.new(rule, conversation).message_conditions(message) - ::AutomationRules::ActionService.new(rule, conversation).perform if conditions_match.present? + conditions_match = ::AutomationRules::ConditionsFilterService.new(rule, message.conversation).message_conditions + ::AutomationRules::ActionService.new(rule, account, message.conversation).perform if conditions_match.present? end end - def rule_present?(event_name, conversation) - return if conversation.blank? + def rule_present?(event_name, account) + return if account.blank? @rules = AutomationRule.where( event_name: event_name, - account_id: conversation.account_id, + account_id: account.id, active: true ) @rules.any? diff --git a/app/mailers/team_notifications/automation_notification_mailer.rb b/app/mailers/team_notifications/automation_notification_mailer.rb index a6d219c54..fd96cdd1d 100644 --- a/app/mailers/team_notifications/automation_notification_mailer.rb +++ b/app/mailers/team_notifications/automation_notification_mailer.rb @@ -4,50 +4,57 @@ class TeamNotifications::AutomationNotificationMailer < ApplicationMailer @agents = team.team_members @conversation = conversation - @message = message + @custom_message = message @action_url = app_account_conversation_url(account_id: @conversation.account_id, id: @conversation.display_id) - send_an_email_to_team + send_an_email_to_team and return end - def conversation_updated(conversation, team) + def conversation_updated(conversation, team, message) return unless smtp_config_set_or_development? @agents = team.team_members @conversation = conversation - @message = message + @custom_message = message @action_url = app_account_conversation_url(account_id: @conversation.account_id, id: @conversation.display_id) - send_an_email_to_team + send_an_email_to_team and return end - def message_created(message, agent) + def message_created(conversation, team, message) return unless smtp_config_set_or_development? - @agent = agent - @conversation = message.conversation - @message = message - subject = "#{@agent.available_name}, You have been mentioned in conversation [ID - #{@conversation.display_id}]" + @agents = team.team_members + @conversation = conversation + @custom_message = message @action_url = app_account_conversation_url(account_id: @conversation.account_id, id: @conversation.display_id) - send_mail_with_liquid(to: @agent.email, subject: subject) + + send_an_email_to_team and return end private def send_an_email_to_team @agents.each do |agent| - subject = "#{@agent.available_name}, A new conversation [ID - #{@conversation.display_id}] has been created in #{@conversation.inbox&.name}." + subject = "#{agent.user.available_name}, This email has been sent via automation rule actions." @action_url = app_account_conversation_url(account_id: @conversation.account_id, id: @conversation.display_id) - send_mail_with_liquid(to: agent.email, subject: subject) + @agent = agent + + send_mail_with_liquid(to: @agent.user.email, subject: subject) end end def liquid_droppables - super.merge({ - user: @agent, - conversation: @conversation, - inbox: @conversation.inbox, - message: @message - }) + super.merge!({ + user: @agent.user, + conversation: @conversation, + inbox: @conversation.inbox + }) + end + + def liquid_locals + super.merge!({ + custom_message: @custom_message + }) end end diff --git a/app/models/automation_rule.rb b/app/models/automation_rule.rb index 4f9717481..0d25a2786 100644 --- a/app/models/automation_rule.rb +++ b/app/models/automation_rule.rb @@ -20,9 +20,9 @@ class AutomationRule < ApplicationRecord belongs_to :account - validates :account, presence: true validate :json_conditions_format validate :json_actions_format + validates :account_id, presence: true scope :active, -> { where(active: true) } diff --git a/app/models/conversation.rb b/app/models/conversation.rb index c9ce4f6a8..6e3a91fb2 100644 --- a/app/models/conversation.rb +++ b/app/models/conversation.rb @@ -210,6 +210,8 @@ class Conversation < ApplicationRecord end def dispatcher_dispatch(event_name) + return if Current.executed_by.present? && Current.executed_by.instance_of?(AutomationRule) + Rails.configuration.dispatcher.dispatch(event_name, Time.zone.now, conversation: self, notifiable_assignee_change: notifiable_assignee_change?) end diff --git a/app/models/message.rb b/app/models/message.rb index 6ad607997..adfc914b6 100644 --- a/app/models/message.rb +++ b/app/models/message.rb @@ -166,6 +166,8 @@ class Message < ApplicationRecord end def dispatch_create_events + return if Current.executed_by.present? && Current.executed_by.instance_of?(AutomationRule) + Rails.configuration.dispatcher.dispatch(MESSAGE_CREATED, Time.zone.now, message: self) if outgoing? && conversation.messages.outgoing.count == 1 @@ -174,6 +176,8 @@ class Message < ApplicationRecord end def dispatch_update_event + return if Current.executed_by.present? && Current.executed_by.instance_of?(AutomationRule) + Rails.configuration.dispatcher.dispatch(MESSAGE_UPDATED, Time.zone.now, message: self) end diff --git a/app/services/automation_rules/action_service.rb b/app/services/automation_rules/action_service.rb index 94dc7ab28..158ac59c5 100644 --- a/app/services/automation_rules/action_service.rb +++ b/app/services/automation_rules/action_service.rb @@ -1,40 +1,68 @@ class AutomationRules::ActionService - def initialize(rule, conversation) + def initialize(rule, account, conversation) @rule = rule + @account = account @conversation = conversation - @account = @conversation.account + Current.executed_by = rule end def perform - @rule.actions.each do |action, _current_index| + @rule.actions.each do |action| action = action.with_indifferent_access - send(action[:action_name], action[:action_params]) + begin + send(action[:action_name], action[:action_params]) + rescue StandardError => e + Sentry.capture_exception(e) + end end + ensure + Current.reset end private + def send_email_transcript(email) + ConversationReplyMailer.with(account: conversation.account).conversation_transcript(@conversation, email)&.deliver_later + end + + def mute_conversation(_params) + @conversation.mute! + end + + def change_status(status) + @conversation.update!(status: status[0]) + end + + def send_webhook_events(webhook_url) + payload = @conversation.webhook_data.merge(event: "automation_event: #{@rule.event_name}") + WebhookJob.perform_later(webhook_url, payload) + end + def send_message(message) - # params = { content: message, private: false } - # mb = Messages::MessageBuilder.new(@administrator, @conversation, params) - # mb.perform + return if @rule.event_name == 'message_created' + + params = { content: message[0], private: false } + mb = Messages::MessageBuilder.new(@administrator, @conversation, params) + mb.perform end def assign_team(team_ids = []) return unless team_belongs_to_account?(team_ids) - @account.teams.find_by(id: team_ids) @conversation.update!(team_id: team_ids[0]) end - def assign_best_agents(agent_ids = []) + def assign_best_agent(agent_ids = []) return unless agent_belongs_to_account?(agent_ids) @agent = @account.users.find_by(id: agent_ids) - @conversation.update_assignee(@agent) + + @conversation.update!(assignee_id: @agent.id) if @agent.present? end - def add_label(labels = []) + def add_label(labels) + return if labels.empty? + @conversation.add_labels(labels) end @@ -43,11 +71,11 @@ class AutomationRules::ActionService case @rule.event_name when 'conversation_created', 'conversation_status_changed' - TeamNotifications::AutomationNotificationMailer.conversation_creation(@conversation, team, params[:message]) + TeamNotifications::AutomationNotificationMailer.conversation_creation(@conversation, team, params[:message])&.deliver_now when 'conversation_updated' - TeamNotifications::AutomationNotificationMailer.conversation_updated(@conversation, team, params[:message]) + TeamNotifications::AutomationNotificationMailer.conversation_updated(@conversation, team, params[:message])&.deliver_now when 'message_created' - TeamNotifications::AutomationNotificationMailer.message_created(@conversation, team, params[:message]) + TeamNotifications::AutomationNotificationMailer.message_created(@conversation, team, params[:message])&.deliver_now end end diff --git a/app/services/automation_rules/conditions_filter_service.rb b/app/services/automation_rules/conditions_filter_service.rb index 2b1fa9e0e..117487b72 100644 --- a/app/services/automation_rules/conditions_filter_service.rb +++ b/app/services/automation_rules/conditions_filter_service.rb @@ -1,7 +1,7 @@ require 'json' class AutomationRules::ConditionsFilterService < FilterService - def initialize(rule, conversation) + def initialize(rule, conversation = nil) super([], nil) @rule = rule @conversation = conversation @@ -21,14 +21,14 @@ class AutomationRules::ConditionsFilterService < FilterService records.any? end - def message_conditions(message) + def message_conditions message_filters = @filters['messages'] @rule.conditions.each_with_index do |query_hash, current_index| current_filter = message_filters[query_hash['attribute_key']] @query_string += message_query_string(current_filter, query_hash.with_indifferent_access, current_index) end - records = Message.where(id: message.id).where(@query_string, @filter_values.with_indifferent_access) + records = Message.where(conversation: @conversation).where(@query_string, @filter_values.with_indifferent_access) records.any? end @@ -44,6 +44,19 @@ class AutomationRules::ConditionsFilterService < FilterService end end + # This will be used in future for contact automation rule + def contact_conditions(_contact) + conversation_filters = @filters['conversations'] + + @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) + end + + records = base_relation.where(@query_string, @filter_values.with_indifferent_access) + records.any? + end + def conversation_query_string(current_filter, query_hash, current_index) attribute_key = query_hash['attribute_key'] query_operator = query_hash['query_operator'] @@ -63,6 +76,6 @@ class AutomationRules::ConditionsFilterService < FilterService end def base_relation - Conversation.where(id: @conversation) + Conversation.where(id: @conversation.id) end end diff --git a/app/views/mailers/team_notifications/automation_notification_mailer/conversation_creation.liquid b/app/views/mailers/team_notifications/automation_notification_mailer/conversation_creation.liquid index e324dd9e6..8edefcc07 100644 --- a/app/views/mailers/team_notifications/automation_notification_mailer/conversation_creation.liquid +++ b/app/views/mailers/team_notifications/automation_notification_mailer/conversation_creation.liquid @@ -1,7 +1,7 @@

Hi {{user.available_name}}

-

Time to save the world. A new conversation has been created in {{ inbox.name }}

+

{{ custom_message }}

Click here to get cracking. diff --git a/app/views/mailers/team_notifications/automation_notification_mailer/conversation_updated.liquid b/app/views/mailers/team_notifications/automation_notification_mailer/conversation_updated.liquid new file mode 100644 index 000000000..8edefcc07 --- /dev/null +++ b/app/views/mailers/team_notifications/automation_notification_mailer/conversation_updated.liquid @@ -0,0 +1,8 @@ +

Hi {{user.available_name}}

+ + +

{{ custom_message }}

+ +

+Click here to get cracking. +

diff --git a/app/views/mailers/team_notifications/automation_notification_mailer/message_created.liquid b/app/views/mailers/team_notifications/automation_notification_mailer/message_created.liquid new file mode 100644 index 000000000..8edefcc07 --- /dev/null +++ b/app/views/mailers/team_notifications/automation_notification_mailer/message_created.liquid @@ -0,0 +1,8 @@ +

Hi {{user.available_name}}

+ + +

{{ custom_message }}

+ +

+Click here to get cracking. +

diff --git a/lib/current.rb b/lib/current.rb index 6f98a7670..3376099f8 100644 --- a/lib/current.rb +++ b/lib/current.rb @@ -2,12 +2,14 @@ module Current thread_mattr_accessor :user thread_mattr_accessor :account thread_mattr_accessor :account_user + thread_mattr_accessor :executed_by thread_mattr_accessor :contact def self.reset Current.user = nil Current.account = nil Current.account_user = nil + Current.executed_by = nil Current.contact = nil end end diff --git a/spec/listeners/automation_rule_listener_spec.rb b/spec/listeners/automation_rule_listener_spec.rb index 8e6a9bc32..8a24a7ea1 100644 --- a/spec/listeners/automation_rule_listener_spec.rb +++ b/spec/listeners/automation_rule_listener_spec.rb @@ -31,16 +31,36 @@ describe AutomationRuleListener do }, { 'action_name' => 'assign_team', 'action_params' => [team.id] }, { 'action_name' => 'add_label', 'action_params' => %w[support priority_customer] }, - { 'action_name' => 'assign_best_agents', 'action_params' => [user_1.id] } + { 'action_name' => 'send_webhook_events', 'action_params' => 'https://www.example.com' }, + { 'action_name' => 'assign_best_agent', 'action_params' => [user_1.id] }, + { 'action_name' => 'send_email_transcript', 'action_params' => 'new_agent@example.com' }, + { 'action_name' => 'mute_conversation', 'action_params' => nil }, + { 'action_name' => 'change_status', 'action_params' => ['snoozed'] }, + { 'action_name' => 'send_message', 'action_params' => ['Send this message.'] } ]) end describe '#conversation_status_changed' do context 'when rule matches' do + it 'triggers automation rule send webhook events' do + payload = conversation.webhook_data.merge(event: "automation_event: #{automation_rule.event_name}") + + automation_rule + + expect(TeamNotifications::AutomationNotificationMailer).to receive(:conversation_creation) + + expect(WebhookJob).to receive(:perform_later).with('https://www.example.com', payload).once + + listener.conversation_status_changed(event) + end + it 'triggers automation rule to assign team' do expect(conversation.team_id).not_to eq(team.id) automation_rule + + expect(TeamNotifications::AutomationNotificationMailer).to receive(:conversation_creation) + listener.conversation_status_changed(event) conversation.reload @@ -51,6 +71,9 @@ describe AutomationRuleListener do expect(conversation.labels).to eq([]) automation_rule + + expect(TeamNotifications::AutomationNotificationMailer).to receive(:conversation_creation) + listener.conversation_status_changed(event) conversation.reload @@ -61,12 +84,65 @@ describe AutomationRuleListener do expect(conversation.assignee).to be_nil automation_rule + + expect(TeamNotifications::AutomationNotificationMailer).to receive(:conversation_creation) + listener.conversation_status_changed(event) conversation.reload expect(conversation.assignee).to eq(user_1) end + + it 'triggers automation rule send message to the contacts' do + expect(conversation.messages).to be_empty + + automation_rule + + expect(TeamNotifications::AutomationNotificationMailer).to receive(:conversation_creation) + + listener.conversation_status_changed(event) + + conversation.reload + + expect(conversation.messages.last.content).to eq('Send this message.') + end + + it 'triggers automation rule changes status to snoozed' do + expect(conversation.status).to eq('resolved') + + automation_rule + + expect(TeamNotifications::AutomationNotificationMailer).to receive(:conversation_creation) + + listener.conversation_status_changed(event) + + conversation.reload + + expect(conversation.status).to eq('snoozed') + end + + it 'triggers automation rule send email transcript to the mentioned email' do + mailer = double + + automation_rule + + expect(TeamNotifications::AutomationNotificationMailer).to receive(:conversation_creation) + + listener.conversation_status_changed(event) + + conversation.reload + + allow(mailer).to receive(:conversation_transcript) + end + + it 'triggers automation rule send email to the team' do + automation_rule + + expect(TeamNotifications::AutomationNotificationMailer).to receive(:conversation_creation) + + listener.conversation_status_changed(event) + end end end @@ -114,6 +190,42 @@ describe AutomationRuleListener do expect(conversation.assignee).to eq(user_1) end + + it 'triggers automation rule send email transcript to the mentioned email' do + mailer = double + + automation_rule + + 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 email to the team' do + automation_rule + + expect(TeamNotifications::AutomationNotificationMailer).to receive(:conversation_updated) + + listener.conversation_updated(event) + end + + it 'triggers automation rule send message to the contacts' do + expect(conversation.messages).to be_empty + + automation_rule + + expect(TeamNotifications::AutomationNotificationMailer).to receive(:conversation_updated) + + listener.conversation_updated(event) + + conversation.reload + + expect(conversation.messages.last.content).to eq('Send this message.') + end end end @@ -137,6 +249,9 @@ describe AutomationRuleListener do expect(conversation.team_id).not_to eq(team.id) automation_rule + + expect(TeamNotifications::AutomationNotificationMailer).to receive(:message_created) + listener.message_created(event) conversation.reload @@ -147,22 +262,41 @@ describe AutomationRuleListener do expect(conversation.labels).to eq([]) automation_rule + + expect(TeamNotifications::AutomationNotificationMailer).to receive(:message_created) + listener.message_created(event) conversation.reload expect(conversation.labels.pluck(:name)).to eq(%w[support priority_customer]) end - it 'triggers automation rule to assign best agents' do + it 'triggers automation rule to assign best agent' do expect(conversation.assignee).to be_nil - automation_rule + + expect(TeamNotifications::AutomationNotificationMailer).to receive(:message_created) + listener.message_created(event) conversation.reload expect(conversation.assignee).to eq(user_1) end + + it 'triggers automation rule send email transcript to the mentioned email' do + mailer = double + + automation_rule + + expect(TeamNotifications::AutomationNotificationMailer).to receive(:message_created) + + listener.message_created(event) + + conversation.reload + + allow(mailer).to receive(:conversation_transcript) + end end end end diff --git a/spec/models/automation_rule_spec.rb b/spec/models/automation_rule_spec.rb index 6b06c3197..fe5fe46c7 100644 --- a/spec/models/automation_rule_spec.rb +++ b/spec/models/automation_rule_spec.rb @@ -2,11 +2,13 @@ require 'rails_helper' RSpec.describe AutomationRule, type: :model do describe 'associations' do + let(:account) { create(:account) } let(:params) do { name: 'Notify Conversation Created and mark priority query', description: 'Notify all administrator about conversation created and mark priority query', event_name: 'conversation_created', + account_id: account.id, conditions: [ { attribute_key: 'browser_language',