Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@ See `Optional Environment Variables` for more information.
mode. Note that this is currently intended _only_ for the geodata app and
may have unexpected consequences if applied to other TIMDEX UI apps.
- `FEATURE_SIMULATE_SEARCH_LATENCY`: DO NOT SET IN PRODUCTION. Set to ensure a minimum of a one second delay in returning search results. Useful to see spinners/loaders. Only introduces delay for results that take less than one second to complete.
- `FEATURE_TAB_TIMDEX_ALL`: Display a tab for displaying the combined TIMDEX data
- `FEATURE_TAB_TIMDEX_ALMA`: Display a tab for displaying Alma data from TIMDEX
- `FILTER_ACCESS_TO_FILES`: The name to use instead of "Access to files" for that filter / aggregation.
- `FILTER_CONTENT_TYPE`: The name to use instead of "Content type" for that filter / aggregation.
- `FILTER_CONTRIBUTOR`: The name to use instead of "Contributor" for that filter / aggregation.
Expand Down
16 changes: 14 additions & 2 deletions app/controllers/application_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ class ApplicationController < ActionController::Base
def set_active_tab
# GeoData doesn't use the tab system.
return if Feature.enabled?(:geodata)

@active_tab = if params[:tab].present? && valid_tab?(params[:tab])
# If params[:tab] is set and valid, use it and set session
cookies[:last_tab] = params[:tab]
Expand All @@ -21,9 +21,21 @@ def set_active_tab
end
end

def primo_tabs
%w[alma cdi primo]
end

def timdex_tabs
%w[aspace timdex timdex_alma website]
end

def all_tabs
['all', *primo_tabs, *timdex_tabs]
end

private

def valid_tab?(tab)
%w[primo timdex all].include?(tab)
all_tabs.include?(tab)
end
end
29 changes: 17 additions & 12 deletions app/controllers/search_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,18 @@ def results
render 'results_geo'
return
end


@primo_tabs = primo_tabs
@timdex_tabs = timdex_tabs
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question:
Do we still need these instance variables? They were used before during the rendering of results, but now that we're handling the rendering based on the new api value in the data, I don't see anywhere in the codebase that still uses these.

This is non-blocking in case I'm missing something, or if there's a longer term plan for these variables that I'm forgetting.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch. Will fix to avoid future confusion.


# Route to appropriate search based on active tab
case @active_tab
when 'primo'
when 'all' # all tab must come first to avoid matching primo or timdex arrays which contain all
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question:
Does the comment here need to change, now that we've removed 'all' from the two tab lists?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, the comment should and will be removed. Great catch. The order here should no longer matter.

load_all_results
when *primo_tabs
load_primo_results
when 'timdex'
when *timdex_tabs
load_timdex_results
when 'all'
load_all_results
end
end

Expand Down Expand Up @@ -108,7 +111,7 @@ def load_all_results

# For now, just use primo pagination as a placeholder
@pagination = primo_data[:pagination] || {}

# Handle primo continuation for high page numbers
@show_primo_continuation = primo_data[:show_continuation] || false
end
Expand All @@ -119,9 +122,7 @@ def fetch_primo_data
offset = (current_page - 1) * per_page

# Check if we're beyond Primo API limits before making the request.
if offset >= Analyzer::PRIMO_MAX_OFFSET
return { results: [], pagination: {}, errors: nil, show_continuation: true }
end
return { results: [], pagination: {}, errors: nil, show_continuation: true } if offset >= Analyzer::PRIMO_MAX_OFFSET

primo_response = query_primo
results = NormalizePrimoResults.new(primo_response, @enhanced_query[:q]).normalize
Expand All @@ -133,7 +134,7 @@ def fetch_primo_data
# exists in Primo UI, sending users there seems like the best we can do.
show_continuation = false
errors = nil

if results.empty?
docs = primo_response['docs'] if primo_response.is_a?(Hash)
if docs.nil? || docs.empty?
Expand All @@ -153,7 +154,7 @@ def fetch_timdex_data
query = QueryBuilder.new(@enhanced_query).query
response = query_timdex(query)
errors = extract_errors(response)

