Skip to content

Commit 4eeafa0

Browse files
authored
Merge pull request #409 from MITLibraries/use-614
Add 'Full text options' fulfillment link.
2 parents 5c6ed91 + 0b390e1 commit 4eeafa0

8 files changed

Lines changed: 118 additions & 12 deletions

File tree

app/controllers/thirdiron_controller.rb

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
require 'uri'
2+
13
class ThirdironController < ApplicationController
24
layout false
35

@@ -14,11 +16,28 @@ def browzine
1416
return unless ThirdIron.enabled? && params[:issn].present?
1517

1618
@browzine = Browzine.lookup(issn: params[:issn])
19+
@full_record_url = safe_full_record_url(params[:full_record_url])
1720
end
1821

1922
private
2023

2124
def expected_params?
2225
params[:type].present? && params[:identifier].present?
2326
end
27+
28+
def safe_full_record_url(url)
29+
return nil unless url.is_a?(String)
30+
31+
url = url.strip
32+
return nil if url.blank?
33+
34+
parsed = URI.parse(url)
35+
return nil unless parsed.is_a?(URI::HTTP)
36+
return nil if parsed.host.blank?
37+
return nil if parsed.userinfo.present?
38+
39+
parsed.to_s
40+
rescue URI::InvalidURIError, ArgumentError
41+
nil
42+
end
2443
end

app/helpers/results_helper.rb

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,16 @@ def article?(format)
8181
format.match?(/\barticle\b/i)
8282
end
8383

84+
# Extracts the full record URL from a result's links
85+
#
86+
# @param result [Hash] A normalized result hash with :links
87+
# @return [String, nil] The URL of the 'full record' link, or nil if not found
88+
def full_record_url(result)
89+
return unless result.is_a?(Hash)
90+
91+
result[:links]&.find { |link| link['kind'] == 'full record' }&.dig('url')
92+
end
93+
8494
# Determines if a result has any fulfillment links to render
8595
#
8696
# @param result [Hash] A normalized Primo result hash

app/views/search/_result_primo.html.erb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
<div class="title-lockup">
44
<%# NOTE: the order of the two items in this lockup are being swapped visually with CSS. The markup is ordered to make the screen reading experience more logical %>
55
<h3 class="record-title">
6-
<% if result[:links]&.find { |link| link['kind'] == 'full record' } %>
7-
<%= link_to(result[:title], result[:links].find { |link| link['kind'] == 'full record' }['url'], data: { content_piece: 'Result Title' }) %>
6+
<% if full_record_url(result) %>
7+
<%= link_to(result[:title], full_record_url(result), data: { content_piece: 'Result Title' }) %>
88
<% else %>
99
<%= result[:title] %>
1010
<% end %>
@@ -122,7 +122,7 @@
122122
<%# Trigger BrowZine lookup (render inside result-get so injected HTML
123123
is part of the flex `.result-get` area and receives expected styles) %>
124124
<% if ThirdIron.enabled? && result[:format].downcase == 'journal' && result[:issn].present? %>
125-
<%= render(partial: 'trigger_browzine', locals: { issn: result[:issn] }) %>
125+
<%= render(partial: 'trigger_browzine', locals: { issn: result[:issn], full_record_url: full_record_url(result) }) %>
126126
<% end %>
127127
</div>
128128
<% end %>

app/views/search/_trigger_browzine.html.erb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
<% return unless ThirdIron.enabled? %>
22

3-
<% data_url = "/browzine?issn=#{issn}" %>
3+
<% query_params = { issn: issn } %>
4+
<% query_params[:full_record_url] = full_record_url if full_record_url.present? %>
5+
<% data_url = "/browzine?#{query_params.to_query}" %>
46

57
<span class="libkey-container"
68
data-controller="content-loader"
Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,14 @@
1-
<% if ThirdIron.enabled? && @browzine.present? %>
2-
<% if @browzine[:browzine_link].present? %>
3-
<div class="libkey-actions">
4-
<%= link_to @browzine[:browzine_link][:text], @browzine[:browzine_link][:link], class: 'button libkey-link', data: { matomo_seen: "Results, Browzine Link Seen, Tab: {{getActiveTabName}}", matomo_click: "Results, Browzine Link Engaged, Link: {{getElementText}}", content_piece: @browzine[:browzine_link][:text] } %>
5-
</div>
1+
<%# Dedicated Browzine endpoint response for journal records.
2+
Rendered when: A Primo journal result with ISSN triggers a Browzine API lookup via /browzine?issn=X&full_record_url=Y
3+
Use case: Primary fulfillment source for journal articles (via trigger_browzine partial from _result_primo.html.erb)
4+
Returns: Browzine link + optional "Full-text options" button (links to Primo record)
5+
%>
6+
<% if ThirdIron.enabled? && @browzine.present? && @browzine[:browzine_link].present? %>
7+
<% if @full_record_url.present? %>
8+
<%= link_to 'Full-text options', @full_record_url, class: 'button libkey-link', data: { matomo_seen: "Results, Full-text Options Link Seen, Tab: {{getActiveTabName}}", matomo_click: "Results, Full-text Options Link Engaged, Link: {{getElementText}}", content_piece: 'Full-text options' } %>
69
<% end %>
10+
11+
<div class="libkey-actions">
12+
<%= link_to @browzine[:browzine_link][:text], @browzine[:browzine_link][:link], class: 'libkey-link', data: { matomo_seen: "Results, Browzine Link Seen, Tab: {{getActiveTabName}}", matomo_click: "Results, Browzine Link Engaged, Link: {{getElementText}}", content_piece: @browzine[:browzine_link][:text] } %>
13+
</div>
714
<% end %>

