feat: Save automation rules (#3359)
This commit is contained in:
parent
9a9462f5cb
commit
a0884310f4
24 changed files with 746 additions and 3 deletions
|
@ -8,9 +8,11 @@ class Messages::MessageBuilder
|
||||||
@conversation = conversation
|
@conversation = conversation
|
||||||
@user = user
|
@user = user
|
||||||
@message_type = params[:message_type] || 'outgoing'
|
@message_type = params[:message_type] || 'outgoing'
|
||||||
@items = params.to_unsafe_h&.dig(:content_attributes, :items)
|
|
||||||
@attachments = params[:attachments]
|
@attachments = params[:attachments]
|
||||||
|
return unless params.instance_of?(ActionController::Parameters)
|
||||||
|
|
||||||
@in_reply_to = params.to_unsafe_h&.dig(:content_attributes, :in_reply_to)
|
@in_reply_to = params.to_unsafe_h&.dig(:content_attributes, :in_reply_to)
|
||||||
|
@items = params.to_unsafe_h&.dig(:content_attributes, :items)
|
||||||
end
|
end
|
||||||
|
|
||||||
def perform
|
def perform
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
class Api::V1::Accounts::AutomationRulesController < Api::V1::Accounts::BaseController
|
||||||
|
before_action :check_authorization
|
||||||
|
|
||||||
|
def index
|
||||||
|
@automation_rules = Current.account.automation_rules
|
||||||
|
end
|
||||||
|
|
||||||
|
def create
|
||||||
|
@automation_rule = Current.account.automation_rules.create(automation_rules_permit)
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def automation_rules_permit
|
||||||
|
params.permit(
|
||||||
|
:name, :description, :event_name, :account_id,
|
||||||
|
conditions: [:attribute_key, :filter_operator, :query_operator, { values: [] }],
|
||||||
|
actions: [:action_name, { action_params: [:intiated_at] }]
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
|
@ -16,7 +16,8 @@ class AsyncDispatcher < BaseDispatcher
|
||||||
HookListener.instance,
|
HookListener.instance,
|
||||||
InstallationWebhookListener.instance,
|
InstallationWebhookListener.instance,
|
||||||
NotificationListener.instance,
|
NotificationListener.instance,
|
||||||
WebhookListener.instance
|
WebhookListener.instance,
|
||||||
|
AutomationRuleListener.instance
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
29
app/listeners/automation_rule_listener.rb
Normal file
29
app/listeners/automation_rule_listener.rb
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
class AutomationRuleListener < BaseListener
|
||||||
|
def conversation_status_changed(event_obj)
|
||||||
|
conversation = event_obj.data[:conversation]
|
||||||
|
return unless rule_present?('conversation_status_changed', conversation)
|
||||||
|
|
||||||
|
@rules.each do |rule|
|
||||||
|
conditions_match = ::AutomationRules::ConditionsFilterService.new(rule, conversation).perform
|
||||||
|
AutomationRules::ActionService.new(rule, 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)
|
||||||
|
|
||||||
|
@rules.each do |rule|
|
||||||
|
conditions_match = AutomationRule::ConditionsFilterService.new(rule, conversation).perform
|
||||||
|
AutomationRule::ActionService.new(rule, conversation).perform if conditions_match.present?
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def rule_present?(event_name, conversation)
|
||||||
|
@rules = AutomationRule.where(
|
||||||
|
event_name: event_name,
|
||||||
|
account_id: conversation.account_id
|
||||||
|
)
|
||||||
|
@rules.any?
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,53 @@
|
||||||
|
class TeamNotifications::AutomationNotificationMailer < ApplicationMailer
|
||||||
|
def conversation_creation(conversation, team, message)
|
||||||
|
return unless smtp_config_set_or_development?
|
||||||
|
|
||||||
|
@agents = team.team_members
|
||||||
|
@conversation = conversation
|
||||||
|
@message = message
|
||||||
|
@action_url = app_account_conversation_url(account_id: @conversation.account_id, id: @conversation.display_id)
|
||||||
|
|
||||||
|
send_an_email_to_team
|
||||||
|
end
|
||||||
|
|
||||||
|
def conversation_updated(conversation, team)
|
||||||
|
return unless smtp_config_set_or_development?
|
||||||
|
|
||||||
|
@agents = team.team_members
|
||||||
|
@conversation = conversation
|
||||||
|
@message = message
|
||||||
|
@action_url = app_account_conversation_url(account_id: @conversation.account_id, id: @conversation.display_id)
|
||||||
|
|
||||||
|
send_an_email_to_team
|
||||||
|
end
|
||||||
|
|
||||||
|
def message_created(message, agent)
|
||||||
|
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}]"
|
||||||
|
@action_url = app_account_conversation_url(account_id: @conversation.account_id, id: @conversation.display_id)
|
||||||
|
send_mail_with_liquid(to: @agent.email, subject: subject)
|
||||||
|
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}."
|
||||||
|
@action_url = app_account_conversation_url(account_id: @conversation.account_id, id: @conversation.display_id)
|
||||||
|
send_mail_with_liquid(to: agent.email, subject: subject)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def liquid_droppables
|
||||||
|
super.merge({
|
||||||
|
user: @agent,
|
||||||
|
conversation: @conversation,
|
||||||
|
inbox: @conversation.inbox,
|
||||||
|
message: @message
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end
|
|
@ -69,6 +69,7 @@ class Account < ApplicationRecord
|
||||||
has_many :webhooks, dependent: :destroy_async
|
has_many :webhooks, dependent: :destroy_async
|
||||||
has_many :whatsapp_channels, dependent: :destroy_async, class_name: '::Channel::Whatsapp'
|
has_many :whatsapp_channels, dependent: :destroy_async, class_name: '::Channel::Whatsapp'
|
||||||
has_many :working_hours, dependent: :destroy_async
|
has_many :working_hours, dependent: :destroy_async
|
||||||
|
has_many :automation_rules, dependent: :destroy
|
||||||
|
|
||||||
has_flags ACCOUNT_SETTINGS_FLAGS.merge(column: 'settings_flags').merge(DEFAULT_QUERY_SETTING)
|
has_flags ACCOUNT_SETTINGS_FLAGS.merge(column: 'settings_flags').merge(DEFAULT_QUERY_SETTING)
|
||||||
|
|
||||||
|
|
44
app/models/automation_rule.rb
Normal file
44
app/models/automation_rule.rb
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
# == Schema Information
|
||||||
|
#
|
||||||
|
# Table name: automation_rules
|
||||||
|
#
|
||||||
|
# id :bigint not null, primary key
|
||||||
|
# actions :jsonb not null
|
||||||
|
# conditions :jsonb not null
|
||||||
|
# description :text
|
||||||
|
# event_name :string not null
|
||||||
|
# name :string not null
|
||||||
|
# created_at :datetime not null
|
||||||
|
# updated_at :datetime not null
|
||||||
|
# account_id :bigint not null
|
||||||
|
#
|
||||||
|
# Indexes
|
||||||
|
#
|
||||||
|
# index_automation_rules_on_account_id (account_id)
|
||||||
|
#
|
||||||
|
class AutomationRule < ApplicationRecord
|
||||||
|
belongs_to :account
|
||||||
|
|
||||||
|
validates :account, presence: true
|
||||||
|
validate :json_conditions_format
|
||||||
|
validate :json_actions_format
|
||||||
|
|
||||||
|
CONDITIONS_ATTRS = %w[country_code status browser_language assignee_id team_id referer].freeze
|
||||||
|
ACTIONS_ATTRS = %w[send_message add_label send_email_to_team assign_team assign_best_agents].freeze
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def json_conditions_format
|
||||||
|
return if conditions.nil?
|
||||||
|
|
||||||
|
attributes = conditions.map { |obj, _| obj['attribute_key'] }
|
||||||
|
(attributes - CONDITIONS_ATTRS).blank?
|
||||||
|
end
|
||||||
|
|
||||||
|
def json_actions_format
|
||||||
|
return if actions.nil?
|
||||||
|
|
||||||
|
attributes = actions.map { |obj, _| obj['attribute_key'] }
|
||||||
|
(attributes - ACTIONS_ATTRS).blank?
|
||||||
|
end
|
||||||
|
end
|
|
@ -8,4 +8,9 @@ module Labelable
|
||||||
def update_labels(labels = nil)
|
def update_labels(labels = nil)
|
||||||
update!(label_list: labels)
|
update!(label_list: labels)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def add_labels(new_labels = nil)
|
||||||
|
new_labels << labels
|
||||||
|
update!(label_list: new_labels)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
9
app/policies/automation_rule_policy.rb
Normal file
9
app/policies/automation_rule_policy.rb
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
class AutomationRulePolicy < ApplicationPolicy
|
||||||
|
def index?
|
||||||
|
@account_user.administrator?
|
||||||
|
end
|
||||||
|
|
||||||
|
def create?
|
||||||
|
@account_user.administrator?
|
||||||
|
end
|
||||||
|
end
|
63
app/services/automation_rules/action_service.rb
Normal file
63
app/services/automation_rules/action_service.rb
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
class AutomationRules::ActionService
|
||||||
|
def initialize(rule, conversation)
|
||||||
|
@rule = rule
|
||||||
|
@conversation = conversation
|
||||||
|
@account = @conversation.account
|
||||||
|
end
|
||||||
|
|
||||||
|
def perform
|
||||||
|
@rule.actions.each do |action, _current_index|
|
||||||
|
action = action.with_indifferent_access
|
||||||
|
send(action[:action_name], action[:action_params])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def send_message(message)
|
||||||
|
# params = { content: message, 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 = [])
|
||||||
|
return unless agent_belongs_to_account?(agent_ids)
|
||||||
|
|
||||||
|
@agent = @account.users.find_by(id: agent_ids)
|
||||||
|
@conversation.update_assignee(@agent)
|
||||||
|
end
|
||||||
|
|
||||||
|
def add_label(labels = [])
|
||||||
|
@conversation.add_labels(labels)
|
||||||
|
end
|
||||||
|
|
||||||
|
def send_email_to_team(params)
|
||||||
|
team = Team.find(params[:team_ids][0])
|
||||||
|
|
||||||
|
case @rule.event_name
|
||||||
|
when 'conversation_created', 'conversation_status_changed'
|
||||||
|
TeamNotifications::AutomationNotificationMailer.conversation_creation(@conversation, team, params[:message])
|
||||||
|
when 'conversation_updated'
|
||||||
|
TeamNotifications::AutomationNotificationMailer.conversation_updated(@conversation, team, params[:message])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def administrator
|
||||||
|
@administrator ||= @account.administrators.first
|
||||||
|
end
|
||||||
|
|
||||||
|
def agent_belongs_to_account?(agent_ids)
|
||||||
|
@account.agents.pluck(:id).include?(agent_ids[0])
|
||||||
|
end
|
||||||
|
|
||||||
|
def team_belongs_to_account?(team_ids)
|
||||||
|
@account.team_ids.include?(team_ids[0])
|
||||||
|
end
|
||||||
|
end
|
45
app/services/automation_rules/conditions_filter_service.rb
Normal file
45
app/services/automation_rules/conditions_filter_service.rb
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
require 'json'
|
||||||
|
|
||||||
|
class AutomationRules::ConditionsFilterService < FilterService
|
||||||
|
def initialize(rule, conversation)
|
||||||
|
super([], nil)
|
||||||
|
@rule = rule
|
||||||
|
@conversation = conversation
|
||||||
|
file = File.read('./lib/filters/filter_keys.json')
|
||||||
|
@filters = JSON.parse(file)
|
||||||
|
end
|
||||||
|
|
||||||
|
def perform
|
||||||
|
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']
|
||||||
|
|
||||||
|
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} "
|
||||||
|
when 'standard'
|
||||||
|
if attribute_key == 'labels'
|
||||||
|
" tags.id #{filter_operator_value} #{query_operator} "
|
||||||
|
else
|
||||||
|
" conversations.#{attribute_key} #{filter_operator_value} #{query_operator} "
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def base_relation
|
||||||
|
Conversation.where(id: @conversation)
|
||||||
|
end
|
||||||
|
end
|
|
@ -51,7 +51,6 @@ class Instagram::SendOnInstagramService < Base::SendOnChannelService
|
||||||
def send_to_facebook_page(message_content)
|
def send_to_facebook_page(message_content)
|
||||||
access_token = channel.page_access_token
|
access_token = channel.page_access_token
|
||||||
app_secret_proof = calculate_app_secret_proof(GlobalConfigService.load('FB_APP_SECRET', ''), access_token)
|
app_secret_proof = calculate_app_secret_proof(GlobalConfigService.load('FB_APP_SECRET', ''), access_token)
|
||||||
|
|
||||||
query = { access_token: access_token }
|
query = { access_token: access_token }
|
||||||
query[:appsecret_proof] = app_secret_proof if app_secret_proof
|
query[:appsecret_proof] = app_secret_proof if app_secret_proof
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
json.partial! 'api/v1/accounts/automation_rules/partials/automation_rule.json.jbuilder', automation_rule: @automation_rule
|
|
@ -0,0 +1,5 @@
|
||||||
|
json.data do
|
||||||
|
json.array! @automation_rules do |automation_rule|
|
||||||
|
json.partial! 'api/v1/accounts/automation_rules/partials/automation_rule.json.jbuilder', automation_rule: automation_rule
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,7 @@
|
||||||
|
json.id automation_rule.id
|
||||||
|
json.account_id automation_rule.account_id
|
||||||
|
json.name automation_rule.name
|
||||||
|
json.description automation_rule.description
|
||||||
|
json.event_name automation_rule.event_name
|
||||||
|
json.conditions automation_rule.conditions
|
||||||
|
json.actions automation_rule.actions
|
|
@ -0,0 +1,8 @@
|
||||||
|
<p>Hi {{user.available_name}}</p>
|
||||||
|
|
||||||
|
|
||||||
|
<p>Time to save the world. A new conversation has been created in {{ inbox.name }}</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Click <a href="{{ action_url }}">here</a> to get cracking.
|
||||||
|
</p>
|
|
@ -52,6 +52,7 @@ Rails.application.routes.draw do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
resources :canned_responses, except: [:show, :edit, :new]
|
resources :canned_responses, except: [:show, :edit, :new]
|
||||||
|
resources :automation_rules, only: [:create, :index]
|
||||||
resources :campaigns, only: [:index, :create, :show, :update, :destroy]
|
resources :campaigns, only: [:index, :create, :show, :update, :destroy]
|
||||||
|
|
||||||
namespace :channels do
|
namespace :channels do
|
||||||
|
|
14
db/migrate/20211110101046_create_automation_rules.rb
Normal file
14
db/migrate/20211110101046_create_automation_rules.rb
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
class CreateAutomationRules < ActiveRecord::Migration[6.1]
|
||||||
|
def change
|
||||||
|
create_table :automation_rules do |t|
|
||||||
|
t.bigint :account_id, null: false
|
||||||
|
t.string :name, null: false
|
||||||
|
t.text :description
|
||||||
|
t.string :event_name, null: false
|
||||||
|
t.jsonb :conditions, null: false, default: '{}'
|
||||||
|
t.jsonb :actions, null: false, default: '{}'
|
||||||
|
t.timestamps
|
||||||
|
t.index :account_id, name: 'index_automation_rules_on_account_id'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
12
db/schema.rb
12
db/schema.rb
|
@ -124,6 +124,18 @@ ActiveRecord::Schema.define(version: 2021_12_21_125545) do
|
||||||
t.string "extension"
|
t.string "extension"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
create_table "automation_rules", force: :cascade do |t|
|
||||||
|
t.bigint "account_id", null: false
|
||||||
|
t.string "name", null: false
|
||||||
|
t.text "description"
|
||||||
|
t.string "event_name", null: false
|
||||||
|
t.jsonb "conditions", default: "{}", null: false
|
||||||
|
t.jsonb "actions", default: "{}", null: false
|
||||||
|
t.datetime "created_at", precision: 6, null: false
|
||||||
|
t.datetime "updated_at", precision: 6, null: false
|
||||||
|
t.index ["account_id"], name: "index_automation_rules_on_account_id"
|
||||||
|
end
|
||||||
|
|
||||||
create_table "campaigns", force: :cascade do |t|
|
create_table "campaigns", force: :cascade do |t|
|
||||||
t.integer "display_id", null: false
|
t.integer "display_id", null: false
|
||||||
t.string "title", null: false
|
t.string "title", null: false
|
||||||
|
|
160
lib/automation_rules/conditions.json
Normal file
160
lib/automation_rules/conditions.json
Normal file
|
@ -0,0 +1,160 @@
|
||||||
|
{
|
||||||
|
"conversations": {
|
||||||
|
"status": {
|
||||||
|
"attribute_name": "Status",
|
||||||
|
"input_type": "multi_select",
|
||||||
|
"table_name": "conversations",
|
||||||
|
"filter_operators": [ "equal_to", "not_equal_to" ],
|
||||||
|
"attribute_type": "standard"
|
||||||
|
},
|
||||||
|
"assignee_id": {
|
||||||
|
"attribute_name": "Assignee Name",
|
||||||
|
"input_type": "search_box with name tags/plain text",
|
||||||
|
"table_name": "conversations",
|
||||||
|
"filter_operators": [ "equal_to", "not_equal_to", "contains", "does_not_contain", "is_present", "is_not_present" ],
|
||||||
|
"attribute_type": "standard"
|
||||||
|
},
|
||||||
|
"contact_id": {
|
||||||
|
"attribute_name": "Contact Name",
|
||||||
|
"input_type": "plain_text",
|
||||||
|
"table_name": "conversations",
|
||||||
|
"filter_operators": [ "equal_to", "not_equal_to", "contains", "does_not_contain", "is_present", "is_not_present" ],
|
||||||
|
"attribute_type": "standard"
|
||||||
|
},
|
||||||
|
"inbox_id": {
|
||||||
|
"attribute_name": "Inbox Name",
|
||||||
|
"input_type": "search_box",
|
||||||
|
"table_name": "conversations",
|
||||||
|
"filter_operators": [ "equal_to", "not_equal_to", "contains", "does_not_contain", "is_present", "is_not_present" ],
|
||||||
|
"attribute_type": "standard"
|
||||||
|
},
|
||||||
|
"team_id": {
|
||||||
|
"attribute_name": "Team Name",
|
||||||
|
"input_type": "search_box",
|
||||||
|
"table_name": "conversations",
|
||||||
|
"filter_operators": [ "equal_to", "not_equal_to", "contains", "does_not_contain", "is_present", "is_not_present" ],
|
||||||
|
"attribute_type": "standard"
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"attribute_name": "Conversation Identifier",
|
||||||
|
"input_type": "textbox",
|
||||||
|
"table_name": "conversations",
|
||||||
|
"filter_operators": [ "equal_to", "not_equal_to", "contains", "does_not_contain", "is_present", "is_not_present" ],
|
||||||
|
"attribute_type": "standard"
|
||||||
|
},
|
||||||
|
"campaign_id": {
|
||||||
|
"attribute_name": "Campaign Name",
|
||||||
|
"input_type": "textbox",
|
||||||
|
"data_type": "Number",
|
||||||
|
"filter_operators": [ "equal_to", "not_equal_to", "contains", "does_not_contain", "is_present", "is_not_present" ],
|
||||||
|
"attribute_type": "standard"
|
||||||
|
},
|
||||||
|
"labels": {
|
||||||
|
"attribute_name": "Labels",
|
||||||
|
"input_type": "tags",
|
||||||
|
"data_type": "text",
|
||||||
|
"filter_operators": ["exactly_equal_to", "contains", "does_not_contain" ],
|
||||||
|
"attribute_type": "standard"
|
||||||
|
},
|
||||||
|
"browser_language": {
|
||||||
|
"attribute_name": "Browser Language",
|
||||||
|
"input_type": "textbox",
|
||||||
|
"data_type": "text",
|
||||||
|
"filter_operators": [ "equal_to", "not_equal_to", "contains", "does_not_contain" ],
|
||||||
|
"attribute_type": "additional_attributes"
|
||||||
|
},
|
||||||
|
"country_code": {
|
||||||
|
"attribute_name": "Country Name",
|
||||||
|
"input_type": "textbox",
|
||||||
|
"data_type": "text",
|
||||||
|
"filter_operators": [ "equal_to", "not_equal_to", "contains", "does_not_contain", "present", "is_not_present" ],
|
||||||
|
"attribute_type": "additional_attributes"
|
||||||
|
},
|
||||||
|
"referer": {
|
||||||
|
"attribute_name": "Referer link",
|
||||||
|
"input_type": "textbox",
|
||||||
|
"data_type": "link",
|
||||||
|
"filter_operators": [ "equal_to", "not_equal_to", "contains", "does_not_contain", "present", "is_not_present" ],
|
||||||
|
"attribute_type": "additional_attributes"
|
||||||
|
},
|
||||||
|
"plan": {
|
||||||
|
"attribute_name": "Plan",
|
||||||
|
"input_type": "multi_select",
|
||||||
|
"data_type": "text",
|
||||||
|
"filter_operators": [ "equal_to", "not_equal_to", "contains", "does_not_contain", "present", "is_not_present" ],
|
||||||
|
"attribute_type": "additional_attributes"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"contacts": {
|
||||||
|
"assignee_id": {
|
||||||
|
"attribute_name": "Assignee Name",
|
||||||
|
"input_type": "search_box with name tags/plain text",
|
||||||
|
"data_type": "text",
|
||||||
|
"filter_operators": [ "equal_to", "not_equal_to", "contains", "does_not_contain", "is_present", "is_not_present" ],
|
||||||
|
"attribute_type": "standard"
|
||||||
|
},
|
||||||
|
"contact_id": {
|
||||||
|
"attribute_name": "Contact Name",
|
||||||
|
"input_type": "plain_text",
|
||||||
|
"data_type": "text",
|
||||||
|
"filter_operators": [ "equal_to", "not_equal_to", "contains", "does_not_contain", "is_present", "is_not_present" ],
|
||||||
|
"attribute_type": "standard"
|
||||||
|
},
|
||||||
|
"inbox_id": {
|
||||||
|
"attribute_name": "Inbox Name",
|
||||||
|
"input_type": "search_box",
|
||||||
|
"data_type": "text",
|
||||||
|
"filter_operators": [ "equal_to", "not_equal_to", "contains", "does_not_contain", "is_present", "is_not_present" ],
|
||||||
|
"attribute_type": "standard"
|
||||||
|
},
|
||||||
|
"team_id": {
|
||||||
|
"attribute_name": "Team Name",
|
||||||
|
"input_type": "search_box",
|
||||||
|
"data_type": "number",
|
||||||
|
"filter_operators": [ "equal_to", "not_equal_to", "contains", "does_not_contain", "is_present", "is_not_present" ],
|
||||||
|
"attribute_type": "standard"
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"attribute_name": "Conversation Identifier",
|
||||||
|
"input_type": "textbox",
|
||||||
|
"data_type": "Number",
|
||||||
|
"filter_operators": [ "equal_to", "not_equal_to", "contains", "does_not_contain", "is_present", "is_not_present" ],
|
||||||
|
"attribute_type": "standard"
|
||||||
|
},
|
||||||
|
"campaign_id": {
|
||||||
|
"attribute_name": "Campaign Name",
|
||||||
|
"input_type": "textbox",
|
||||||
|
"data_type": "Number",
|
||||||
|
"filter_operators": [ "equal_to", "not_equal_to", "contains", "does_not_contain", "is_present", "is_not_present" ],
|
||||||
|
"attribute_type": "standard"
|
||||||
|
},
|
||||||
|
"labels": {
|
||||||
|
"attribute_name": "Labels",
|
||||||
|
"input_type": "tags",
|
||||||
|
"data_type": "text",
|
||||||
|
"filter_operators": ["exactly_equal_to", "contains", "does_not_contain" ],
|
||||||
|
"attribute_type": "standard"
|
||||||
|
},
|
||||||
|
"browser_language": {
|
||||||
|
"attribute_name": "Browser Language",
|
||||||
|
"input_type": "textbox",
|
||||||
|
"data_type": "text",
|
||||||
|
"filter_operators": [ "equal_to", "not_equal_to", "contains", "does_not_contain" ],
|
||||||
|
"attribute_type": "additional_attributes"
|
||||||
|
},
|
||||||
|
"country_code": {
|
||||||
|
"attribute_name": "Country Name",
|
||||||
|
"input_type": "textbox",
|
||||||
|
"data_type": "text",
|
||||||
|
"filter_operators": [ "equal_to", "not_equal_to", "contains", "does_not_contain", "present", "is_not_present" ],
|
||||||
|
"attribute_type": "additional_attributes"
|
||||||
|
},
|
||||||
|
"referer": {
|
||||||
|
"attribute_name": "Referer link",
|
||||||
|
"input_type": "textbox",
|
||||||
|
"data_type": "link",
|
||||||
|
"filter_operators": [ "equal_to", "not_equal_to", "contains", "does_not_contain", "present", "is_not_present" ],
|
||||||
|
"attribute_type": "additional_attributes"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,118 @@
|
||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
RSpec.describe 'Api::V1::Accounts::AutomationRulesController', type: :request do
|
||||||
|
let(:account) { create(:account) }
|
||||||
|
let(:administrator) { create(:user, account: account, role: :administrator) }
|
||||||
|
let!(:inbox) { create(:inbox, account: account, enable_auto_assignment: false) }
|
||||||
|
let!(:contact) { create(:contact, account: account) }
|
||||||
|
let(:contact_inbox) { create(:contact_inbox, inbox_id: inbox.id, contact_id: contact.id) }
|
||||||
|
|
||||||
|
describe 'GET /api/v1/accounts/{account.id}/automation_rules' do
|
||||||
|
context 'when it is an authenticated user' do
|
||||||
|
it 'returns all records' do
|
||||||
|
automation_rule = create(:automation_rule, account: account, name: 'Test Automation Rule')
|
||||||
|
|
||||||
|
get "/api/v1/accounts/#{account.id}/automation_rules",
|
||||||
|
headers: administrator.create_new_auth_token
|
||||||
|
|
||||||
|
expect(response).to have_http_status(:success)
|
||||||
|
body = JSON.parse(response.body, symbolize_names: true)
|
||||||
|
expect(body[:data].first[:id]).to eq(automation_rule.id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when it is an unauthenticated user' do
|
||||||
|
it 'returns unauthorized' do
|
||||||
|
post "/api/v1/accounts/#{account.id}/automation_rules"
|
||||||
|
|
||||||
|
expect(response).to have_http_status(:unauthorized)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'POST /api/v1/accounts/{account.id}/automation_rules' do
|
||||||
|
context 'when it is an unauthenticated user' do
|
||||||
|
it 'returns unauthorized' do
|
||||||
|
post "/api/v1/accounts/#{account.id}/automation_rules"
|
||||||
|
|
||||||
|
expect(response).to have_http_status(:unauthorized)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when it is an authenticated user' do
|
||||||
|
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',
|
||||||
|
conditions: [
|
||||||
|
{
|
||||||
|
attribute_key: 'browser_language',
|
||||||
|
filter_operator: 'equal_to',
|
||||||
|
values: ['en'],
|
||||||
|
query_operator: 'AND'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
attribute_key: 'country_code',
|
||||||
|
filter_operator: 'equal_to',
|
||||||
|
values: %w[USA UK],
|
||||||
|
query_operator: nil
|
||||||
|
}
|
||||||
|
],
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
action_name: :send_message,
|
||||||
|
action_params: ['Welcome to the chatwoot platform.']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
action_name: :assign_team,
|
||||||
|
action_params: [1]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
action_name: :add_label,
|
||||||
|
action_params: %w[support priority_customer]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
action_name: :assign_best_administrator,
|
||||||
|
action_params: [1]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
action_name: :update_additional_attributes,
|
||||||
|
action_params: [{ intiated_at: '2021-12-03 17:25:26.844536 +0530' }]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}.with_indifferent_access
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'Saves for automation_rules for account with country_code and browser_language conditions' do
|
||||||
|
expect(account.automation_rules.count).to eq(0)
|
||||||
|
|
||||||
|
post "/api/v1/accounts/#{account.id}/automation_rules",
|
||||||
|
headers: administrator.create_new_auth_token,
|
||||||
|
params: params
|
||||||
|
|
||||||
|
expect(response).to have_http_status(:success)
|
||||||
|
expect(account.automation_rules.count).to eq(1)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'Saves for automation_rules for account with status conditions' do
|
||||||
|
params[:conditions] = [
|
||||||
|
{
|
||||||
|
attribute_key: 'status',
|
||||||
|
filter_operator: 'equal_to',
|
||||||
|
values: ['resolved'],
|
||||||
|
query_operator: nil
|
||||||
|
}.with_indifferent_access
|
||||||
|
]
|
||||||
|
expect(account.automation_rules.count).to eq(0)
|
||||||
|
|
||||||
|
post "/api/v1/accounts/#{account.id}/automation_rules",
|
||||||
|
headers: administrator.create_new_auth_token,
|
||||||
|
params: params
|
||||||
|
|
||||||
|
expect(response).to have_http_status(:success)
|
||||||
|
expect(account.automation_rules.count).to eq(1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
19
spec/factories/automation_rules.rb
Normal file
19
spec/factories/automation_rules.rb
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
FactoryBot.define do
|
||||||
|
factory :automation_rule do
|
||||||
|
account
|
||||||
|
event_name { 'conversation_status_changed' }
|
||||||
|
conditions { [{ 'values': ['resolved'], 'attribute_key': 'status', 'query_operator': nil, 'filter_operator': 'equal_to' }] }
|
||||||
|
actions do
|
||||||
|
[
|
||||||
|
{
|
||||||
|
'action_name' => 'send_email_to_team', 'action_params' => {
|
||||||
|
'message' => 'Please pay attention to this conversation, its from high priority customer', 'team_ids' => [1]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ 'action_name' => 'assign_team', 'action_params' => [1] },
|
||||||
|
{ 'action_name' => 'add_label', 'action_params' => %w[support priority_customer] },
|
||||||
|
{ 'action_name' => 'assign_best_agents', 'action_params' => [1, 2, 3, 4] }
|
||||||
|
]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
72
spec/listeners/automation_rule_listener_spec.rb
Normal file
72
spec/listeners/automation_rule_listener_spec.rb
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
require 'rails_helper'
|
||||||
|
describe AutomationRuleListener do
|
||||||
|
let(:listener) { described_class.instance }
|
||||||
|
let(:account) { create(:account) }
|
||||||
|
let(:inbox) { create(:inbox, account: account) }
|
||||||
|
let(:contact) { create(:contact, account: account, identifier: '123') }
|
||||||
|
let(:contact_inbox) { create(:contact_inbox, contact: contact, inbox: inbox) }
|
||||||
|
let(:conversation) { create(:conversation, contact_inbox: contact_inbox, inbox: inbox, account: account) }
|
||||||
|
let(:automation_rule) { create(:automation_rule, account: account, name: 'Test Automation Rule') }
|
||||||
|
let(:team) { create(:team, account: account) }
|
||||||
|
let(:user_1) { create(:user, role: 0) }
|
||||||
|
let(:user_2) { create(:user, role: 0) }
|
||||||
|
let!(:event) do
|
||||||
|
Events::Base.new('conversation_status_changed', Time.zone.now, { conversation: conversation })
|
||||||
|
end
|
||||||
|
|
||||||
|
before do
|
||||||
|
create(:team_member, user: user_1, team: team)
|
||||||
|
create(:team_member, user: user_2, team: team)
|
||||||
|
create(:account_user, user: user_2, account: account)
|
||||||
|
create(:account_user, user: user_1, account: account)
|
||||||
|
|
||||||
|
conversation.resolved!
|
||||||
|
automation_rule.update!(actions:
|
||||||
|
[
|
||||||
|
{
|
||||||
|
'action_name' => 'send_email_to_team', 'action_params' => {
|
||||||
|
'message' => 'Please pay attention to this conversation, its from high priority customer',
|
||||||
|
'team_ids' => [team.id]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ '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] }
|
||||||
|
])
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#conversation_status_changed' do
|
||||||
|
context 'when rule matches' do
|
||||||
|
it 'triggers automation rule to assign team' do
|
||||||
|
expect(conversation.team_id).not_to eq(team.id)
|
||||||
|
|
||||||
|
automation_rule
|
||||||
|
listener.conversation_status_changed(event)
|
||||||
|
|
||||||
|
conversation.reload
|
||||||
|
expect(conversation.team_id).to eq(team.id)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'triggers automation rule to add label' do
|
||||||
|
expect(conversation.labels).to eq([])
|
||||||
|
|
||||||
|
automation_rule
|
||||||
|
listener.conversation_status_changed(event)
|
||||||
|
|
||||||
|
conversation.reload
|
||||||
|
expect(conversation.labels.pluck(:name)).to eq(%w[support priority_customer])
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'triggers automation rule to assign best agents' do
|
||||||
|
expect(conversation.assignee).to be_nil
|
||||||
|
|
||||||
|
automation_rule
|
||||||
|
listener.conversation_status_changed(event)
|
||||||
|
|
||||||
|
conversation.reload
|
||||||
|
|
||||||
|
expect(conversation.assignee).to eq(user_1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
54
spec/models/automation_rule_spec.rb
Normal file
54
spec/models/automation_rule_spec.rb
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
RSpec.describe AutomationRule, type: :model do
|
||||||
|
describe 'associations' do
|
||||||
|
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',
|
||||||
|
conditions: [
|
||||||
|
{
|
||||||
|
attribute_key: 'browser_language',
|
||||||
|
filter_operator: 'equal_to',
|
||||||
|
values: ['en'],
|
||||||
|
query_operator: 'AND'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
attribute_key: 'country_code',
|
||||||
|
filter_operator: 'equal_to',
|
||||||
|
values: %w[USA UK],
|
||||||
|
query_operator: nil
|
||||||
|
}
|
||||||
|
],
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
action_name: :send_message,
|
||||||
|
action_params: ['Welcome to the chatwoot platform.']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
action_name: :assign_team,
|
||||||
|
action_params: [1]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
action_name: :add_label,
|
||||||
|
action_params: %w[support priority_customer]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
action_name: :assign_best_administrator,
|
||||||
|
action_params: [1]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
action_name: :update_additional_attributes,
|
||||||
|
action_params: [{ intiated_at: '2021-12-03 17:25:26.844536 +0530' }]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}.with_indifferent_access
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns valid record' do
|
||||||
|
rule = FactoryBot.build(:automation_rule, params)
|
||||||
|
expect(rule.valid?).to eq true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in a new issue