From 2ea08824bda41a633dd09cc2a358c35f2b9311be Mon Sep 17 00:00:00 2001 From: Caio Almeida <117518+caiosba@users.noreply.github.com> Date: Mon, 9 Oct 2023 12:18:25 -0300 Subject: [PATCH] Individual feedback for tipline search results on WhatsApp For WhatsApp tiplines, instead of asking for feedback for all the results, ask for feedback individually by appending a thumbs-up button to each result. The timeout case is also individual as part of this change. Reference: CV2-3414. --- app/models/bot/smooch.rb | 5 ++ app/models/concerns/smooch_menus.rb | 8 ++- app/models/concerns/smooch_search.rb | 84 ++++++++++++++++++++++++---- test/models/bot/smooch_6_test.rb | 49 ++++++++++++++++ 4 files changed, 135 insertions(+), 11 deletions(-) diff --git a/app/models/bot/smooch.rb b/app/models/bot/smooch.rb index 5c5220305a..3403e33020 100644 --- a/app/models/bot/smooch.rb +++ b/app/models/bot/smooch.rb @@ -381,6 +381,11 @@ def self.parse_message_based_on_state(message, app_id) return true end + if self.clicked_on_search_result_button?(message) + self.search_result_button_click_callback(message, uid, app_id, workflow, language) + return true + end + case state when 'waiting_for_message' self.bundle_message(message) diff --git a/app/models/concerns/smooch_menus.rb b/app/models/concerns/smooch_menus.rb index 755e40d155..7f0469508b 100644 --- a/app/models/concerns/smooch_menus.rb +++ b/app/models/concerns/smooch_menus.rb @@ -166,7 +166,7 @@ def get_custom_string(key, language, truncate_at = 1024) label.to_s.truncate(truncate_at) end - def send_message_to_user_with_buttons(uid, text, options) + def send_message_to_user_with_buttons(uid, text, options, image_url = nil) buttons = [] options.each_with_index do |option, i| buttons << { @@ -196,6 +196,12 @@ def send_message_to_user_with_buttons(uid, text, options) } } } + extra[:override][:whatsapp][:payload][:interactive][:header] = { + type: 'image', + image: { + link: CheckS3.rewrite_url(image_url) + } + } unless image_url.blank? extra, fallback = self.format_fallback_text_menu_from_options(text, options, extra) self.send_message_to_user(uid, fallback.join("\n"), extra) end diff --git a/app/models/concerns/smooch_search.rb b/app/models/concerns/smooch_search.rb index cbd339bd3a..d442c925df 100644 --- a/app/models/concerns/smooch_search.rb +++ b/app/models/concerns/smooch_search.rb @@ -19,10 +19,15 @@ def search(app_id, uid, language, message, team_id, workflow) self.bundle_messages(uid, '', app_id, 'default_requests', nil, true) self.send_final_message_to_user(uid, self.get_custom_string('search_no_results', language), workflow, language) else - self.send_search_results_to_user(uid, results, team_id) - sm.go_to_search_result - self.save_search_results_for_user(uid, results.map(&:id)) - self.delay_for(1.second, { queue: 'smooch_priority' }).ask_for_feedback_when_all_search_results_are_received(app_id, language, workflow, uid, platform, 1) + self.send_search_results_to_user(uid, results, team_id, platform, app_id) + # For WhatsApp, each search result goes with a button where the user can give feedback individually, so, reset the conversation right away + if platform == 'WhatsApp' + sm.reset + else + sm.go_to_search_result + self.save_search_results_for_user(uid, results.map(&:id)) + self.delay_for(1.second, { queue: 'smooch_priority' }).ask_for_feedback_when_all_search_results_are_received(app_id, language, workflow, uid, platform, 1) + end end rescue StandardError => e self.handle_search_error(uid, e, language) @@ -214,21 +219,51 @@ def search_by_keywords_for_similar_published_fact_checks(words, after, team_ids, results end - def send_search_results_to_user(uid, results, team_id) + def send_search_results_to_user(uid, results, team_id, platform, app_id) team = Team.find(team_id) - redis = Redis.new(REDIS_CONFIG) language = self.get_user_language(uid) - reports = results.collect{ |r| r.get_dynamic_annotation('report_design') } + reports = results.collect{ |r| r.get_dynamic_annotation('report_design') }.reject{ |r| r.blank? } # Get reports languages - reports_language = reports.map{|r| r&.report_design_field_value('language')}.uniq + reports_language = reports.map{ |r| r.report_design_field_value('language') }.uniq if team.get_languages.to_a.size > 1 && !reports_language.include?(language) self.send_message_to_user(uid, self.get_string(:no_results_in_language, language).gsub('%{language}', CheckCldr.language_code_to_name(language, language))) sleep 1 end + if platform == 'WhatsApp' + self.send_search_results_to_whatsapp_user(uid, reports, app_id) + else + self.send_search_results_to_non_whatsapp_user(uid, reports) + end + end + + def generate_search_id + SecureRandom.hex + end + + def send_search_results_to_whatsapp_user(uid, reports, app_id) + search_id = self.generate_search_id + # Cache the current bundle of messages from this user related to this search, so a request can be created correctly + # Expires after the time to give feedback is expired + Rails.cache.write("smooch:user_search_bundle:#{uid}:#{search_id}", self.list_of_bundled_messages_from_user(uid), expires_in: 20.minutes) + self.clear_user_bundled_messages(uid) + reports.each do |report| + text = report.report_design_text if report.report_design_field_value('use_text_message') + image_url = report.report_design_image_url if report.report_design_field_value('use_visual_card') + options = [{ + value: { project_media_id: report.annotated_id, keyword: 'search_result_is_relevant', search_id: search_id }.to_json, + label: '👍' + }] + self.send_message_to_user_with_buttons(uid, text || '-', options, image_url) # "text" is mandatory for WhatsApp interactive messages + self.delay_for(15.minutes, { queue: 'smooch_priority' }).timeout_if_no_feedback_is_given_to_search_result(app_id, uid, search_id, report.annotated_id) + end + end + + def send_search_results_to_non_whatsapp_user(uid, reports) + redis = Redis.new(REDIS_CONFIG) reports.each do |report| response = nil - response = self.send_message_to_user(uid, report.report_design_text) if report&.report_design_field_value('use_text_message') - response = self.send_message_to_user(uid, '', { 'type' => 'image', 'mediaUrl' => report.report_design_image_url }) if !report&.report_design_field_value('use_text_message') && report&.report_design_field_value('use_visual_card') + response = self.send_message_to_user(uid, report.report_design_text) if report.report_design_field_value('use_text_message') + response = self.send_message_to_user(uid, '', { 'type' => 'image', 'mediaUrl' => report.report_design_image_url }) if !report.report_design_field_value('use_text_message') && report.report_design_field_value('use_visual_card') id = self.get_id_from_send_response(response) redis.rpush("smooch:search:#{uid}", id) unless id.blank? end @@ -253,5 +288,34 @@ def ask_for_feedback_when_all_search_results_are_received(app_id, language, work self.delay_for(1.second, { queue: 'smooch_priority' }).ask_for_feedback_when_all_search_results_are_received(app_id, language, workflow, uid, platform, attempts + 1) if attempts < max # Try for 20 seconds end end + + def timeout_if_no_feedback_is_given_to_search_result(app_id, uid, search_id, pmid) + key = "smooch:user_search_bundle:#{uid}:#{search_id}:#{pmid}" + if Rails.cache.read(key).nil? # User gave no feedback for the search result + bundle = Rails.cache.read("smooch:user_search_bundle:#{uid}:#{search_id}").to_a + self.delay_for(1.seconds, { queue: 'smooch', retry: false }).bundle_messages(uid, nil, app_id, 'timeout_search_requests', [ProjectMedia.find(pmid)], true, bundle) + else + Rails.cache.delete(key) # User gave feedback to search result + end + end + + def clicked_on_search_result_button?(message) + begin + JSON.parse(message['payload'])['keyword'] == 'search_result_is_relevant' + rescue + false + end + end + + def search_result_button_click_callback(message, uid, app_id, workflow, language) + payload = JSON.parse(message['payload']) + result = ProjectMedia.find(payload['project_media_id']) + bundle = Rails.cache.read("smooch:user_search_bundle:#{uid}:#{payload['search_id']}").to_a + unless bundle.empty? + Rails.cache.write("smooch:user_search_bundle:#{uid}:#{payload['search_id']}:#{result.id}", Time.now.to_i) # Store that the user has given feedback to this search result + self.delay_for(1.seconds, { queue: 'smooch', retry: false }).bundle_messages(uid, message['_id'], app_id, 'relevant_search_result_requests', [result], true, bundle) + self.send_final_message_to_user(uid, self.get_custom_string('search_result_is_relevant', language), workflow, language) + end + end end end diff --git a/test/models/bot/smooch_6_test.rb b/test/models/bot/smooch_6_test.rb index 5f66ead55c..4d98d7ba06 100644 --- a/test/models/bot/smooch_6_test.rb +++ b/test/models/bot/smooch_6_test.rb @@ -880,4 +880,53 @@ def send_message_outside_24_hours_window(template, pm = nil) end end end + + test 'should generate a unique ID for search results' do + assert_not_equal Bot::Smooch.generate_search_id, Bot::Smooch.generate_search_id + end + + test 'should give individual feedback to search result on WhatsApp' do + search_id = random_string + Bot::Smooch.stubs(:generate_search_id).returns(search_id) + pm1 = create_project_media(team: @team) + pm2 = create_project_media(team: @team) + publish_report(pm1, {}, nil, { language: 'en', use_visual_card: true }) + publish_report(pm2, {}, nil, { language: 'en', use_visual_card: false }) + CheckSearch.any_instance.stubs(:medias).returns([pm1, pm2]) + Sidekiq::Testing.inline! do + send_message 'hello', '1', '1', random_string + end + assert_state 'ask_if_ready' + Rails.cache.write("smooch:user_search_bundle:#{@uid}:#{search_id}:#{pm1.id}", Time.now.to_i) # User gave feedback to one result + Sidekiq::Testing.fake! do + send_message_to_smooch_bot('1', @uid, { 'source' => { 'type' => 'whatsapp' } }) + end + assert_difference 'DynamicAnnotation::Field.where(field_name: "smooch_request_type", value: "timeout_search_requests").count' do + Sidekiq::Worker.drain_all + end + end + + test 'should click on button to evaluate search result on WhatsApp' do + search_id = random_string + pm = create_project_media(team: @team) + bundle = [ + { + '_id': random_string, + authorId: @uid, + type: 'text', + text: random_string + }.to_json + ] + Rails.cache.write("smooch:user_search_bundle:#{@uid}:#{search_id}", bundle) + Sidekiq::Testing.inline! do + assert_difference 'DynamicAnnotation::Field.where(field_name: "smooch_request_type", value: "relevant_search_result_requests").count' do + payload = { + 'keyword' => 'search_result_is_relevant', + 'project_media_id' => pm.id, + 'search_id' => search_id + } + send_message_to_smooch_bot('Thumbs up', @uid, { 'source' => { 'type' => 'whatsapp' }, 'payload' => payload.to_json }) + end + end + end end