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

pulibrary / pdc_discovery / 5c8f78ec-7ed2-40cf-b5a0-80a009270825

17 Jun 2026 07:04PM UTC coverage: 78.246% (-18.2%) from 96.464%
5c8f78ec-7ed2-40cf-b5a0-80a009270825

Pull #951

circleci

leefaisonr
Additional work for withdrawn works landing page
Co-authored-by: Carolyn Cole <carolyncole@users.noreply.github.com>
Pull Request #951: adding withdrawn json with updated metadata

2 of 4 new or added lines in 2 files covered. (50.0%)

150 existing lines in 5 files now uncovered.

2435 of 3112 relevant lines covered (78.25%)

4.82 hits per line

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

71.43
/app/models/solr_document.rb
1
# frozen_string_literal: true
2

3
# rubocop:disable Metrics/ClassLength
4
class SolrDocument
1✔
5
  include Blacklight::Solr::Document
1✔
6

7
  field_semantics.merge!(
1✔
8
    title: 'title_tesim',
9
    contributor: 'author_tesim',
10
    format: 'genre_ssim',
11
    date: 'issue_date_ssim'
12
  )
13

14
  # self.unique_key = 'id'
15

16
  # Email uses the semantic field mappings below to generate the body of an email.
17
  SolrDocument.use_extension(Blacklight::Document::Email)
1✔
18

19
  # SMS uses the semantic field mappings below to generate the body of an SMS email.
20
  SolrDocument.use_extension(Blacklight::Document::Sms)
1✔
21

22
  # DublinCore uses the semantic field mappings below to assemble an OAI-compliant Dublin Core document
23
  # Semantic mappings of solr stored fields. Fields may be multi or
24
  # single valued. See Blacklight::Document::SemanticFields#field_semantics
25
  # and Blacklight::Document::SemanticFields#to_semantic_values
26
  # Recommendation: Use field names from Dublin Core
27
  use_extension(Blacklight::Document::DublinCore)
1✔
28

29
  ABSTRACT_FIELD = 'abstract_tsim'
1✔
30
  DESCRIPTION_FIELD = 'description_tsim'
1✔
31
  ISSUED_DATE_FIELD = 'issue_date_ssim'
1✔
32
  METHODS_FIELD = 'methods_tsim'
1✔
33
  TITLE_FIELD = 'title_tesim'
1✔
34

35
  # These icons map to CSS classes in Bootstrap
36
  ICONS = {
1✔
37
    "dataset" => "bi-stack",
38
    "moving image" => "bi-film",
39
    "software" => "bi-code-slash",
40
    "image" => "bi-image",
41
    "text" => "bi-card-text",
42
    "collection" => "bi-collection-fill",
43
    "article" => "bi-journal-text",
44
    "interactive resource" => "bi-pc-display-horizontal"
45
  }.freeze
46

47
  # .*?\s is lazy match so the regex stop as soon as space is found
48
  GLOBUS_URI_REGEX = /.*(https\:\/\/app.globus.org\/file-manager.*?\s).*/.freeze
1✔
49

50
  def id
1✔
51
    fetch('id')
14✔
52
  end
53

54
  def titles
1✔
55
    fetch(TITLE_FIELD, [])
13✔
56
  end
57

58
  def title
1✔
59
    titles.first
13✔
60
  end
61

62
  # Returns the list of author names (ordered if possible)
63
  def authors
1✔
64
    authors_ordered.map(&:value)
9✔
65
  end
66

67
  # Returns the list of authors with all their information including
68
  # name, ORCID, and affiliation. List is ordered if possible.
69
  def authors_ordered
1✔
70
    @authors_ordered ||= begin
22✔
71
      authors_json = fetch('authors_json_ss', nil)
13✔
72
      if authors_json
13✔
73
        # PDC Describe records contain this field;
74
        # get the author data and sort it.
75
        authors = JSON.parse(authors_json).map { |hash| Author.new(hash) }
46✔
76
        authors.sort_by(&:sequence)
11✔
77
      else
78
        []
2✔
79
      end
80
    end
81
  end
82

83
  # Returns a string with the authors and shortens it if there are more than 2 authors.
