Skip to content

Commit 266ab40

Browse files
authored
Merge pull request #367 from MITLibraries/use-367
Add support for Primo NDE links
2 parents f8cce06 + 86b1863 commit 266ab40

10 files changed

Lines changed: 216 additions & 35 deletions

.env.test

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,15 @@ TURNSTILE_SITEKEY=test-sitekey
33
TURNSTILE_SECRET=test-secret
44
FEATURE_TIMDEX_FULLTEXT=true
55
FEATURE_GEODATA=false
6+
FEATURE_PRIMO_NDE_LINKS=false
67
MIT_PRIMO_URL=https://mit.primo.exlibrisgroup.com
78
OPENALEX_EMAIL=FAKE_OPENALEX_EMAIL
89
PRIMO_API_KEY=FAKE_PRIMO_API_KEY
910
PRIMO_API_URL=https://api-na.hosted.exlibrisgroup.com/primo/v1
1011
PRIMO_SCOPE=cdi
1112
PRIMO_TAB=all
1213
PRIMO_VID=01MIT_INST:MIT
14+
PRIMO_NDE_VID=01MIT_INST:NDE
1315
RESULTS_PER_PAGE=20
1416
SYNDETICS_PRIMO_URL=https://syndetics.com/index.php?client=primo
1517
TACOS_HOST=FAKE_TACOS_HOST

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ may have unexpected consequences if applied to other TIMDEX UI apps.
106106
- `FEATURE_TAB_TIMDEX_ALL`: Display a tab for displaying the combined TIMDEX data. `TIMDEX_INDEX` affects which data appears in this tab.
107107
- `FEATURE_TAB_TIMDEX_ALMA`: Display a tab for displaying Alma data from TIMDEX. `TIMDEX_INDEX` must include `Alma` data or no results will return.
108108
- `FEATURE_TIMDEX_FULLTEXT`: Activate fulltext searching for sources in TIMDEX that support it
109+
- `FEATURE_PRIMO_NDE_LINKS`: Enables all Primo UI links to target the NDE version of Primo. When enabled, links will use `/nde/search` and `/nde/fulldisplay` paths along with the NDE view ID from `PRIMO_NDE_VID`.
109110
- `FILTER_ACCESS_TO_FILES`: The name to use instead of "Access to files" for that filter / aggregation.
110111
- `FILTER_CONTENT_TYPE`: The name to use instead of "Content type" for that filter / aggregation.
111112
- `FILTER_CONTRIBUTOR`: The name to use instead of "Contributor" for that filter / aggregation.
@@ -126,6 +127,7 @@ may have unexpected consequences if applied to other TIMDEX UI apps.
126127
- `OPENALEX_EMAIL`: required to enable OpenAlex OpenAccess lookups. In dev use your personal email. In production we'll use a Moira.
127128
- `ORIGINS`: sets origins for CORS (currently used only for TACOS API calls).
128129
- `PLATFORM_NAME`: The value set is added to the header after the MIT Libraries logo. The logic and CSS for this comes from our theme gem.
130+
- `PRIMO_NDE_VID`: The Primo view ID for NDE Only required if `FEATURE_PRIMO_NDE_LINKS` is enabled. Ask Enterprise Systems for value.
129131
- `PRIMO_TIMEOUT`: The number of seconds before a Primo request times out (default 6).
130132
- `REQUESTS_PER_PERIOD` - number of requests that can be made for general throttles per `REQUEST_PERIOD`
131133
- `REQUEST_PERIOD` - time in minutes used along with `REQUESTS_PER_PERIOD`

app/helpers/results_helper.rb

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -30,18 +30,9 @@ def tab_description
3030
# Examples from UI we are targeting:
3131
# - https://mit.primo.exlibrisgroup.com/discovery/search?query=any,contains,breakfast%20of%20champions&tab=all&search_scope=bento_catalog&vid=01MIT_INST:MIT
3232
# - https://mit.primo.exlibrisgroup.com/discovery/search?query=any,contains,breakfast%20of%20champions&tab=all&search_scope=cdi&vid=01MIT_INST:MIT
33+
# - https://mit.primo.exlibrisgroup.com/nde/search?query=breakfast%20of%20champions&tab=all&search_scope=all&vid=01MIT_INST:NDE
3334
def search_primo_link
34-
base_url = ENV.fetch('MIT_PRIMO_URL') + '/discovery/search?'
35-
base_url + search_primo_params
36-
end
37-
38-
def search_primo_params
39-
URI.encode_www_form({
40-
query: "any,contains,#{params[:q]}",
41-
tab: 'all',
42-
search_scope: 'all',
43-
vid: ENV.fetch('PRIMO_VID')
44-
})
35+
PrimoLinkBuilder.new(query_term: params[:q]).search_link
4536
end
4637

