Skip to content

Commit d86e5d9

Browse files
committed
Initial tabbed Primo/TIMDEX interface
Why these changes are being introduced: USE UI needs affordances to switch between results from different sources. Primo results should be the default view. Relevant ticket(s): * [USE-31](https://mitlibraries.atlassian.net/browse/USE-31) * [TIMX-549](https://mitlibraries.atlassian.net/browse/TIMX-549) How this addresses that need: * Integrates Primo search into the controller and view layers * Adds Turbo frame 'tabs' to switch between Primo and TIMDEX results. Side effects of this change: * Maintaining separate views for each result type is probably not ideal, but I'm accepting it as a risk until we normalize TIMDEX records similarly to how we normalize Primo. * Several tests have been skipped for features that are no longer relevant to USE UI, but are core to GDT. We will need to revise our overall test strategy such that these features are tested as part of GDT. * FRBRized full record links don't always work. It the method we use breaks FRBR links for CDI records. Predictably, Ex Libris documentation on FRBR does not address this issue. We might decide to forgo FRBR links in general, or perhaps limit them by content type (assuming book results always come from Alma). * Pagination is not yet implemented.
1 parent 0baa069 commit d86e5d9

File tree

7 files changed

+685
-404
lines changed

7 files changed

+685
-404
lines changed

app/controllers/search_controller.rb

Lines changed: 98 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -15,59 +15,108 @@ def results
1515
# inject session preference for boolean type if it is present
1616
params[:booleanType] = cookies[:boolean_type] || 'AND'
1717

18-
# hand off to Enhancer chain
19-
@enhanced_query = Enhancer.new(params).enhanced_query
18+
# Determine which tab to load - default to primo unless gdt is enabled
19+
@active_tab = if Flipflop.enabled?(:gdt)
20+
'gdt' # Keep existing GDT behavior unchanged
21+
else
22+
params[:tab] || 'primo' # Default to primo for new tabbed interface
23+
end
24+
25+
# Route to appropriate search based on active tab
26+
if Flipflop.enabled?(:gdt)
27+
# Keep existing GDT behavior unchanged
28+
load_gdt_results
29+
else
30+
case @active_tab
31+
when 'primo'
32+
load_primo_results
33+
when 'timdex'
34+
load_timdex_results
35+
end
36+
end
37+
end
2038

21-
# hand off enhanced query to builder
22-
query = QueryBuilder.new(@enhanced_query).query
39+
private
2340

24-
# Create cache key for this query
25-
# Sorting query hash to ensure consistent key generation regardless of the parameter order
26-
sorted_query = query.sort_by { |k, v| k.to_sym }.to_h
27-
cache_key = Digest::MD5.hexdigest(sorted_query.to_s)
41+
def load_gdt_results
42+
@enhanced_query = Enhancer.new(params).enhanced_query
43+
query = QueryBuilder.new(@enhanced_query).query
2844

29-
# builder hands off to wrapper which returns raw results here
30-
# We are using two difference caches to allow for Geo and USE to be cached separately. This ensures we don't have
31-
# cache key collission for these two different query types. In practice, the likelihood of this happening is low,
32-
# as the query parameters are different for each type and they won't often be run with the same cache backend other
33-
# than locally, but this is a safeguard.
34-
# The response type is a GraphQL::Client::Response, which is not directly serializable, so we convert it to a hash.
35-
response = if Flipflop.enabled?(:gdt)
36-
Rails.cache.fetch("#{cache_key}/geo", expires_in: 12.hours) do
37-
raw = execute_geospatial_query(query)
38-
{
39-
data: raw.data.to_h,
40-
errors: raw.errors.details.to_h
41-
}
42-
end
43-
else
44-
Rails.cache.fetch("#{cache_key}/use", expires_in: 12.hours) do
45-
raw = TimdexBase::Client.query(TimdexSearch::BaseQuery, variables: query)
46-
{
47-
data: raw.data.to_h,
48-
errors: raw.errors.details.to_h
49-
}
50-
end
51-
end
45+
response = cache_timdex_query(query)
5246

5347
# Handle errors
5448
@errors = extract_errors(response)
55-
56-
# Analayze results
57-
# The @pagination instance variable includes info about next/previous pages (where they exist) to assist the UI.
5849
@pagination = Analyzer.new(@enhanced_query, response).pagination if @errors.nil?
59-
60-
# Display results
6150
@results = extract_results(response)
6251
@filters = extract_filters(response)
6352
end
6453

65-
private
54+
def load_primo_results
55+
@enhanced_query = Enhancer.new(params).enhanced_query
56+
@errors = nil
57+
@results = []
58+
@pagination = nil
59+
60+
begin
61+
primo_search = PrimoSearch.new
62+
per_page = params[:per_page] || 20
63+
primo_response = primo_search.search(params[:q], per_page)
64+
65+
@results = NormalizePrimoResults.new(primo_response, params[:q]).normalize
66+
67+
# Basic pagination for now.
68+
if @results.present?
69+
@pagination = {
70+
hits: @results.count,
71+
start: 1,
72+
end: @results.count
73+
}
74+
end
75+
76+
rescue StandardError => e
77+
@errors = handle_primo_errors(e)
78+
end
79+
end
80+
81+
def load_timdex_results
82+
@enhanced_query = Enhancer.new(params).enhanced_query
83+
query = QueryBuilder.new(@enhanced_query).query
84+
response = cache_timdex_query(query)
85+
86+
@errors = extract_errors(response)
87+
@pagination = Analyzer.new(@enhanced_query, response).pagination if @errors.nil?
88+
@results = extract_results(response)
89+
end
6690

6791
def active_filters
6892
ENV.fetch('ACTIVE_FILTERS', '').split(',').map(&:strip)
6993
end
7094

95+
def cache_timdex_query(query)
96+
# Create cache key for this query
97+
# Sorting query hash to ensure consistent key generation regardless of the parameter order
98+
sorted_query = query.sort_by { |k, v| k.to_sym }.to_h
99+
cache_key = Digest::MD5.hexdigest(sorted_query.to_s)
100+
101+
# builder hands off to wrapper which returns raw results here
102+
# We are using two difference caches to allow for Geo and USE to be cached separately. This ensures we don't have
103+
# cache key collision for these two different query types. In practice, the likelihood of this happening is low,
104+
# as the query parameters are different for each type and they won't often be run with the same cache backend other
105+
# than locally, but this is a safeguard.
106+
# The response type is a GraphQL::Client::Response, which is not directly serializable, so we convert it to a hash.
107+
Rails.cache.fetch("#{cache_key}/#{@active_tab}", expires_in: 12.hours) do
108+
raw = if @active_tab == 'gdt'
109+
execute_geospatial_query(query)
110+
elsif @active_tab == 'timdex'
111+
TimdexBase::Client.query(TimdexSearch::BaseQuery, variables: query)
112+
end
113+
{
114+
data: raw.data.to_h,
115+
errors: raw.errors.details.to_h
116+
}
117+
end
118+
end
119+
71120
def execute_geospatial_query(query)
72121
if query['geobox'] == 'true' && query[:geodistance] == 'true'
73122
TimdexBase::Client.query(TimdexSearch::AllQuery, variables: query)
@@ -214,4 +263,16 @@ def validate_geobox_values!
214263
flash[:error] = 'Maximum latitude cannot exceed minimum latitude.'
215264
redirect_to root_url
216265
end
266+
267+
def handle_primo_errors(error)
268+
Rails.logger.error("Primo search error: #{error.message}")
269+
270+
if error.is_a?(ArgumentError)
271+
[{ 'message' => 'Primo search is not properly configured.' }]
272+
elsif error.is_a?(HTTP::TimeoutError)
273+
[{ 'message' => 'The Primo service is currently slow to respond. Please try again.' }]
274+
else
275+
[{ 'message' => error.message }]
276+
end
277+
end
217278
end

0 commit comments

Comments
 (0)