84
  # https://owl.purdue.edu/owl/research_and_citation/apa_style/apa_formatting_and_style_guide/in_text_citations_author_authors.html
85
  def authors_et_al
1✔
86
    authors_all = authors
5✔
87
    if authors_all.count <= 2
5✔
88
      authors_all.join(" & ")
3✔
89
    else
90
      authors_all.first + " et al."
2✔
91
    end
92
  end
93

94
  def creators
1✔
UNCOV
95
    fetch('creator_tesim', [])
×
96
  end
97

98
  def community_path
1✔
99
    communities.first
1✔
100
  end
101

102
  def communities
1✔
103
    fetch("communities_ssim", [])
1✔
104
  end
105

106
  def subcommunities
1✔
UNCOV
107
    fetch("subcommunities_ssim", [])
×
108
  end
109

110
  def collection_name
1✔
111
    fetch("collection_name_ssi", "")
1✔
112
  end
113

114
  def collection_tags
1✔
UNCOV
115
    fetch("collection_tag_ssim", [])
×
116
  end
117

118
  def contributors
1✔
UNCOV
119
    fetch("contributor_tsim", [])
×
120
  end
121

122
  def accessioned_dates
1✔
UNCOV
123
    fetch("date_accessioned_ssim", [])
×
124
  end
125

126
  def accessioned_date
1✔
UNCOV
127
    accessioned_dates.first
×
128
  end
129

130
  def issued_dates
1✔
131
    fetch(ISSUED_DATE_FIELD, [])
5✔
132
  end
133

134
  def issued_date
1✔
135
    issued_dates.first
5✔
136
  end
137

138
  def abstracts
1✔
139
    fetch(ABSTRACT_FIELD, [])
2✔
140
  end
141

142
  def abstract
1✔
143
    abstracts.first
2✔
144
  end
145

146
  def descriptions
1✔
147
    fetch(DESCRIPTION_FIELD, [])
7✔
148
  end
149

150
  def description
1✔
151
    descriptions.first
7✔
152
  end
153

154
  def methods
1✔
155
    fetch(METHODS_FIELD, [])
1✔
156
  end
157

158
  def data_source
1✔
159
    fetch("data_source_ssi", "pdc_describe")
1✔
160
  end
161

162
  def pdc_describe_record?
1✔
UNCOV
163
    data_source == "pdc_describe"
×
164
  end
165

166
  def files
1✔
167
    @files ||= begin
17✔
168
      data = JSON.parse(fetch("pdc_describe_json_ss", "{}"))["files"] || []
8✔
169
      data.map { |file| DatasetFile.from_hash(file) }.sort_by(&:sequence)
26✔
170
    end
171
  end
172

173
  def total_file_size
1✔
174
    total = 0
6✔
175
    files.each do |file|
6✔
176
      total += file.size.to_i
15✔
177
    end
178
    total
6✔
179
  end
180

181
  # Returns an array with the counts by file extension
182
  # e.g. [{extension: "txt", file_count: 3}, {extension: "csv", file_count: 1}]
183
  def file_counts
1✔
184
    groups = files.group_by(&:extension)
3✔
185
    groups.map { |key, value| { extension: key, file_count: value.count } }.sort_by { |group| -group[:file_count] }
11✔
186
  end
187

188
  def funders
1✔
UNCOV
189
    funders_string = fetch("funders_ss", "[]")
×
UNCOV
190
    @funders = JSON.parse(funders_string)
×
UNCOV
191
    @funders
×
192
  end
193

194
  def table_of_contents
1✔
UNCOV
195
    fetch("tableofcontents_tesim", [])
×
196
  end
197

198
  def referenced_by
1✔
UNCOV
199
    fetch("referenced_by_ssim", [])
×
200
  end
201

202
  def uri
1✔
203
    fetch("uri_ssim", [])
13✔
204
  end
205

206
  def doi_url
1✔
207
    uri.each do |link|
12✔
208
      return link if link.downcase.start_with?('https://doi.org/')
8✔
209
    end
210
    nil
211
  end
212

213
  def doi_value
1✔
214
    doi_url&.gsub('https://doi.org/', '')
2✔
215
  end
216

217
  def format
1✔
UNCOV
218
    fetch("format_ssim", [])
×
219
  end
220

