• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

pulibrary / orangelight / a9945904-c4b1-47a3-9b94-042f58dd8a8e

26 Nov 2024 06:06PM UTC coverage: 96.419% (-0.3%) from 96.676%
a9945904-c4b1-47a3-9b94-042f58dd8a8e

push

circleci

web-flow
Merge pull request #4446 from pulibrary/4081-bootstrap5

[#4081] Update to bootstrap 5

4 of 5 new or added lines in 4 files covered. (80.0%)

16 existing lines in 4 files now uncovered.

6005 of 6228 relevant lines covered (96.42%)

1553.05 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

97.38
/app/services/physical_holdings_markup_builder.rb
1
# frozen_string_literal: false
2

3
class PhysicalHoldingsMarkupBuilder < HoldingRequestsBuilder
3✔
4
  include ApplicationHelper
3✔
5

6
  # Generate <span> markup used in links for browsing by call numbers
7
  # @return [String] the markup
8
  def call_number_span
3✔
9
    %(<span class="link-text">#{I18n.t('blacklight.holdings.browse')}</span>\
172✔
10
      <span class="icon-bookslibrary"></span>)
11
  end
12

13
  ##
14
  # Add call number link
15
  # @param [Hash] holding
16
  # @param [String] cn_value - a call number
17
  def call_number_link(holding, cn_value)
3✔
18
    cn = ''
173✔
19
    unless cn_value.nil?
173✔
20
      children = call_number_span
172✔
21
      cn_browse_link = link_to(children.html_safe,
172✔
22
                               "/browse/call_numbers?q=#{CGI.escape(cn_value)}",
23
                               class: 'browse-cn',
24
                               'data-original-title' => "Browse: #{cn_value}")
25
      cn = "#{holding['call_number']} #{cn_browse_link}"
172✔
26
    end
27
    content_tag(:td, cn.html_safe, class: 'holding-call-number')
173✔
28
  end
29

30
  def holding_location_repository
3✔
31
    children = content_tag(:span,
12✔
32
                           'On-site access',
33
                           class: 'availability-icon badge bg-success')
34
    content_tag(:td, children.html_safe)
12✔
35
  end
36

37
  def holding_location_scsb_span
3✔
38
    markup = content_tag(:span, '',
16✔
39
                         class: 'availability-icon badge')
40
    markup
16✔
41
  end
42

43
  def holding_location_scsb(holding, doc_id, holding_id)
3✔
44
    content_tag(:td, holding_location_scsb_span.html_safe,
16✔
45
                class: 'holding-status',
46
                data: {
47
                  'availability_record' => true,
48
                  'record_id' => doc_id,
49
                  'holding_id' => holding_id,
50
                  'scsb-barcode' => holding['items'].first['barcode'],
51
                  'aeon' => scsb_supervised_items?(holding)
52
                })
53
  end
54

55
  def holding_location_default(doc_id, holding_id, location_rules, temp_location_code)
3✔
56
    children = content_tag(:span, '', class: 'availability-icon')
145✔
57

58
    data = {
59
      'availability_record' => true,
145✔
60
      'record_id' => doc_id,
61
      'holding_id' => holding_id,
62
      aeon: self.class.aeon_location?(location_rules)
63
    }
64

65
    data['temp_location_code'] = temp_location_code unless temp_location_code.nil?
145✔
66

67
    content_tag(:td,
145✔
68
                 children.html_safe,
69
                 class: 'holding-status',
70
                 data:)
71
  end
72

73
  # Holding record with "dspace": false
74
  def holding_location_unavailable
3✔
UNCOV
75
    children = content_tag(:span,
×
76
                           'Unavailable',
77
                           class: 'availability-icon badge bg-danger')
UNCOV
78
    content_tag(:td, children.html_safe, class: 'holding-status')
×
79
  end
80

81
  def self.holding_label(label)
3✔
82
    content_tag(:li, label, class: 'holding-label')
113✔
83
  end
84

85
  def self.shelving_titles_list(holding)
3✔
86
    children = "#{holding_label('Shelving title')} #{listify_array(holding['shelving_title'])}"
1✔
87
    content_tag(:ul, children.html_safe, class: 'shelving-title')
1✔
88
  end
89

90
  def self.location_notes_list(holding)
3✔
91
    children = "#{holding_label('Location note')} #{listify_array(holding['location_note'])}"
1✔
92
    content_tag(:ul, children.html_safe, class: 'location-note')
1✔
93
  end
94

95
  def self.location_has_list(holding)
3✔
96
    children = "#{holding_label('Location has')} #{listify_array(holding['location_has'])}"
105✔
97
    content_tag(:ul, children.html_safe, class: 'location-has')
105✔
98
  end
99

100
  def self.multi_item_availability(doc_id, holding_id)
3✔
101
    content_tag(:ul, '',
174✔
102
                class: 'item-status',
103
                data: {
104
                  'record_id' => doc_id,
105
                  'holding_id' => holding_id
106
                })
107
  end
108

109
  def self.supplements_list(holding)
3✔
110
    children = "#{holding_label('Supplements')} #{listify_array(holding['supplements'])}"
1✔
111
    content_tag(:ul, children.html_safe, class: 'holding-supplements')
1✔
112
  end
113

114
  def self.indexes_list(holding)
3✔
115
    children = "#{holding_label('Indexes')} #{listify_array(holding['indexes'])}"
5✔
116
    content_tag(:ul, children.html_safe, class: 'holding-indexes')
5✔
117
  end
118

119
  def self.journal_issues_list(holding_id)
3✔
120
    content_tag(:ul, '',
83✔
121
                class: 'journal-current-issues',
122
                data: { journal: true, holding_id: })
123
  end
124

125
  def self.scsb_use_label(restriction)
3✔
126
    "#{restriction} Only"
13✔
127
  end
128

129
  # Generate the markup for record restrictions
130
  # @param holding [Hash] the restrictions for all holdings
131
  # @return [String] the markup
132
  def self.restrictions_markup(restrictions)
3✔
133
    restricted_items = restrictions.map do |value|
12✔
134
      content_tag(:td, scsb_use_label(value))
13✔
135
    end
136
    if restricted_items.length > 1
12✔
137
      list = restricted_items.map { |value| content_tag(:li, value) }
3✔
138
      content_tag(:ul, list.join.html_safe, class: 'restrictions-list item-list')
1✔
139
    else
140
      restricted_items.join.html_safe
11✔
141
    end
142
  end
143

144
  def self.open_location?(location)
3✔
145
    location.nil? ? false : location[:open]
183✔
146
  end
147

148
  def self.requestable_location?(location, adapter, holding)
3✔
149
    return false if adapter.sc_location_with_suppressed_button?(holding)
181✔
150
    if location.nil?
181✔
151
      false
2✔
152
    elsif adapter.unavailable_holding?(holding)
179✔
153
      false
×
154
    else
155
      location[:requestable]
179✔
156
    end
157
  end
158

159
  def self.aeon_location?(location)
3✔
160
    location.nil? ? false : location[:aeon_location]
556✔
161
  end
162

163
  def aeon_location?(location)
3✔
164
    self.class.aeon_location?(location)
75✔
165
  end
166

167
  def self.scsb_location?(location)
3✔
168
    location.nil? ? false : /^scsb.+/ =~ location['code']
150✔
169
  end
170

171
  def self.requestable?(adapter, holding_id, location)
3✔
172
    !adapter.alma_holding?(holding_id) || aeon_location?(location) || scsb_location?(location)
187✔
173
  end
174

175
  def self.thesis?(adapter, holding_id)
3✔
176
    holding_id == 'thesis' && adapter.pub_date > 2012
80✔
177
  end
178

179
  def self.numismatics?(holding_id)
3✔
180
    holding_id == 'numismatics'
285✔
181
  end
182

183
  # Generate the CSS class for holding based upon its location and ID
184
  # @param adapter [HoldingRequestsAdapter] adapter for the Solr Document and Bibdata
185
  # @param location [Hash] location information
186
  # @param holding_id [String]
187
  # @return [String] the CSS class
188
  def self.show_request(adapter, location, holding_id)
3✔
189
    if requestable?(adapter, holding_id, location) && !thesis?(adapter, holding_id) || numismatics?(holding_id)
187✔
190
      'service-always-requestable'
76✔
191
    else
192
      'service-conditional'
111✔
193
    end
194
  end
195

196
  # Generate the location services markup for a holding
197
  # @param adapter [HoldingRequestsAdapter] adapter for the Solr Document and Bibdata
198
  # @param holding_id [String]
199
  # @param location_rules [Hash]
200
  # @param link [String] link markup
201
  # @return [String] block markup
202
  def self.location_services_block(adapter, holding_id, location_rules, link, holding)
3✔
203
    content_tag(:td, link,
181✔
204
                class: "location-services #{show_request(adapter, location_rules, holding_id)}",
205
                data: {
206
                  open: open_location?(location_rules),
207
                  requestable: requestable_location?(location_rules, adapter, holding),
208
                  aeon: aeon_location?(location_rules),
209
                  holding_id:
210
                })
211
  end
212

213
  def self.scsb_supervised_items?(holding)
3✔
214
    if holding.key? 'items'
16✔
215
      restricted_items = holding['items'].select do |item|
16✔
216
        item['use_statement'] == 'Supervised Use'
125✔
217
      end
218
      restricted_items.count == holding['items'].count
16✔
219
    else
220
      false
×
221
    end
222
  end
223

224
  def scsb_supervised_items?(holding)
3✔
225
    self.class.scsb_supervised_items?(holding)
16✔
226
  end
227

228
  ##
229
  def self.listify_array(arr)
3✔
230
    arr = arr.map do |e|
113✔
231
      content_tag(:li, e)
191✔
232
    end
233
    arr.join
113✔
234
  end
235

236
  def doc_id(holding)
3✔
237
    holding.dig("mms_id") || adapter.doc_id
526✔
238
  end
239

240
  # Example of a temporary holding, in this case holding_id is : firestone$res3hr
241
  # {\"firestone$res3hr\":{\"location_code\":\"firestone$res3hr\",
242
  # \"current_location\":\"Circulation Desk (3 Hour Reserve)\",\"current_library\":\"Firestone Library\",
243
  # \"call_number\":\"HT1077 .M87\",\"call_number_browse\":\"HT1077 .M87\",
244
  # \"items\":[{\"holding_id\":\"22740601020006421\",\"id\":\"23740600990006421\",
245
  # \"status_at_load\":\"1\",\"barcode\":\"32101005621469\",\"copy_number\":\"1\"}]}}
246
  def self.temporary_holding_id?(holding_id)
3✔
247
    /[a-zA-Z]\$[a-zA-Z]/.match?(holding_id)
29✔
248
  end
249

250
  # When it is a temporary location and is requestable, use the first holding_id of this temporary location items.
251
  def self.temporary_location_holding_id_first(holding)
3✔
252
    holding["items"][0]["holding_id"]
1✔
253
  end
254

255
  # Generate the links for a given holding
256
  # TODO: Come back and remove class method calls
257
  def request_placeholder(adapter, holding_id, location_rules, holding)
3✔
258
    doc_id = doc_id(holding)
180✔
259
    view_base = ActionView::Base.new(ActionView::LookupContext.new([]), {}, nil)
180✔
260
    link = request_link_component(adapter:, holding_id:, doc_id:, holding:, location_rules:).render_in(view_base)
180✔
261
    markup = self.class.location_services_block(adapter, holding_id, location_rules, link, holding)
180✔
262
    markup
180✔
263
  end
264

265
  def request_link_component(adapter:, holding_id:, doc_id:, holding:, location_rules:)
3✔
266
    holding_object = Requests::Holding.new(mfhd_id: holding_id, holding_data: holding)
180✔
267
    if holding_id == 'thesis' || self.class.numismatics?(holding_id)
180✔
268
      AeonRequestButtonComponent.new(document: adapter.document, holding: holding_object.to_h, url_class: Requests::NonAlmaAeonUrl)
14✔
269
    elsif holding['items'] && holding['items'].length > 1
166✔
270
      RequestButtonComponent.new(doc_id:, holding_id:, location: location_rules)
91✔
271
    elsif aeon_location?(location_rules)
75✔
272
      AeonRequestButtonComponent.new(document: adapter.document, holding: holding_object.to_h)
35✔
273
    elsif self.class.scsb_location?(location_rules)
40✔
274
      RequestButtonComponent.new(doc_id:, location: location_rules, holding:)
11✔
275
    elsif self.class.temporary_holding_id?(holding_id)
29✔
276
      holding_identifier = self.class.temporary_location_holding_id_first(holding)
1✔
277
      RequestButtonComponent.new(doc_id:, holding_id: holding_identifier, location: location_rules)
1✔
278
    else
279
      RequestButtonComponent.new(doc_id:, holding_id:, location: location_rules)
28✔
280
    end
281
  end
282

283
  attr_reader :adapter
3✔
284
  delegate :content_tag, :link_to, to: :class
3✔
285

286
  # Constructor
287
  # @param adapter [HoldingRequestsAdapter] adapter for the SolrDocument and Bibdata API
288
  def initialize(adapter)
3✔
289
    @adapter = adapter
113✔
290
  end
291

292
  # Builds the markup for online and physical holdings for a given record
293
  # @return [String] the markup for the online and physical holdings
294
  def build
3✔
295
    physical_holdings_block
101✔
296
  end
297

298
  # Generate a <span> element for a holding location
299
  # @param location [String] the location value
300
  # @param holding_id [String] the ID for the holding
301
  # @return [String] <span> markup
302
  def holding_location_span(location, holding_id)
3✔
303
    content_tag(:span, location,
178✔
304
                class: 'location-text',
305
                data: { location: true, holding_id: })
306
  end
307

308
  # Generate the link for a specific holding
309
  # @param holding [Hash] the information for the holding
310
  # @param location [Hash] the location information for the holding
311
  # @param holding_id [String] the ID for the holding
312
  # @param call_number [String] the call number
313
  # @param library [String] the library in which the holding resides
314
  # @param [String] the markup
315
  def locate_link(location, call_number, library, holding)
3✔
316
    locator = StackmapLocationFactory.new(resolver_service: ::StackmapService::Url)
177✔
317
    return '' if locator.exclude?(call_number:, library:)
177✔
318

319
    markup = ''
129✔
320
    markup = stackmap_span_markup(location, library, holding) if find_it_location?(location)
129✔
321
    ' ' + markup
129✔
322
  end
323

324
  def stackmap_span_markup(location, library, holding)
3✔
325
    content_tag(:span, '',
101✔
326
                data: {
327
                  'map-location': location.to_s,
328
                  'location-library': library,
329
                  'location-name': holding['location']
330
                })
331
  end
332

333
  # Generate the links for a specific holding
334
  # @param holding [Hash] the information for the holding
335
  # @param location [Hash] the location information for the holding
336
  # @param holding_id [String] the ID for the holding
337
  # @param call_number [String] the call number
338
  # @param [String] the markup
339
  def holding_location_container(holding, location, holding_id, call_number)
3✔
340
    markup = holding_location_span(location, holding_id)
177✔
341
    link_markup = locate_link(holding['location_code'], call_number, holding['library'], holding)
177✔
342
    markup << link_markup.html_safe
177✔
343
    markup
177✔
344
  end
345

346
  # Generate the markup block for a specific holding
347
  # @param holding [Hash] the information for the holding
348
  # @param location [Hash] the location information for the holding
349
  # @param holding_id [String] the ID for the holding
350
  # @param call_number [String] the call number
351
  # @param [String] the markup
352
  def holding_location(holding, location, holding_id, call_number)
3✔
353
    location = holding_location_container(holding, location, holding_id, call_number)
177✔
354
    markup = ''
177✔
355
    markup << content_tag(:td, location.html_safe,
177✔
356
                          class: 'library-location',
357
                          data: { holding_id: })
358
    markup
177✔
359
  end
360

361
  private
3✔
362

363
    # Generate the markup for a physical holding record
364
    # @param holding [Hash] holding information from a Solr Document
365
    # @param holding_id [String] the ID for the holding record
366
    # @return [String] the markup
367
    def process_physical_holding(holding, holding_id)
3✔
368
      markup = ''
173✔
369
      doc_id = doc_id(holding)
173✔
370
      temp_location_code = @adapter.temp_location_code(holding)
173✔
371

372
      location_rules = @adapter.holding_location_rules(holding)
173✔
373
      cn_value = @adapter.call_number(holding)
173✔
374

375
      holding_loc = @adapter.holding_location_label(holding)
173✔
376
      if holding_loc.present?
173✔
377
        markup = holding_location(
173✔
378
          holding,
379
          holding_loc,
380
          holding_id,
381
          cn_value
382
        )
383
      end
384
      markup << call_number_link(holding, cn_value)
173✔
385
      markup << if @adapter.repository_holding?(holding)
173✔
386
                  holding_location_repository
12✔
387
                elsif @adapter.scsb_holding?(holding) && !@adapter.empty_holding?(holding)
161✔
388
                  holding_location_scsb(holding, doc_id, holding_id)
16✔
389
                elsif @adapter.unavailable_holding?(holding)
145✔
UNCOV
390
                  holding_location_unavailable
×
391
                else
392
                  holding_location_default(doc_id,
145✔
393
                                           holding_id,
394
                                           location_rules,
395
                                           temp_location_code)
396
                end
397

398
      request_placeholder_markup = request_placeholder(@adapter, holding_id, location_rules, holding)
173✔
399
      markup << request_placeholder_markup.html_safe
173✔
400

401
      markup << build_holding_notes(holding, holding_id)
173✔
402

403
      markup = self.class.holding_block(markup) unless markup.empty?
173✔
404
      markup
173✔
405
    end
406

407
    def build_holding_notes(holding, holding_id)
3✔
408
      holding_notes = ''
173✔
409

410
      holding_notes << self.class.shelving_titles_list(holding) if @adapter.shelving_title?(holding)
173✔
411
      holding_notes << self.class.location_notes_list(holding) if @adapter.location_note?(holding)
173✔
412
      holding_notes << self.class.location_has_list(holding) if @adapter.location_has?(holding)
173✔
413
      holding_notes << self.class.multi_item_availability(doc_id(holding), holding_id)
173✔
414
      holding_notes << self.class.supplements_list(holding) if @adapter.supplements?(holding)
173✔
415
      holding_notes << self.class.indexes_list(holding) if @adapter.indexes?(holding)
173✔
416
      holding_notes << self.class.journal_issues_list(holding_id) if @adapter.journal?
173✔
417

418
      self.class.holding_details(holding_notes) unless holding_notes.empty?
173✔
419
    end
420

421
    # Generate the markup for physical holdings
422
    # @return [String] the markup
423
    def physical_holdings
3✔
424
      markup = ''
101✔
425
      @adapter.sorted_physical_holdings.each do |holding_id, holding|
101✔
426
        markup << process_physical_holding(holding, holding_id)
173✔
427
      end
428
      markup
101✔
429
    end
430

431
    # Generate the markup block for physical holdings
432
    # @return [String] the markup
433
    def physical_holdings_block
3✔
434
      markup = ''
101✔
435
      children = physical_holdings
101✔
436
      markup = self.class.content_tag(:tbody, children.html_safe) unless children.empty?
101✔
437
      markup
101✔
438
    end
439
end
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc