From 96b5780ea7d05a61ba0e16d6e3ba9bfb14c42a79 Mon Sep 17 00:00:00 2001 From: Muhsin Keloth Date: Fri, 16 Jan 2026 10:16:00 +0400 Subject: [PATCH] fix: Respect survey label rules for WhatsApp CSAT template (#13285) Ensure CSAT survey label rules are evaluated once in CsatSurveyService before any channel-specific sending (including WhatsApp/Twilio templates), remove the duplicated rule check from the template builder, and cover the blocking-label scenario in service specs while simplifying the template specs accordingly. Co-authored-by: Sojan Jose --- app/services/csat_survey_service.rb | 37 ++++++++- .../message_templates/template/csat_survey.rb | 35 -------- spec/services/csat_survey_service_spec.rb | 42 ++++++++++ .../template/csat_survey_spec.rb | 79 +++---------------- 4 files changed, 88 insertions(+), 105 deletions(-) diff --git a/app/services/csat_survey_service.rb b/app/services/csat_survey_service.rb index 6c8a288b8..cc38b820b 100644 --- a/app/services/csat_survey_service.rb +++ b/app/services/csat_survey_service.rb @@ -20,7 +20,7 @@ class CsatSurveyService delegate :inbox, :contact, to: :conversation def should_send_csat_survey? - conversation_allows_csat? && csat_enabled? && !csat_already_sent? + conversation_allows_csat? && csat_enabled? && !csat_already_sent? && csat_allowed_by_survey_rules? end def conversation_allows_csat? @@ -39,6 +39,37 @@ class CsatSurveyService conversation.can_reply? end + def csat_allowed_by_survey_rules? + return true unless survey_rules_configured? + + labels = conversation.label_list + return true if rule_values.empty? + + case rule_operator + when 'contains' + rule_values.any? { |label| labels.include?(label) } + when 'does_not_contain' + rule_values.none? { |label| labels.include?(label) } + else + true + end + end + + def survey_rules_configured? + return false if csat_config.blank? + return false if csat_config['survey_rules'].blank? + + rule_values.any? + end + + def rule_operator + csat_config.dig('survey_rules', 'operator') || 'contains' + end + + def rule_values + csat_config.dig('survey_rules', 'values') || [] + end + def whatsapp_channel? inbox.channel_type == 'Channel::Whatsapp' end @@ -113,6 +144,10 @@ class CsatSurveyService ) end + def csat_config + inbox.csat_config || {} + end + def send_twilio_whatsapp_template_survey template_config = inbox.csat_config&.dig('template') content_sid = template_config['content_sid'] diff --git a/app/services/message_templates/template/csat_survey.rb b/app/services/message_templates/template/csat_survey.rb index dd9cf3bd6..4fcef3e87 100644 --- a/app/services/message_templates/template/csat_survey.rb +++ b/app/services/message_templates/template/csat_survey.rb @@ -2,8 +2,6 @@ class MessageTemplates::Template::CsatSurvey pattr_initialize [:conversation!] def perform - return unless should_send_csat_survey? - ActiveRecord::Base.transaction do conversation.messages.create!(csat_survey_message_params) end @@ -13,39 +11,6 @@ class MessageTemplates::Template::CsatSurvey delegate :contact, :account, :inbox, to: :conversation - def should_send_csat_survey? - return true unless survey_rules_configured? - - labels = conversation.label_list - - return true if rule_values.empty? - - case rule_operator - when 'contains' - rule_values.any? { |label| labels.include?(label) } - when 'does_not_contain' - rule_values.none? { |label| labels.include?(label) } - else - true - end - end - - def survey_rules_configured? - return false if csat_config.blank? - return false if csat_config['survey_rules'].blank? - return false if rule_values.empty? - - true - end - - def rule_operator - csat_config.dig('survey_rules', 'operator') || 'contains' - end - - def rule_values - csat_config.dig('survey_rules', 'values') || [] - end - def message_content return I18n.t('conversations.templates.csat_input_message_body') if csat_config.blank? || csat_config['message'].blank? diff --git a/spec/services/csat_survey_service_spec.rb b/spec/services/csat_survey_service_spec.rb index 6359fbda1..5a62e32e5 100644 --- a/spec/services/csat_survey_service_spec.rb +++ b/spec/services/csat_survey_service_spec.rb @@ -88,6 +88,25 @@ describe CsatSurveyService do expect(MessageTemplates::Template::CsatSurvey).not_to have_received(:new) expect(Conversations::ActivityMessageJob).not_to have_received(:perform_later) end + + context 'when survey rules block sending' do + before do + inbox.update(csat_config: { + 'survey_rules' => { + 'operator' => 'does_not_contain', + 'values' => ['bot-detectado'] + } + }) + conversation.update(label_list: ['bot-detectado']) + end + + it 'does not send CSAT' do + service.perform + + expect(MessageTemplates::Template::CsatSurvey).not_to have_received(:new) + expect(conversation.messages.where(content_type: :input_csat)).to be_empty + end + end end context 'when it is a WhatsApp channel' do @@ -306,6 +325,29 @@ describe CsatSurveyService do expect(MessageTemplates::Template::CsatSurvey).not_to have_received(:new) end end + + context 'when survey rules block sending' do + before do + whatsapp_inbox.update(csat_config: { + 'template' => { 'name' => 'customer_survey_template', 'language' => 'en' }, + 'message' => 'Please rate your experience', + 'survey_rules' => { + 'operator' => 'does_not_contain', + 'values' => ['bot-detectado'] + } + }) + whatsapp_conversation.update(label_list: ['bot-detectado']) + end + + it 'does not call WhatsApp template or create a CSAT message' do + expect(mock_provider_service).not_to receive(:get_template_status) + expect(mock_provider_service).not_to receive(:send_template) + + whatsapp_service.perform + + expect(whatsapp_conversation.messages.where(content_type: :input_csat)).to be_empty + end + end end end diff --git a/spec/services/message_templates/template/csat_survey_spec.rb b/spec/services/message_templates/template/csat_survey_spec.rb index a2cae684b..837a30012 100644 --- a/spec/services/message_templates/template/csat_survey_spec.rb +++ b/spec/services/message_templates/template/csat_survey_spec.rb @@ -17,83 +17,24 @@ describe MessageTemplates::Template::CsatSurvey do expect(conversation.messages.template.first.content_type).to eq('input_csat') end end - end - describe '#perform with contains operator' do - let(:csat_config) do - { - 'display_type' => 'emoji', - 'message' => 'Please rate your experience', - 'survey_rules' => { - 'operator' => 'contains', - 'values' => %w[support help] + context 'when csat config is provided' do + let(:csat_config) do + { + 'display_type' => 'star', + 'message' => 'Please rate your experience' } - } - end + end - before do - inbox.update(csat_config: csat_config) - end - - context 'when conversation has matching labels' do - it 'creates a CSAT survey message' do - conversation.update(label_list: %w[support urgent]) + before { inbox.update(csat_config: csat_config) } + it 'creates a CSAT message with configured attributes' do service.perform - expect(conversation.messages.template.count).to eq(1) - message = conversation.messages.template.first + message = conversation.messages.template.last expect(message.content_type).to eq('input_csat') expect(message.content).to eq('Please rate your experience') - expect(message.content_attributes['display_type']).to eq('emoji') - end - end - - context 'when conversation has no matching labels' do - it 'does not create a CSAT survey message' do - conversation.update(label_list: %w[billing-support payment]) - - service.perform - - expect(conversation.messages.template.count).to eq(0) - end - end - end - - describe '#perform with does_not_contain operator' do - let(:csat_config) do - { - 'display_type' => 'emoji', - 'message' => 'Please rate your experience', - 'survey_rules' => { - 'operator' => 'does_not_contain', - 'values' => %w[support help] - } - } - end - - before do - inbox.update(csat_config: csat_config) - end - - context 'when conversation does not have matching labels' do - it 'creates a CSAT survey message' do - conversation.update(label_list: %w[billing payment]) - - service.perform - - expect(conversation.messages.template.count).to eq(1) - expect(conversation.messages.template.first.content_type).to eq('input_csat') - end - end - - context 'when conversation has matching labels' do - it 'does not create a CSAT survey message' do - conversation.update(label_list: %w[support urgent]) - - service.perform - - expect(conversation.messages.template.count).to eq(0) + expect(message.content_attributes['display_type']).to eq('star') end end end