221
  def globus_uri
1✔
222
    # First we try to use the value indexed (only exists for PDC Describe records)
223
    indexed_uri = fetch("globus_uri_ssi", nil)
4✔
224
    return indexed_uri unless indexed_uri.nil?
4✔
225

226
    # ...then check the links indexed to see if one of them looks like a Globus URI
227
    uri.each do |link|
1✔
228
      return link if link.downcase.start_with?('https://app.globus.org/')
2✔
229
    end
230

231
    # ...if all fails, see if there is a Globus URI in the description
232
    globus_uri_from_description
×
233
  end
234

235
  def globus_uri_from_description
1✔
236
    match = description&.match(GLOBUS_URI_REGEX)
3✔
237
    match.captures.first.strip if match
3✔
238
  end
239

240
  def extent
1✔
UNCOV
241
    fetch("extent_ssim", [])
×
242
  end
243

244
  def medium
1✔
UNCOV
245
    fetch("medium_ssim", [])
×
246
  end
247

248
  def mimetype
1✔
UNCOV
249
    fetch("mimetype_ssim", [])
×
250
  end
251

252
  def language
1✔
UNCOV
253
    fetch("language_ssim", [])
×
254
  end
255

256
  def publisher
1✔
257
    fetch("publisher_ssim", [])
4✔
258
  end
259

260
  def publisher_place
1✔
UNCOV
261
    fetch("publisher_place_ssim", [])
×
262
  end
263

264
  def publisher_corporate
1✔
UNCOV
265
    fetch("publisher_corporate_ssim", [])
×
266
  end
267

268
  def related_identifiers
1✔
UNCOV
269
    @related_identifiers ||= begin
×
UNCOV
270
      hash = JSON.parse(fetch("pdc_describe_json_ss", "{}"))
×
UNCOV
271
      hash.dig("resource", "related_objects") || []
×
272
    end
273
  end
274

275
  def relation
1✔
UNCOV
276
    fetch("relation_ssim", [])
×
277
  end
278

279
  def relation_is_format_of
1✔
UNCOV
280
    fetch("relation_is_format_of_ssim", [])
×
281
  end
282

283
  def relation_has_format
1✔
UNCOV
284
    fetch("relation_has_format_ssim", [])
×
285
  end
286

287
  def relation_is_part_of
1✔
UNCOV
288
    fetch("relation_is_part_of_ssim", [])
×
289
  end
290

291
  def relation_is_part_of_series
1✔
UNCOV
292
    fetch("relation_is_part_of_series_ssim", [])
×
293
  end
294

295
  def relation_has_part
1✔
UNCOV
296
    fetch("relation_has_part_ssim", [])
×
297
  end
298

299
  def relation_is_version_of
1✔
UNCOV
300
    fetch("relation_is_version_of_ssim", [])
×
301
  end
302

303
  def relation_has_version
1✔
UNCOV
304
    fetch("relation_has_version_ssim", [])
×
305
  end
306

307
  def version_number
1✔
308
    fetch("version_number_ssi", "")
6✔
309
  end
310

311
  def relation_is_based_on
1✔
UNCOV
312
    fetch("relation_is_based_on_ssim", [])
×
313
  end
314

315
  def relation_is_referenced_by
1✔
UNCOV
316
    fetch("relation_is_referenced_by_ssim", [])
×
317
  end
318

319
  def relation_is_required_by
1✔
UNCOV
320
    fetch("relation_is_required_by_ssim", [])
×
321
  end
322

323
  def relation_requires
1✔
UNCOV
324
    fetch("relation_requires_ssim", [])
×
325
  end
326

327
  def relation_replaces
1✔
UNCOV
328
    fetch("relation_replaces_ssim", [])
×
329
  end
330

331
  def relation_is_replaced_by
1✔
UNCOV
332
    fetch("relation_is_replaced_by_ssim", [])
×
333
  end
334

335
  def relation_uri
1✔
UNCOV
336
    fetch("relation_uri_ssim", [])
×
337
  end
338

339
  # For PDC Describe records we have a single value for the name and the uri
340
  # and we can safely assume they are related.
341
  def rights_name_and_uri
1✔
UNCOV
342
    name = fetch("rights_name_ssi", nil)
