diff --git a/app/controllers/api/v1/accounts/automation_rules_controller.rb b/app/controllers/api/v1/accounts/automation_rules_controller.rb index 5e649b6e0..78cd15e31 100644 --- a/app/controllers/api/v1/accounts/automation_rules_controller.rb +++ b/app/controllers/api/v1/accounts/automation_rules_controller.rb @@ -9,6 +9,7 @@ class Api::V1::Accounts::AutomationRulesController < Api::V1::Accounts::BaseCont def create @automation_rule = Current.account.automation_rules.new(automation_rules_permit) @automation_rule.actions = params[:actions] + @automation_rule.conditions = params[:conditions] render json: { error: @automation_rule.errors.messages }, status: :unprocessable_entity and return unless @automation_rule.valid? @@ -31,9 +32,7 @@ class Api::V1::Accounts::AutomationRulesController < Api::V1::Accounts::BaseCont def update ActiveRecord::Base.transaction do - @automation_rule.update!(automation_rules_permit) - @automation_rule.actions = params[:actions] if params[:actions] - @automation_rule.save! + automation_rule_update process_attachments rescue StandardError => e @@ -67,6 +66,13 @@ class Api::V1::Accounts::AutomationRulesController < Api::V1::Accounts::BaseCont private + def automation_rule_update + @automation_rule.update!(automation_rules_permit) + @automation_rule.actions = params[:actions] if params[:actions] + @automation_rule.conditions = params[:conditions] if params[:conditions] + @automation_rule.save! + end + def automation_rules_permit params.permit( :name, :description, :event_name, :account_id, :active, diff --git a/app/listeners/automation_rule_listener.rb b/app/listeners/automation_rule_listener.rb index 0f445fea7..f778dcd69 100644 --- a/app/listeners/automation_rule_listener.rb +++ b/app/listeners/automation_rule_listener.rb @@ -4,11 +4,12 @@ class AutomationRuleListener < BaseListener conversation = event_obj.data[:conversation] account = conversation.account + changed_attributes = event_obj.data[:changed_attributes] return unless rule_present?('conversation_updated', account) @rules.each do |rule| - conditions_match = ::AutomationRules::ConditionsFilterService.new(rule, conversation).perform + conditions_match = ::AutomationRules::ConditionsFilterService.new(rule, conversation, { changed_attributes: changed_attributes }).perform AutomationRules::ActionService.new(rule, account, conversation).perform if conditions_match.present? end end @@ -18,11 +19,12 @@ class AutomationRuleListener < BaseListener conversation = event_obj.data[:conversation] account = conversation.account + changed_attributes = event_obj.data[:changed_attributes] return unless rule_present?('conversation_created', account) @rules.each do |rule| - conditions_match = ::AutomationRules::ConditionsFilterService.new(rule, conversation).perform + conditions_match = ::AutomationRules::ConditionsFilterService.new(rule, conversation, { changed_attributes: changed_attributes }).perform ::AutomationRules::ActionService.new(rule, account, conversation).perform if conditions_match.present? end end @@ -32,11 +34,13 @@ class AutomationRuleListener < BaseListener message = event_obj.data[:message] account = message.try(:account) + changed_attributes = event_obj.data[:changed_attributes] return unless rule_present?('message_created', account) @rules.each do |rule| - conditions_match = ::AutomationRules::ConditionsFilterService.new(rule, message.conversation, { message: message }).perform + conditions_match = ::AutomationRules::ConditionsFilterService.new(rule, message.conversation, + { message: message, changed_attributes: changed_attributes }).perform ::AutomationRules::ActionService.new(rule, account, message.conversation).perform if conditions_match.present? end end diff --git a/app/services/automation_rules/conditions_filter_service.rb b/app/services/automation_rules/conditions_filter_service.rb index 67cd2a62c..e32a95751 100644 --- a/app/services/automation_rules/conditions_filter_service.rb +++ b/app/services/automation_rules/conditions_filter_service.rb @@ -11,18 +11,24 @@ class AutomationRules::ConditionsFilterService < FilterService file = File.read('./lib/filters/filter_keys.json') @filters = JSON.parse(file) @options = options + @changed_attributes = options[:changed_attributes] end def perform @conversation_filters = @filters['conversations'] @contact_filters = @filters['contacts'] @message_filters = @filters['messages'] + @attribute_changed_query_filter = [] @rule.conditions.each_with_index do |query_hash, current_index| + @attribute_changed_query_filter << query_hash and next if query_hash['filter_operator'] == 'attribute_changed' + apply_filter(query_hash, current_index) end records = base_relation.where(@query_string, @filter_values.with_indifferent_access) + records = perform_attribute_changed_filter(records) if @attribute_changed_query_filter.any? + records.any? end @@ -43,6 +49,37 @@ class AutomationRules::ConditionsFilterService < FilterService end end + # If attribute_changed type filter is present perform this against array + def perform_attribute_changed_filter(records) + @attribute_changed_records = [] + current_attribute_changed_record = base_relation + filter_based_on_attribute_change(records, current_attribute_changed_record) + + @attribute_changed_records.uniq + end + + # Loop through attribute_changed_query_filter + def filter_based_on_attribute_change(records, current_attribute_changed_record) + @attribute_changed_query_filter.each do |filter| + @changed_attributes = @changed_attributes.with_indifferent_access + changed_attribute = @changed_attributes[filter['attribute_key']].presence + + if changed_attribute[0].in?(filter['values']['from']) && changed_attribute[1].in?(filter['values']['to']) + @attribute_changed_records = attribute_changed_filter_query(filter, records, current_attribute_changed_record) + end + current_attribute_changed_record = @attribute_changed_records + end + end + + # We intersect with the record if query_operator-AND is present and union if query_operator-OR is present + def attribute_changed_filter_query(filter, records, current_attribute_changed_record) + if filter['query_operator'] == 'AND' + @attribute_changed_records + (current_attribute_changed_record & records) + else + @attribute_changed_records + (current_attribute_changed_record | records) + end + end + def message_query_string(current_filter, query_hash, current_index) attribute_key = query_hash['attribute_key'] query_operator = query_hash['query_operator'] diff --git a/spec/listeners/automation_rule_listener_spec.rb b/spec/listeners/automation_rule_listener_spec.rb index 6ab931554..8f4213137 100644 --- a/spec/listeners/automation_rule_listener_spec.rb +++ b/spec/listeners/automation_rule_listener_spec.rb @@ -259,6 +259,101 @@ describe AutomationRuleListener do expect(conversation.messages.first.content).to eq('Send this message.') end end + + context 'when conditions based on attribute_changed' do + before do + automation_rule.update!( + event_name: 'conversation_updated', + name: 'Call actions conversation updated when company changed from DC to Marvel', + description: 'Add labels, assign team after conversation updated', + conditions: [ + { + attribute_key: 'company', + filter_operator: 'attribute_changed', + values: { from: ['DC'], to: ['Marvel'] }, + query_operator: 'AND' + }.with_indifferent_access, + { + attribute_key: 'status', + filter_operator: 'equal_to', + values: ['snoozed'], + query_operator: nil + }.with_indifferent_access + ] + ) + conversation.update(status: :snoozed) + end + + let!(:event) do + Events::Base.new('conversation_updated', Time.zone.now, { conversation: conversation, changed_attributes: { + company: %w[DC Marvel] + } }) + end + + context 'when rule matches' 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 assign team with OR operator' do + conversation.update(status: :open) + automation_rule.update!( + conditions: [ + { + attribute_key: 'company', + filter_operator: 'attribute_changed', + values: { from: ['DC'], to: ['Marvel'] }, + query_operator: 'OR' + }.with_indifferent_access, + { + attribute_key: 'status', + filter_operator: 'equal_to', + values: ['snoozed'], + 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 + + context 'when rule doesnt match' do + it 'when automation rule is triggered it will not assign team' do + conversation.update(status: :open) + + expect(conversation.team_id).not_to eq(team.id) + + listener.conversation_updated(event) + + conversation.reload + expect(conversation.team_id).not_to eq(team.id) + end + + it 'when automation rule is triggers, it will not assign team on attribute_changed values' do + conversation.update(status: :snoozed) + event = Events::Base.new('conversation_updated', Time.zone.now, { conversation: conversation, + changed_attributes: { company: %w[Marvel DC] } }) + + expect(conversation.team_id).not_to eq(team.id) + + listener.conversation_updated(event) + + conversation.reload + expect(conversation.team_id).not_to eq(team.id) + end + end + end end describe '#message_created' do