Skip to content

Commit 4a46861

Browse files
committed
Implement natural language search opt-in
Why these changes are being introduced: We want users to be able to decide whether to use hybrid (termed 'natural language search' in the public UI) or lexical search. USE-577 implemented the frontend work on this feature; this ticket implements the controller layer. Relevant ticket(s): - [USE-576](https://mitlibraries.atlassian.net/browse/USE-576) - [USE-577](https://mitlibraries.atlassian.net/browse/USE-577) How this addresses that need: This adds natural language search opt-in cookie, which is toggled via a stimulus controller. When the cookie is set, hybrid search is the default query mode. When it is not set, the query mode falls back to lexical search (or other default mode specified by env). This behavior can be overridden by supplying a `queryMode` param manually. For example, if a user has the NLS cookie set but adds `queryMode=semantic` to their query string, the search will execute in semantic mode, not hybrid. If NLS is not available for a given tab, a message displays to notify the user of the limitation. Side effects of this change: Tests for this feature are stored in a separate file. This is different from how we usually do things, but there are so many conditions to test that I did not want to pollute the search controller test file further.
1 parent fc98b7a commit 4a46861

8 files changed

Lines changed: 385 additions & 3 deletions

File tree

app/controllers/application_controller.rb

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
class ApplicationController < ActionController::Base
22
helper Mitlibraries::Theme::Engine.helpers
33

4+
before_action :set_natural_language_search_optin
5+
46
# Set active tab based on params (no persistent cookie). This intentionally
57
# avoids storing the user's last-used tab in a cookie per UXWS request.
68
def set_active_tab
@@ -31,4 +33,8 @@ def all_tabs
3133
def valid_tab?(tab)
3234
all_tabs.include?(tab)
3335
end
36+
37+
def set_natural_language_search_optin
38+
@natural_language_search_optin = cookies[:natural_language_search_optin] == 'true'
39+
end
3440
end

app/controllers/search_controller.rb

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,11 @@ def results
1616
# inject session preference for boolean type if it is present
1717
params[:booleanType] = cookies[:boolean_type] || 'AND'
1818

19+
# inject queryMode from cookie if not provided in URL params
20+
params[:queryMode] = 'hybrid' if params[:queryMode].blank? && cookies[:natural_language_search_optin] == 'true'
21+
1922
@enhanced_query = Enhancer.new(params).enhanced_query
23+
@show_nls_warning = show_nls_warning?
2024

2125
# Load GeoData results if applicable
2226
if Feature.enabled?(:geodata)
@@ -442,4 +446,10 @@ def valid_token?
442446

443447
false
444448
end
449+
450+
# show_nls_warning? determines if the user should see a warning that results are not using natural language search.
451+
# Shows when: user is opted-in AND viewing a Primo-only tab (which only supports keyword search).
452+
def show_nls_warning?
453+
@natural_language_search_optin && primo_tabs.include?(@active_tab)
454+
end
445455
end

app/controllers/static_controller.rb

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
require 'uri'
2+
13
class StaticController < ApplicationController
24
def style_guide; end
35

@@ -10,4 +12,29 @@ def boolpref
1012

1113
redirect_back_or_to root_path
1214
end
15+
16+
def natural_language_search_optin
17+
optin = params[:natural_language_search_optin]
18+
if %w[true false].include?(optin)
19+
cookies[:natural_language_search_optin] = optin
20+
else
21+
cookies.delete :natural_language_search_optin
22+
end
23+
24+
# Redirect to return_to param if it's a safe local path, otherwise root
25+
return_to = params[:return_to].presence
26+
safe_return_to = nil
27+
if return_to
28+
begin
29+
parsed = URI.parse(return_to)
30+
# Only allow local paths with no host or scheme
31+
if parsed.path&.start_with?('/') && !return_to.start_with?('//') && parsed.host.nil? && parsed.scheme.nil?
32+
safe_return_to = return_to
33+
end
34+
rescue URI::InvalidURIError
35+
safe_return_to = nil
36+
end
37+
end
38+
redirect_to(safe_return_to || root_path)
39+
end
1340
end
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { Controller } from "@hotwired/stimulus"
2+
3+
export default class extends Controller {
4+
toggle(event) {
5+
event.preventDefault()
6+
7+
const isCurrentlyOn = this.element.classList.contains('toggled-on')
8+
const newState = isCurrentlyOn ? 'false' : 'true'
9+
10+
this.setOptinState(newState)
11+
}
12+
13+
setOptinState(state) {
14+
// Build the redirect URL to return to the current results page
15+
const redirectUrl = window.location.pathname + window.location.search
16+
17+
// Make a GET request to the toggle endpoint
18+
const url = new URL('/natural_language_search_optin', window.location.origin)
19+
url.searchParams.set('natural_language_search_optin', state)
20+
url.searchParams.set('return_to', redirectUrl)
21+
22+
// Navigate to the toggle URL, which will redirect back with the new cookie set
23+
window.location.href = url.toString()
24+
}
25+
}

app/views/search/_form.html.erb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@
1111
</div>
1212
<div class="search-actions">
1313
<a href="https://libraries.mit.edu/search-advanced" data-matomo-click="Search, Advanced Search Engaged, Tab: {{getActiveTabName}}">Advanced search</a>
14-
<div class="semantic-search-toggle toggled-off" data-matomo-seen="Semantic Search Toggle, Seen by user, Tab: {{getActiveTabName}}">
15-
<button type="button" data-matomo-click="Semantic Search Toggle, Toggle Clicked, Was: {{getToggleState}}">Natural language search</button>
14+
<div class="semantic-search-toggle <%= @natural_language_search_optin ? 'toggled-on' : 'toggled-off' %>" data-controller="natural-language-search-toggle" data-matomo-seen="Semantic Search Toggle, Seen by user, Tab: {{getActiveTabName}}">
15+
<button type="button" aria-pressed="<%= @natural_language_search_optin %>" data-action="click->natural-language-search-toggle#toggle" data-matomo-click="Semantic Search Toggle, Toggle Clicked, Was: {{getToggleState}}">Natural language search</button>
1616
<a href="#" data-matomo-click="Semantic Search Toggle, Learn more Clicked, Tab: {{getActiveTabName}}">Learn more</a>
1717
</div>
1818
</div>

app/views/search/results.html.erb

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,10 @@
1818
<% elsif @results.present? && @errors.blank? %>
1919

2020
<h2 class="results-context" data-matomo-seen="Search, Results Found, Tab: {{getActiveTabName}}"><%= results_summary(@pagination[:hits]) %></h2>
21-
<%= render partial: 'search/nls_alert' %>
21+
<p class="results-context-description"><%= tab_description %></p>
22+
<% if @show_nls_warning %>
23+
<%= render partial: 'search/nls_alert' %>
24+
<% end %>
2225
<div id="results-layout-wrapper">
2326
<main id="results">
2427
<ol class="results-list use" data-matomo-click="Results, Link Engaged, Link: {{getElementText}}; Tab: {{getActiveTabName}}" start="<%= @pagination[:start] %>">

config/routes.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
get 'style-guide', to: 'static#style_guide'
1818

1919
get 'boolpref', to: 'static#boolpref'
20+
get 'natural_language_search_optin', to: 'static#natural_language_search_optin'
2021

2122
get 'robots.txt', to: 'robots#robots'
2223
end

0 commit comments

Comments
 (0)