×
UNCOV
343
    uri = fetch("rights_uri_ssi", nil)
×
UNCOV
344
    return nil if name.nil? || uri.nil?
×
UNCOV
345
    { name: name, uri: uri }
×
346
  end
347

348
  def rights_holder
1✔
UNCOV
349
    fetch("rights_holder_ssim", [])
×
350
  end
351

352
  # PDC Describe records have enhanced license information (e.g. the name, the identifier, and a URL)
353
  def rights_enhanced
1✔
354
    @rights_enhanced ||= begin
4✔
355
      hash = JSON.parse(fetch("pdc_describe_json_ss", "{}"))
4✔
356
      hash.dig("resource", "rights_many") || []
4✔
357
    end
358
  end
359

360
  def subject
1✔
361
    fetch("subject_all_ssim", [])
3✔
362
  end
363

364
  def subject_classification
1✔
UNCOV
365
    fetch("subject_classification_tesim", [])
×
366
  end
367

368
  def subject_ddc
1✔
UNCOV
369
    fetch("subject_ddc_tesim", [])
×
370
  end
371

372
  def subject_lcc
1✔
UNCOV
373
    fetch("subject_lcc_tesim", [])
×
374
  end
375

376
  def subject_lcsh
1✔
UNCOV
377
    fetch("subject_lcsh_tesim", [])
×
378
  end
379

380
  def subject_mesh
1✔
UNCOV
381
    fetch("subject_mesh_tesim", [])
×
382
  end
383

384
  def subject_other
1✔
UNCOV
385
    fetch("subject_other_tesim", [])
×
386
  end
387

388
  def alternative_title
1✔
UNCOV
389
    fetch("alternative_title_tesim", [])
×
390
  end
391

392
  def genres
1✔
393
    fetch("genre_ssim", []).sort
4✔
394
  end
395

396
  # Sometimes we need a single genre for an item, even though an item may have more than one.
397
  # This method makes sure we always get the same value (the first one from a sorted list).
398
  def genre
1✔
399
    genres.first
4✔
400
  end
401

402
  def peer_review_status
1✔
UNCOV
403
    fetch("peer_review_status_ssim", [])
×
404
  end
405

406
  def translator
1✔
UNCOV
407
    fetch("translator_ssim", [])
×
408
  end
409

410
  def isan
1✔
UNCOV
411
    fetch("isan_ssim", [])
×
412
  end
413

414
  def access_rights
1✔
UNCOV
415
    fetch("access_rights_ssim", [])
×
416
  end
417

418
  def funding_agency
1✔
UNCOV
419
    fetch("funding_agency_ssim", [])
×
420
  end
421

422
  def provenance
1✔
UNCOV
423
    fetch("provenance_ssim", [])
×
424
  end
425

426
  def license
1✔
UNCOV
427
    fetch("license_ssim", [])
×
428
  end
429

430
  def accrual_method
1✔
UNCOV
431
    fetch("accrual_method_ssim", [])
×
432
  end
433

434
  def accrual_periodicity
1✔
UNCOV
435
    fetch("accrual_periodicity_ssim", [])
×
436
  end
437

438
  def accrual_policy
1✔
UNCOV
439
    fetch("accrual_policy_ssim", [])
×
440
  end
441

442
  def audience
1✔
UNCOV
443
    fetch("audience_ssim", [])
×
444
  end
445

446
  def available
1✔
UNCOV
447
    fetch("available_ssim", [])
×
448
  end
449

450
  def bibliographic_citation
1✔
UNCOV
451
    fetch("bibliographic_citation_ssim", [])
×
452
  end
453

454
  def conforms_to
1✔
UNCOV
455
    fetch("conforms_to_ssim", [])
×
456
  end
457

458
  def coverage
1✔
UNCOV
459
    fetch("coverage_tesim", [])
×
460
  end
461

462
  def spatial_coverage
1✔
UNCOV
463
    fetch("spatial_coverage_tesim", [])
×
464
  end
465

466
  def temporal_coverage
1✔
UNCOV
467
    fetch("temporal_coverage_tesim", [])
×
468
  end
469

470
  def date_created
1✔
471
    fetch("issue_date_strict_ssi", nil)
4✔
472
  end