if errors.nil?
pagination = Analyzer.new(@enhanced_query, response, :timdex).pagination
raw_results = extract_results(response)
Expand All @@ -169,14 +170,18 @@ def active_filters
end

def query_timdex(query)
query[:sourceFilter] = ['MIT Libraries Website', 'LibGuides'] if @active_tab == 'website'
query[:sourceFilter] = ['MIT ArchivesSpace'] if @active_tab == 'aspace'
query[:sourceFilter] = ['MIT Alma'] if @active_tab == 'timdex_alma'

# We generate unique cache keys to avoid naming collisions.
cache_key = generate_cache_key(query)

# Builder hands off to wrapper which returns raw results here.
Rails.cache.fetch("#{cache_key}/#{@active_tab}", expires_in: 12.hours) do
raw = if Feature.enabled?(:geodata)
execute_geospatial_query(query)
elsif @active_tab == 'timdex' || @active_tab == 'all'
else
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was this a bug that slipped past me? Reading it now, it seems like raw was going to be empty when the active tab was something like website.

I think this is cleaner, but I want to know if I missed something here before.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think there was an intermediary change that used the timdex_tabs you may not be seeing if you looked at the full changes rather than just the commit that changed the behavior. It was working on previous commits, but would no longer have handled the all tab case as I removed all from timdex_tabs along the way

TimdexBase::Client.query(TimdexSearch::BaseQuery, variables: query)
end

Expand Down
24 changes: 15 additions & 9 deletions app/helpers/search_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,22 @@ def link_to_result(result)
end
end

def link_to_tab(target)
if @active_tab == target.downcase
link_to target, results_path(params.permit(:q, :per_page, :booleanType, :tab).merge(tab: target.downcase)),
aria: { current: "page" },
class: "active tab-link",
data: { turbo_frame: "search-results", turbo_action: "advance" }
# Generates a link for a search results tab, marking it as active if it matches the current active tab.
# @param target [String] The name of the tab to link to
# @param label [String] The display label for the tab (optional)
# @return [String] HTML link element for the tab
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for writing the document block for this helper!

def link_to_tab(target, label = nil)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wondered how soon we'd need to have a label argument in this helper - I guess the answer is "a few days" :-)

Question:
As I follow the interactions between label and target inside the method, I wonder whether we need tests for this behavior. If I'm following this correctly, we derive clean_target for use in the DOM first, and then overlay label atop target, so that target ends up getting used in human-facing contexts like the visible text on the UI. I like that logic and sequence, so I don't have a concern there - but after dealing with UI bugs all day I'm starting to wonder if we need more tests for this type of thing.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh...my names for all of this is confusing after me reading your summary of what is going on :)

I have no idea why I redefine target as clean_target and then create a new target for the label. I'm going to clean that logic up. I think it works as expected but apologies for having to parse wtf is going on.

My fix is likely to change target = label || target to label = label || target so we can use the label throughout. I think the current state was a lazy refactor I did when I slipped in the label feature to this PR in anticipation of UX not actually wanting any of the tab names I was using and not wanting to have to fix a bunch of tests later :)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added tests and updated variable names to be clearer.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I like these variable names better - thanks!

clean_target = target.downcase.gsub(' ', '_').downcase
tab_label = label || target
if @active_tab == clean_target
link_to tab_label, results_path(params.permit(:q, :per_page, :booleanType, :tab).merge(tab: clean_target)),
aria: { current: 'page' },
class: 'active tab-link',
data: { turbo_frame: 'search-results', turbo_action: 'advance' }
else
link_to target, results_path(params.permit(:q, :per_page, :booleanType, :tab).merge(tab: target.downcase)),
class: "tab-link",
data: { turbo_frame: "search-results", turbo_action: "advance" }
link_to tab_label, results_path(params.permit(:q, :per_page, :booleanType, :tab).merge(tab: clean_target)),
class: 'tab-link',
data: { turbo_frame: 'search-results', turbo_action: 'advance' }
end
end

Expand Down
2 changes: 1 addition & 1 deletion app/models/feature.rb
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
#
class Feature
# List of all valid features in the application
VALID_FEATURES = %i[geodata boolean_picker simulate_search_latency].freeze
VALID_FEATURES = %i[geodata boolean_picker simulate_search_latency tab_timdex_all tab_timdex_alma].freeze

# Check if a feature is enabled by name
#
Expand Down
1 change: 1 addition & 0 deletions app/models/normalize_primo_record.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ def initialize(record, query)
def normalize
{
# Core fields
api: 'primo',
title:,
creators:,
source:,
Expand Down
1 change: 1 addition & 0 deletions app/models/normalize_timdex_record.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ def initialize(record, query)
def normalize
{
# Core fields
api: 'timdex',
title:,
creators:,
source:,
Expand Down
11 changes: 9 additions & 2 deletions app/views/search/_source_tabs.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,15 @@
<nav id="tabs" class="tab-navigation" aria-label="Result type navigation">
<ul>
<li><%= link_to_tab("All") %></li>
<li><%= link_to_tab("Primo") %></li>
<li><%= link_to_tab("TIMDEX") %></li>
<li><%= link_to_tab("Primo", "Articles and Catalog") %></li>
<% if Feature.enabled?(:tab_timdex_all) %>
<li><%= link_to_tab("TIMDEX") %></li>
<% end %>
<% if Feature.enabled?(:tab_timdex_alma) %>
<li><%= link_to_tab("timdex_alma", "Alma (TIMDEX)") %></li>
<% end %>
<li><%= link_to_tab("Website") %></li>
<li><%= link_to_tab("aspace", "Archival materials") %></li>
</ul>
</nav>

15 changes: 4 additions & 11 deletions app/views/search/results.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -22,21 +22,14 @@
<p class="results-context-description">From all MIT Libraries sources</p>
<div id="results-layout-wrapper">
<main id="results">
<ol class="results-list" start="<%= @pagination[:start] %>">
<% case @active_tab %>
<% when 'primo' %>
<%= render(partial: 'search/result_primo', collection: @results, as: :result) %>
<% when 'timdex' %>
<%= render(partial: 'search/result', collection: @results, as: :result) %>
<% when 'all' %>
<% @results.each do |result| %>
<% if result[:source] == 'Primo' %>
<ol class="results-list" start="<%= @pagination[:start] %>">
<% @results.each do |result| %>
<% if result[:api] == 'primo' %>
<%= render(partial: 'search/result_primo', locals: { result: result }) %>
<% else %>
<% elsif result[:api] == 'timdex' %>
<%= render(partial: 'search/result', locals: { result: result }) %>
<% end %>
<% end %>
<% end %>
</ol>
<%= render partial: "results_callouts" %>
</main>
Expand Down
66 changes: 66 additions & 0 deletions test/controllers/application_controller_unit_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
require 'test_helper'

class ApplicationControllerUnitTest < ActionController::TestCase
setup do
@controller = ApplicationController.new
end

test '#primo_tabs returns expected tabs' do
assert_equal %w[alma cdi primo], @controller.primo_tabs
end

test '#timdex_tabs returns expected tabs' do
assert_equal %w[aspace timdex timdex_alma website], @controller.timdex_tabs
end

test '#all_tabs includes both primo and timdex tabs as well as all tab' do
expected = %w[all alma cdi primo aspace timdex timdex_alma website]
assert_equal expected, @controller.all_tabs
end

test '#valid_tab? returns true for valid tabs' do
assert @controller.send(:valid_tab?, 'all')
assert @controller.send(:valid_tab?, 'alma')
assert @controller.send(:valid_tab?, 'timdex')
end

test '#valid_tab? returns false for invalid tabs' do
refute @controller.send(:valid_tab?, 'invalid')
refute @controller.send(:valid_tab?, '')
end

test 'set_active_tab defaults @active_tab to all when no params or cookies are set' do
@controller.stubs(:params).returns({})
@controller.stubs(:cookies).returns({})
@controller.set_active_tab
assert_equal 'all', @controller.instance_variable_get(:@active_tab)
end

test 'set_active_tab sets @active_tab to tab when tab params is valid' do
@controller.stubs(:params).returns({ tab: 'primo' })
@controller.stubs(:cookies).returns({})
@controller.set_active_tab
assert_equal 'primo', @controller.instance_variable_get(:@active_tab)
end

test 'set_active_tab sets @active_tab to all when tab params is invalid and no cookie is set' do
@controller.stubs(:params).returns({ tab: 'supertab' })
@controller.stubs(:cookies).returns({})
@controller.set_active_tab
assert_equal 'all', @controller.instance_variable_get(:@active_tab)
end

test 'set_active_tab sets @active_tab to cookie value when tab params is invalid and valid cookie is set' do
@controller.stubs(:params).returns({ tab: 'supertab' })
@controller.stubs(:cookies).returns({ last_tab: 'timdex' })
@controller.set_active_tab
assert_equal 'timdex', @controller.instance_variable_get(:@active_tab)
end

test 'set_active_tab sets @active_tab to all value when tab params is invalid and cookie is invalid' do
@controller.stubs(:params).returns({ tab: 'supertab' })
@controller.stubs(:cookies).returns({ last_tab: 'woohoo' })
@controller.set_active_tab
assert_equal 'all', @controller.instance_variable_get(:@active_tab)
end
end
32 changes: 24 additions & 8 deletions test/controllers/search_controller_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ class SearchControllerTest < ActionDispatch::IntegrationTest
def mock_primo_search_success
# Mock the Primo search components to avoid external API calls
sample_doc = {
api: 'primo',
title: 'Sample Primo Document Title',
format: 'Article',
year: '2025',
Expand All @@ -26,6 +27,7 @@ def mock_primo_search_success
def mock_timdex_search_success
# Mock the TIMDEX GraphQL client to avoid external API calls
sample_result = {
'api' => 'timdex',
'title' => 'Sample TIMDEX Document Title',
'timdexRecordId' => 'sample-record-123',
'contentType' => [{ 'value' => 'Article' }],
Expand Down Expand Up @@ -593,12 +595,24 @@ def source_filter_count(controller)
assert_select 'a.tab-link.active[href*="tab=primo"]', count: 1
end

test 'results respects timdex tab parameter' do
mock_timdex_search_success
test 'results respects timdex all tab parameter' do
ClimateControl.modify FEATURE_TAB_TIMDEX_ALL: 'true' do
mock_timdex_search_success

get '/results?q=test&tab=timdex'
assert_response :success
assert_select 'a.tab-link.active[href*="tab=timdex"]', count: 1
get '/results?q=test&tab=timdex'
assert_response :success
assert_select 'a.tab-link.active[href*="tab=timdex"]', count: 1
end
end

test 'results respects timdex alma tab parameter' do
ClimateControl.modify FEATURE_TAB_TIMDEX_ALMA: 'true' do
mock_timdex_search_success

get '/results?q=test&tab=timdex_alma'
assert_response :success
assert_select 'a.tab-link.active[href*="tab=timdex_alma"]', count: 1
end
end

test 'results shows tab navigation when GeoData is disabled' do
Expand All @@ -608,7 +622,9 @@ def source_filter_count(controller)
assert_response :success
assert_select '.tab-navigation', count: 1
assert_select 'a[href*="tab=primo"]', count: 1
assert_select 'a[href*="tab=timdex"]', count: 1
# assert_select 'a[href*="tab=timdex"]', count: 1
assert_select 'a[href*="tab=aspace"]', count: 1
assert_select 'a[href*="tab=website"]', count: 1
end

test 'results handles primo search errors gracefully' do
Expand Down Expand Up @@ -726,7 +742,7 @@ def source_filter_count(controller)

get '/results?q=test&tab=all'
assert_response :success

# Verify that we get results from both sources
assert_select '.record-title', text: /Sample Primo Document Title/
assert_select '.record-title', text: /Sample TIMDEX Document Title/
Expand Down Expand Up @@ -768,7 +784,7 @@ def source_filter_count(controller)

get '/results?q=test&tab=all&page=49'
assert_response :success

# Should show primo continuation partial
assert_select '.primo-continuation', count: 1
assert_select '.primo-continuation h2', text: /Continue your search in Search Our Collections/
Expand Down
Loading
Loading