Skip to content

Commit

Permalink
Individual feedback for tipline search results on WhatsApp
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
caiosba authored Oct 9, 2023
1 parent c92ecc7 commit 2ea0882
Show file tree
Hide file tree
Showing 4 changed files with 135 additions and 11 deletions.
5 changes: 5 additions & 0 deletions app/models/bot/smooch.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
8 changes: 7 additions & 1 deletion app/models/concerns/smooch_menus.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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 << {
Expand Down Expand Up @@ -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
Expand Down
84 changes: 74 additions & 10 deletions app/models/concerns/smooch_search.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand All @@ -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
49 changes: 49 additions & 0 deletions test/models/bot/smooch_6_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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

0 comments on commit 2ea0882

Please sign in to comment.