473

474
  def dates_submitted
1✔
UNCOV
475
    fetch("date_submitted_ssim", [])
×
476
  end
477

478
  def date_submitted
1✔
UNCOV
479
    dates_submitted.first
×
480
  end
481

482
  def dates_accepted
1✔
UNCOV
483
    fetch("date_accepted_ssim", [])
×
484
  end
485

486
  def date_accepted
1✔
UNCOV
487
    dates_accepted.first
×
488
  end
489

490
  def dates_copyrighted
1✔
UNCOV
491
    fetch("copyright_date_ssim", [])
×
492
  end
493

494
  def date_copyrighted
1✔
UNCOV
495
    dates_copyrighted.first
×
496
  end
497

498
  def date_modified
1✔
499
    # pdc describe - pdc_updated_at_dtsi comes as a string
500
    # we want to make sure the formatting is consistent
501
    pdc_date = fetch("pdc_updated_at_dtsi", nil)
6✔
502
    begin
503
      DateTime.parse(pdc_date).strftime('%Y-%m-%d')
6✔
504
    rescue
505
      nil
3✔
506
    end
507
  end
508

509
  def dates_valid
1✔
UNCOV
510
    fetch("date_valid_ssim", [])
×
511
  end
512

513
  def education_level
1✔
UNCOV
514
    fetch("education_level_ssim", [])
×
515
  end
516

517
  def other_identifier
1✔
UNCOV
518
    fetch("other_identifier_ssim", [])
×
519
  end
520

521
  def instructional_method
1✔
UNCOV
522
    fetch("instructional_method_ssim", [])
×
523
  end
524

525
  def mediator
1✔
UNCOV
526
    fetch("mediator_ssim", [])
×
527
  end
528

529
  def source
1✔
UNCOV
530
    fetch("source_ssim", [])
×
531
  end
532

533
  def domains
1✔
UNCOV
534
    fetch("domain_ssim", "")
×
535
  end
536

537
  def icon_css
1✔
538
    ICONS[genre&.downcase] || 'bi-file-earmark-fill'
3✔
539
  end
540

541
  # Returns a DatasetCitation object for the current document
542
  def citation
1✔
543
    @citation ||= begin
5✔
544
      year_available = fetch('year_available_itsi', nil)
4✔
545
      years = year_available ? [year_available.to_s] : []
4✔
546
      DatasetCitation.new(authors, years, title, 'Data set', publisher.first, doi_url, version_number)
4✔
547
    end
548
  end
549

550
  # Returns a string with the indicated citation style (e.g. APA or BibTeX)
551
  def cite(style)
1✔
552
    citation.to_s(style)
1✔
553
  end
554

555
  # Returns the ID for a BibTeX citation for this document.
556
  # rubocop:disable Rails/Delegate
557
  def bibtex_id
1✔
558
    citation.bibtex_id
2✔
559
  end
560
  # rubocop:enable Rails/Delegate
561

562
  # Access and parse the embargo date timestamp
563
  # @return [Date]
564
  def embargo_date
1✔
565
    value = fetch("embargo_date_dtsi", nil)
12✔
566
    return if value.nil?
12✔
567

568
    Date.parse(value)
11✔
569
  rescue Date::Error => date_error
570
    Rails.logger.warn("Failed to parse the embargo date value for #{id}: #{value}. The error was: #{date_error}")
2✔
571
    # This ensures that works under embargo with invalid embargo dates are still inaccessible
572
    @embargo_invalid = true
2✔
573
    nil
2✔
574
  end
575

576
  # Determines whether or not the embargo is invalid
577
  # @return [Boolean]
578
  def embargo_invalid?
1✔
579
    embargo_date if @embargo_date.nil?
3✔
580
    @embargo_invalid
3✔
581
  end
582

583
  # Determine if the PDC Describe resource is under active embargo
584
  # @return [Boolean]
585
  def embargoed?
1✔
586
    return true if embargo_invalid?
3✔
587
    return false if embargo_date.blank?
2✔
588

589
    current_date = Time.zone.now
2✔
590
    embargo_date >= current_date
2✔
591
  end
592
end
593
# rubocop:enable Metrics/ClassLength
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

© 2026 Coveralls, Inc