app/views/thirdiron/libkey.html.erb

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,16 @@
1313
<%= link_to( @libkey[:best_integrator_link][:text], @libkey[:best_integrator_link][:link], class: 'button libkey-link', data: { matomo_seen: "Results, LibKey Link Seen, Tab: {{getActiveTabName}}", matomo_click: "Results, LibKey Link Engaged, Link: {{getElementText}}", content_piece: @libkey[:best_integrator_link][:text] } ) %>
1414
<% end %>
1515

16+
<%# Browzine link returned as part of LibKey fulfillment response.
17+
Rendered when: LibKey API includes a browzine_link in its response
18+
Use case: Secondary fulfillment option alongside PDF, HTML, or other direct links from LibKey
19+
Note: Different from dedicated browzine.html.erb which is called directly for journals.
20+
LibKey Browzine link appears when looking up DOI/PMID fulfillment (articles, etc.)
21+
%>
1622
<%# Display browzine link if available. This should always display if we have it regardless of other links. %>
1723
<% if @libkey[:browzine_link].present? %>
1824
<div class="libkey-actions">
19-
<%= link_to @libkey[:browzine_link][:text], @libkey[:browzine_link][:link], class: 'button libkey-link', data: { matomo_seen: "Results, Browzine Link Seen, Tab: {{getActiveTabName}}", matomo_click: "Results, Browzine Link Engaged, Link: {{getElementText}}", content_piece: @libkey[:browzine_link][:text] } %>
25+
<%= link_to @libkey[:browzine_link][:text], @libkey[:browzine_link][:link], class: 'libkey-link', data: { matomo_seen: "Results, Browzine Link Seen, Tab: {{getActiveTabName}}", matomo_click: "Results, Browzine Link Engaged, Link: {{getElementText}}", content_piece: @libkey[:browzine_link][:text] } %>
2026
</div>
2127
<% end %>
2228

test/controllers/thirdiron_controller_test.rb

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,14 @@ class ThirdironControllerTest < ActionDispatch::IntegrationTest
2727
get '/libkey?type=doi&identifier=10.1038/s41567-023-02305-y'
2828

2929
assert_response :success
30-
assert_select 'a.button', { count: 3 }
30+
assert_select 'a.libkey-link', { count: 3 }
3131
end
3232

3333
VCR.use_cassette('libkey pmid') do
3434
get '/libkey?type=pmid&identifier=22110403'
3535

3636
assert_response :success
37-
assert_select 'a.button', { count: 3 }
37+
assert_select 'a.libkey-link', { count: 3 }
3838
end
3939
end
4040

@@ -111,7 +111,35 @@ class ThirdironControllerTest < ActionDispatch::IntegrationTest
111111
get '/browzine?issn=1546170X'
112112

113113
assert_response :success
114+
115+
# Only browzine link, no button since no full_record_url provided
116+
assert_select 'a.button', { count: 0 }
117+
assert_select 'a.libkey-link', { count: 1 }
118+
end
119+
end
120+
121+
test 'browzine route with full_record_url returns both links' do
122+
VCR.use_cassette('browzine issn') do
123+
get '/browzine?issn=1546170X&full_record_url=https://example.com/full-record'
124+
125+
assert_response :success
126+
127+
# Button (Full-text options) and browzine link both have libkey-link class
114128
assert_select 'a.button', { count: 1 }
129+
assert_select 'a.button[href="https://example.com/full-record"]'
130+
assert_select 'a.libkey-link', { count: 2 }
131+
end
132+
end
133+
134+
test 'browzine route ignores unsafe full_record_url values' do
135+
VCR.use_cassette('browzine issn') do
136+
get '/browzine?issn=1546170X&full_record_url=javascript:alert(1)'
137+
138+
assert_response :success
139+
140+
# Unsafe URL should not produce a "Full-text options" button
141+
assert_select 'a.button', { count: 0 }
142+
assert_select 'a.libkey-link', { count: 1 }
115143
end
116144
end
117145

test/helpers/results_helper_test.rb

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,4 +191,38 @@ class ResultsHelperTest < ActionView::TestCase
191191
})
192192
end
193193
end
194+
195+
test 'full_record_url returns the URL when result has a full record link' do
196+
result = {
197+
links: [
198+
{ 'kind' => 'PDF', 'url' => 'https://pdf.example.com' },
199+
{ 'kind' => 'full record', 'url' => 'https://primo.example.com/record' }
200+
]
201+
}
202+
assert_equal 'https://primo.example.com/record', full_record_url(result)
203+
end
204+
205+
test 'full_record_url returns nil when result has no links' do
206+
result = {}
207+
assert_nil full_record_url(result)
208+
end
209+
210+
test 'full_record_url returns nil when result has links but no full record link' do
211+
result = {
212+
links: [
213+
{ 'kind' => 'PDF', 'url' => 'https://pdf.example.com' },
214+
{ 'kind' => 'HTML', 'url' => 'https://html.example.com' }
215+
]
216+
}
217+
assert_nil full_record_url(result)
218+
end
219+
220+
test 'full_record_url returns nil when result is nil' do
221+
assert_nil full_record_url(nil)
222+
end
223+
224+
test 'full_record_url returns nil when result[:links] is nil' do
225+
result = { links: nil }
226+
assert_nil full_record_url(result)
227+
end
194228
end

0 commit comments

Comments
 (0)