4738
# Creates MIT ArchivesSpace links based on current search term

app/helpers/search_helper.rb

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -128,11 +128,6 @@ def extract_year(date, delimiter)
128128
end
129129

130130
def primo_search_url(query_term)
131-
base_url = 'https://mit.primo.exlibrisgroup.com/discovery/search'
132-
params = {
133-
vid: ENV.fetch('PRIMO_VID'),
134-
query: "any,contains,#{query_term}"
135-
}
136-
"#{base_url}?#{params.to_query}"
131+
PrimoLinkBuilder.new(query_term: query_term).search_link
137132
end
138133
end

app/models/feature.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
#
3434
class Feature
3535
# List of all valid features in the application
36-
VALID_FEATURES = %i[bot_detection geodata boolean_picker oa_always simulate_search_latency tab_primo_all tab_timdex_all
36+
VALID_FEATURES = %i[bot_detection geodata boolean_picker oa_always primo_nde_links simulate_search_latency tab_primo_all tab_timdex_all
3737
tab_timdex_alma record_link timdex_fulltext].freeze
3838

3939
# Check if a feature is enabled by name

app/models/normalize_primo_record.rb

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -237,16 +237,10 @@ def record_link
237237
return unless @record['context']
238238

239239
record_id = @record['pnx']['control']['recordid'].join
240-
base = [ENV.fetch('MIT_PRIMO_URL'), '/discovery/fulldisplay?'].join
241-
query = {
242-
docid: record_id,
243-
vid: ENV.fetch('PRIMO_VID'),
244-
context: @record['context'],
245-
search_scope: 'all',
246-
lang: 'en',
247-
tab: ENV.fetch('PRIMO_TAB')
248-
}.to_query
249-
[base, query].join
240+
PrimoLinkBuilder.new(
241+
record_id: record_id,
242+
context: @record['context']
243+
).full_record_link
250244
end
251245

252246
def numbering

app/models/primo_link_builder.rb

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
# Builds Primo links with support for discovery and NDE UIs.
2+
#
3+
# @example Building a search link
4+
# builder = PrimoLinkBuilder.new(query_term: "machine learning")
5+
# builder.search_link
6+
# # => "https://mit.primo.exlibrisgroup.com/discovery/search?query=any%2Ccontains%2Cmachine+learning&..."
7+
#
8+
# @example Building a full record link
9+
# builder = PrimoLinkBuilder.new(record_id: "alma123", context: "L")
10+
# builder.full_record_link
11+
# # => "https://mit.primo.exlibrisgroup.com/discovery/fulldisplay?docid=alma123&..."
12+
class PrimoLinkBuilder
13+
# @param query_term [String, nil] The search query term (used for search_link)
14+
# @param record_id [String, nil] The Primo record ID (used for full_record_link)
15+
# @param context [String, nil] The Primo context code indicating record type:
16+
# - 'L' for local catalog (Alma) records
17+
# - 'PC' for CDI records
18+
# - 'ALL' for all scopes
19+
# See https://developers.exlibrisgroup.com/primo/apis/deep-links-new-ui/ and
20+
# https://knowledge.exlibrisgroup.com/Primo/Product_Documentation/Primo/Back_Office_Guide/070Monitoring_and_Maintaining_Primo/Displaying_PNX_Records_from_Primo_Front_End
21+
def initialize(query_term: nil, record_id: nil, context: nil)
22+
@query_term = query_term
23+
@record_id = record_id
24+
@context = context
25+
end
26+
27+
# Build a Primo search results link
28+
# @param tab [String] Determines which results tab to display. (default: PRIMO_TAB env var, or 'all').
29+
# For detailed documentation, see primo_search model.
30+
# @param search_scope [String] Determines which Primo scope to search. (default: PRIMO_SCOPE env var, or 'all').
31+
# For detailed documentation, see primo_search model.
32+
# @return [String] The complete search URL
33+
def search_link(tab: ENV.fetch('PRIMO_TAB', 'all'), search_scope: ENV.fetch('PRIMO_SCOPE', 'all'))
34+
return nil unless @query_term
35+
36+
base_url = "#{ENV.fetch('MIT_PRIMO_URL')}#{search_path}?"
37+
params = {
38+
query: search_query,
39+
tab: tab,
40+
search_scope: search_scope,
41+
vid: vid
42+
}
43+
base_url + URI.encode_www_form(params)
44+
end
45+
46+
# Build a Primo full record link
47+
# @param tab [String] Determines which results tab to display. (default: PRIMO_TAB env var, or 'all').
48+
# For detailed documentation, see primo_search model.
49+
# @param search_scope [String] Determines which Primo scope to search. (default: PRIMO_SCOPE env var, or 'all').
50+
# For detailed documentation, see primo_search model.
51+
# @param lang [String] The language (default: 'en')
52+
# @return [String] The complete full record URL, or nil if record_id or context is missing
53+
def full_record_link(tab: ENV.fetch('PRIMO_TAB', 'all'), search_scope: ENV.fetch('PRIMO_SCOPE', 'all'), lang: 'en')
54+
return nil unless @record_id && @context
55+
56+
base_url = "#{ENV.fetch('MIT_PRIMO_URL')}#{full_record_path}?"
57+
params = {
58+
docid: @record_id,
59+
vid: vid,
60+
context: @context,
61+
search_scope: search_scope,
62+
lang: lang,
63+
tab: tab
64+
}
65+
base_url + URI.encode_www_form(params)
66+
end
67+
68+
private
69+
70+
def search_path
71+
Feature.enabled?(:primo_nde_links) ? '/nde/search' : '/discovery/search'
72+
end
73+
74+
def full_record_path
75+
Feature.enabled?(:primo_nde_links) ? '/nde/fulldisplay' : '/discovery/fulldisplay'
76+
end
77+
78+
def vid
79+
Feature.enabled?(:primo_nde_links) ? ENV.fetch('PRIMO_NDE_VID') : ENV.fetch('PRIMO_VID')
80+
end
81+
82+
def search_query
83+
if Feature.enabled?(:primo_nde_links)
84+
@query_term
85+
else
86+
"any,contains,#{@query_term}"
87+
end
88+
end
89+
end

test/helpers/results_helper_test.rb

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -54,13 +54,16 @@ class ResultsHelperTest < ActionView::TestCase
5454
assert_equal description, tab_description
5555
end
5656

57-
test 'search_primo_params includes encoded search query' do
57+
test 'search_primo_link includes encoded search query and correct path' do
5858
params[:q] = 'breakfast of champions'
59-
result = search_primo_params
59+
link = search_primo_link
6060

61-
assert_includes result, 'query=any%2Ccontains%2Cbreakfast+of+champions'
62-
assert_includes result, 'tab=all'
63-
assert_includes result, 'search_scope=all'
61+
assert link.start_with?('https://')
62+
assert_includes link, '/discovery/search?'
63+
assert_includes link, 'query=any%2Ccontains%2Cbreakfast+of+champions'
64+
assert_includes link, 'tab=all'
65+
assert_includes link, 'search_scope=cdi'
66+
assert_includes link, 'vid=01MIT_INST%3AMIT'
6467
end
6568

6669
test 'search_primo_link returns a valid URL string' do

test/helpers/search_helper_test.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -213,13 +213,13 @@ class SearchHelperTest < ActionView::TestCase
213213

214214
test 'primo_search_url generates correct Primo URL' do
215215
query = 'machine learning'
216-
expected_url = 'https://mit.primo.exlibrisgroup.com/discovery/search?query=any%2Ccontains%2Cmachine+learning&vid=01MIT_INST%3AMIT'
216+
expected_url = 'https://mit.primo.exlibrisgroup.com/discovery/search?query=any%2Ccontains%2Cmachine+learning&tab=all&search_scope=cdi&vid=01MIT_INST%3AMIT'
217217
assert_equal expected_url, primo_search_url(query)
218218
end
219219

220220
test 'primo_search_url handles special characters in query' do
221221
query = 'data & analytics'
222-
expected_url = 'https://mit.primo.exlibrisgroup.com/discovery/search?query=any%2Ccontains%2Cdata+%26+analytics&vid=01MIT_INST%3AMIT'
222+
expected_url = 'https://mit.primo.exlibrisgroup.com/discovery/search?query=any%2Ccontains%2Cdata+%26+analytics&tab=all&search_scope=cdi&vid=01MIT_INST%3AMIT'
223223
assert_equal expected_url, primo_search_url(query)
224224
end
225225

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
# Test NDE link generation
2+
require 'test_helper'
3+
4+
class PrimoLinkBuilderTest < ActiveSupport::TestCase
5+
test 'search_link generates Primo discovery URL by default' do
6+
link = PrimoLinkBuilder.new(query_term: 'machine learning').search_link
7+
8+
assert_includes link, '/discovery/search?'
9+
assert_includes link, 'query=any%2Ccontains%2Cmachine+learning'
10+
assert_includes link, 'vid=01MIT_INST%3AMIT'
11+
end
12+
13+
test 'search_link generates Primo NDE URL when feature flag us enabled' do
14+
ClimateControl.modify(FEATURE_PRIMO_NDE_LINKS: 'true') do
15+
link = PrimoLinkBuilder.new(query_term: 'machine learning').search_link
16+
17+
assert_includes link, '/nde/search?'
18+
assert_includes link, 'query=machine+learning'
19+
assert_includes link, 'vid=01MIT_INST%3ANDE'
20+
assert_not_includes link, 'any%2Ccontains'
21+
end
22+
end
23+
24+
test 'full_record_link generates discovery URL by default' do
25+
link = PrimoLinkBuilder.new(record_id: 'alma990003098710106761', context: 'L').full_record_link
26+
27+
assert_includes link, '/discovery/fulldisplay?'
28+
assert_includes link, 'docid=alma990003098710106761'
29+
assert_includes link, 'vid=01MIT_INST%3AMIT'
30+
assert_includes link, 'context=L'
31+
end
32+
33+
test 'full_record_link generates NDE URL when feature flag enabled' do
34+
ClimateControl.modify(FEATURE_PRIMO_NDE_LINKS: 'true') do
35+
link = PrimoLinkBuilder.new(record_id: 'alma990003098710106761', context: 'L').full_record_link
36+
37+
assert_includes link, '/nde/fulldisplay?'
38+
assert_includes link, 'docid=alma990003098710106761'
39+
assert_includes link, 'vid=01MIT_INST%3ANDE'
40+
assert_includes link, 'context=L'
41+
end
42+
end
43+
44+
test 'search_link returns nil when query_term is nil' do
45+
link = PrimoLinkBuilder.new(query_term: nil).search_link
46+
47+
assert_nil link
48+
end
49+
50+
test 'full_record_link returns nil when record_id is missing' do
51+
link = PrimoLinkBuilder.new(context: 'foo').full_record_link
52+
53+
assert_nil link
54+
end
55+
56+
test 'full_record_link returns nil when context is missing' do
57+
link = PrimoLinkBuilder.new(record_id: 'alma123').full_record_link
58+
59+
assert_nil link
60+
end
61+
62+
test 'search_link generates complete URL for discovery' do
63+
builder = PrimoLinkBuilder.new(query_term: 'database security')
64+
link = builder.search_link
65+
66+
expected = 'https://mit.primo.exlibrisgroup.com/discovery/search?query=any%2Ccontains%2Cdatabase+security&tab=all&search_scope=cdi&vid=01MIT_INST%3AMIT'
67+
assert_equal expected, link
68+
end
69+
70+
test 'search_link generates complete URL for NDE' do
71+
ClimateControl.modify(FEATURE_PRIMO_NDE_LINKS: 'true') do
72+
builder = PrimoLinkBuilder.new(query_term: 'machine learning')
73+
link = builder.search_link
74+
75+
expected = 'https://mit.primo.exlibrisgroup.com/nde/search?query=machine+learning&tab=all&search_scope=cdi&vid=01MIT_INST%3ANDE'
76+
assert_equal expected, link
77+
end
78+
end
79+
80+
test 'full_record_link generates complete URL for discovery' do
81+
builder = PrimoLinkBuilder.new(record_id: 'alma990003098710106761', context: 'L')
82+
link = builder.full_record_link
83+
84+
assert link.start_with?('https://mit.primo.exlibrisgroup.com/discovery/fulldisplay?')
85+
assert_includes link, 'docid=alma990003098710106761'
86+
assert_includes link, 'context=L'
87+
assert_includes link, 'vid=01MIT_INST%3AMIT'
88+
assert_includes link, 'search_scope=cdi'
89+
assert_includes link, 'lang=en'
90+
end
91+
92+
test 'full_record_link generates complete URL for NDE' do
93+
ClimateControl.modify(FEATURE_PRIMO_NDE_LINKS: 'true') do
94+
builder = PrimoLinkBuilder.new(record_id: 'alma990003098710106761', context: 'P')
95+
link = builder.full_record_link
96+
97+
assert link.start_with?('https://mit.primo.exlibrisgroup.com/nde/fulldisplay?')
98+
assert_includes link, 'docid=alma990003098710106761'
99+
assert_includes link, 'context=P'
100+
assert_includes link, 'vid=01MIT_INST%3ANDE'
101+
assert_includes link, 'search_scope=cdi'
102+
assert_includes link, 'lang=en'
103+
end
104+
end
105+
end

0 commit comments

Comments
 (0)