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

pulibrary / pdc_describe / d9e091ce-355b-4326-b126-bc09ea9897f4

30 Nov 2023 04:44PM UTC coverage: 95.508% (+0.02%) from 95.493%
d9e091ce-355b-4326-b126-bc09ea9897f4

Pull #1620

circleci

carolyncole
Moving work validation to a Service before we make it more complex
Pull Request #1620: Moving work validation to a Service before we make it more complex

87 of 87 new or added lines in 3 files covered. (100.0%)

30 existing lines in 2 files now uncovered.

3062 of 3206 relevant lines covered (95.51%)

200.63 hits per line

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

90.91
/app/models/work.rb
1
# frozen_string_literal: true
2

3
# rubocop:disable Metrics/ClassLength
4
class Work < ApplicationRecord
1✔
5
  # Errors for cases where there is no valid Group
6
  class InvalidGroupError < ::ArgumentError; end
1✔
7

8
  has_many :work_activity, -> { order(updated_at: :desc) }, dependent: :destroy
273✔
9
  has_many :user_work, -> { order(updated_at: :desc) }, dependent: :destroy
9✔
10
  has_many :upload_snapshots, -> { order(updated_at: :desc) }, dependent: :destroy
274✔
11
  has_many_attached :pre_curation_uploads, service: :amazon_pre_curation
1✔
12

13
  belongs_to :group, class_name: "Group"
1✔
14
  belongs_to :curator, class_name: "User", foreign_key: "curator_user_id", optional: true
1✔
15

16
  attribute :work_type, :string, default: "DATASET"
1✔
17
  attribute :profile, :string, default: "DATACITE"
1✔
18

19
  attr_accessor :user_entered_doi
1✔
20

21
  alias state_history user_work
1✔
22

23
  delegate :valid_to_submit, :valid_to_draft, to: :work_validator
1✔
24

25
  include AASM
1✔
26

27
  aasm column: :state do
1✔
28
    state :none, initial: true
1✔
29
    state :draft, :awaiting_approval, :approved, :withdrawn, :deletion_marker
1✔
30

31
    event :draft, after: :draft_doi do
1✔
32
      transitions from: :none, to: :draft, guard: :valid_to_draft
1✔
33
    end
34

35
    event :complete_submission do
1✔
36
      transitions from: :draft, to: :awaiting_approval, guard: :valid_to_submit
1✔
37
    end
38

39
    event :request_changes do
1✔
40
      transitions from: :awaiting_approval, to: :awaiting_approval, guard: :valid_to_submit
1✔
41
    end
42

43
    event :approve do
1✔
44
      transitions from: :awaiting_approval, to: :approved, guard: :valid_to_approve, after: :publish
1✔
45
    end
46

47
    event :withdraw do
1✔
48
      transitions from: [:draft, :awaiting_approval, :approved], to: :withdrawn
1✔
49
    end
50

51
    event :resubmit do
1✔
52
      transitions from: :withdrawn, to: :draft
1✔
53
    end
54

55
    event :remove do
1✔
56
      transitions from: :withdrawn, to: :deletion_marker
1✔
57
    end
58

59
    after_all_events :track_state_change
1✔
60
  end
61

62
  def state=(new_state)
1✔
63
    new_state_sym = new_state.to_sym
591✔
64
    valid_states = self.class.aasm.states.map(&:name)
591✔
65
    raise(StandardError, "Invalid state '#{new_state}'") unless valid_states.include?(new_state_sym)
591✔
66
    aasm_write_state_without_persistence(new_state_sym)
590✔
67
  end
68

69
  ##
70
  # Is this work editable by a given user?
71
  # A work is editable when:
72
  # * it is being edited by the person who made it
73
  # * it is being edited by a group admin of the group where is resides
74
  # * it is being edited by a super admin
75
  # @param [User]
76
  # @return [Boolean]
77
  def editable_by?(user)
1✔
78
    submitted_by?(user) || administered_by?(user)
250✔
79
  end
80

81
  def editable_in_current_state?(user)
1✔
82
    # anyone with edit privleges can edit a work while it is in draft or awaiting approval
83
    return editable_by?(user) if draft? || awaiting_approval?
154✔
84

85
    # Only admisitrators can edit a work in other states
86
    administered_by?(user)
23✔
87
  end
88

89
  def submitted_by?(user)
1✔
90
    created_by_user_id == user.id
250✔
91
  end
92

93
  def administered_by?(user)
1✔
94
    user.has_role?(:group_admin, group)
82✔
95
  end
96

97
  class << self
1✔
98
    def find_by_doi(doi)
1✔
99
      prefix = "10.34770/"
38✔
100
      doi = "#{prefix}#{doi}" unless doi.blank? || doi.start_with?(prefix)
38✔
101
      Work.find_by!("metadata @> ?", JSON.dump(doi:))
38✔
102
    end
103

104
    def find_by_ark(ark)
1✔
105
      prefix = "ark:/"
299✔
106
      ark = "#{prefix}#{ark}" unless ark.blank? || ark.start_with?(prefix)
299✔
107
      Work.find_by!("metadata @> ?", JSON.dump(ark:))
299✔
108
    end
109

110
    delegate :resource_type_general_values, to: PDCMetadata::Resource
1✔
111
  end
112

113
  include Rails.application.routes.url_helpers
1✔
114

115
  before_save do |work|
1✔
116
    # Ensure that the metadata JSONB postgres field is persisted properly
117
    work.metadata = JSON.parse(work.resource.to_json)
987✔
118
  end
119

120
  after_save do |work|
1✔
121
    if work.approved?
986✔
122
      work.reload
127✔
123
    end
124
  end
125

126
  validate do |_work|
1✔
127
    work_validator.valid?
1,030✔
128
  end
129

130
  # Overload ActiveRecord.reload method
131
  # https://apidock.com/rails/ActiveRecord/Base/reload
132
  #
133
  # NOTE: Usually `after_save` is a better place to put this kind of code:
134
  #
135
  #   after_save do |work|
136
  #     work.resource = nil
137
  #   end
138
  #
139
  # but that does not work in this case because the block points to a different
140
  # memory object for `work` than the we want we want to reload.
141
  def reload(options = nil)
1✔
142
    super
222✔
143
    # Force `resource` to be reloaded
144
    @resource = nil
222✔
145
    self
222✔
146
  end
147

148
  def valid_to_approve(user)
1✔
149
    work_validator.valid_to_submit
37✔
150
    if resource.doi.blank?
37✔
151
      errors.add :base, "DOI must be present for a work to be approved"
1✔
152
    end
153
    unless user.has_role? :group_admin, group
37✔
154
      errors.add :base, "Unauthorized to Approve"
4✔
155
    end
156
    if pre_curation_uploads_fast.empty? && post_curation_uploads.empty?
37✔
157
      errors.add :base, "Uploads must be present for a work to be approved"
2✔
158
    end
159
    errors.count == 0
37✔
160
  end
161

162
  def title
1✔
163
    resource.main_title
394✔
164
  end
165

166
  def uploads_attributes
1✔
167
    return [] if approved? # once approved we no longer allow the updating of uploads via the application
81✔
168
    uploads.map do |upload|
75✔
169
      {
170
        id: upload.id,
19✔
171
        key: upload.key,
172
        filename: upload.filename.to_s,
173
        created_at: upload.created_at,
174
        url: upload.url
175
      }
176
    end
177
  end
178

179
  def form_attributes
1✔
180
    {
181
      uploads: uploads_attributes
81✔
182
    }
183
  end
184

185
  def draft_doi
1✔
186
    return if resource.doi.present?
49✔
187
    resource.doi = datacite_service.draft_doi
25✔
188
    save!
23✔
189
  end
190

191
  # Return the DOI formatted as a URL, so it can be used as a link on display pages
192
  # @return [String] A url formatted version of the DOI
193
  def doi_url
1✔
194
    return "https://doi.org/#{doi}" unless doi.starts_with?("https://doi.org")
1✔
UNCOV
195
    doi
×
196
  end
197

198
  def created_by_user
1✔
199
    User.find(created_by_user_id)
429✔
200
  rescue ActiveRecord::RecordNotFound
201
    nil
1✔
202
  end
203

204
  def resource=(resource)
1✔
205
    @resource = resource
776✔
206
    # Ensure that the metadata JSONB postgres field is persisted properly
207
    self.metadata = JSON.parse(resource.to_json)
776✔
208
  end
209

210
  def resource
1✔
211
    @resource ||= PDCMetadata::Resource.new_from_jsonb(metadata)
24,313✔
212
  end
213

214
  def url
1✔
UNCOV
215
    return unless persisted?
×
216

UNCOV
217
    @url ||= url_for(self)
×
218
  end
219

220
  def files_location_upload?
1✔
221
    files_location.blank? || files_location == "file_upload"
6✔
222
  end
223

224
  def files_location_cluster?
1✔
225
    files_location == "file_cluster"
74✔
226
  end
227

228
  def files_location_other?
1✔
229
    files_location == "file_other"
74✔
230
  end
231

232
  def change_curator(curator_user_id, current_user)
1✔
233
    if curator_user_id == "no-one"
5✔
234
      clear_curator(current_user)
1✔
235
    else
236
      update_curator(curator_user_id, current_user)
4✔
237
    end
238
  end
239

240
  def clear_curator(current_user)
1✔
241
    # Update the curator on the Work
242
    self.curator_user_id = nil
2✔
243
    save!
2✔
244

245
    # ...and log the activity
246
    WorkActivity.add_work_activity(id, "Unassigned existing curator", current_user.id, activity_type: WorkActivity::SYSTEM)
2✔
247
  end
248

249
  def update_curator(curator_user_id, current_user)
1✔
250
    # Update the curator on the Work
251
    self.curator_user_id = curator_user_id
5✔
252
    save!
5✔
253

254
    # ...and log the activity
255
    new_curator = User.find(curator_user_id)
4✔
256
    message = if curator_user_id == current_user.id
4✔
257
                "Self-assigned as curator"
1✔
258
              else
259
                "Set curator to @#{new_curator.uid}"
3✔
260
              end
261
    WorkActivity.add_work_activity(id, message, current_user.id, activity_type: WorkActivity::SYSTEM)
4✔
262
  end
263

264
  def add_message(message, current_user_id)
1✔
265
    WorkActivity.add_work_activity(id, message, current_user_id, activity_type: WorkActivity::MESSAGE)
11✔
266
  end
267

268
  def add_provenance_note(date, note, current_user_id, change_label = "")
1✔
269
    WorkActivity.add_work_activity(id, { note:, change_label: }.to_json, current_user_id, activity_type: WorkActivity::PROVENANCE_NOTES, created_at: date)
4✔
270
    # WorkActivity.add_work_activity(id, note, current_user_id, activity_type: WorkActivity::PROVENANCE_NOTES, created_at: date)
271
  end
272

273
  def log_changes(resource_compare, current_user_id)
1✔
274
    return if resource_compare.identical?
48✔
275
    WorkActivity.add_work_activity(id, resource_compare.differences.to_json, current_user_id, activity_type: WorkActivity::CHANGES)
45✔
276
  end
277

278
  def log_file_changes(current_user_id)
1✔
279
    return if changes.count == 0
21✔
280
    WorkActivity.add_work_activity(id, changes.to_json, current_user_id, activity_type: WorkActivity::FILE_CHANGES)
21✔
281
  end
282

283
  def activities
1✔
284
    WorkActivity.activities_for_work(id, WorkActivity::MESSAGE_ACTIVITY_TYPES + WorkActivity::CHANGE_LOG_ACTIVITY_TYPES)
104✔
285
  end
286

287
  def new_notification_count_for_user(user_id)
1✔
288
    WorkActivityNotification.joins(:work_activity)
115✔
289
                            .where(user_id:, read_at: nil)
290
                            .where(work_activity: { work_id: id })
291
                            .count
292
  end
293

294
  # Marks as read the notifications for the given user_id in this work.
295
  # In practice, the user_id is the id of the current user and therefore this method marks the current's user
296
  # notifications as read.
297
  def mark_new_notifications_as_read(user_id)
1✔
298
    activities.each do |activity|
104✔
299
      unread_notifications = WorkActivityNotification.where(user_id:, work_activity_id: activity.id, read_at: nil)
148✔
300
      unread_notifications.each do |notification|
148✔
301
        notification.read_at = Time.now.utc
33✔
302
        notification.save
33✔
303
      end
304
    end
305
  end
306

307
  def current_transition
1✔
308
    aasm.current_event.to_s.humanize.delete("!")
12✔
309
  end
310

311
  def uploads
1✔
312
    return post_curation_uploads if approved?
670✔
313

314
    pre_curation_uploads_fast
606✔
315
  end
316

317
  # Returns the list of files for the work with some basic information about each of them.
318
  # This method is much faster than `uploads` because it does not return the actual S3File
319
  # objects to the client, instead it returns just a few selected data elements.
320
  def file_list
1✔
321
    s3_files = approved? ? post_curation_uploads : pre_curation_uploads_fast
125✔
322
    files_info = s3_files.map do |s3_file|
125✔
323
      {
324
        "safe_id": s3_file.safe_id,
73✔
325
        "filename": s3_file.filename,
326
        "filename_display": s3_file.filename_display,
327
        "last_modified": s3_file.last_modified,
328
        "last_modified_display": s3_file.last_modified_display,
329
        "size": s3_file.size,
330
        "display_size": s3_file.display_size,
331
        "url": s3_file.url
332
      }
333
    end
334
    files_info
125✔
335
  end
336

337
  # Fetches the data from S3 directly bypassing ActiveStorage
338
  def pre_curation_uploads_fast
1✔
339
    s3_query_service.client_s3_files.sort_by(&:filename)
1,031✔
340
  end
341

342
  # Accesses post-curation S3 Bucket Objects
343
  def post_curation_s3_resources
1✔
344
    if approved?
127✔
345
      s3_resources
99✔
346
    else
347
      []
28✔
348
    end
349
  end
350

351
  # Returns the files in post-curation for the work
352
  def post_curation_uploads(force_post_curation: false)
1✔
353
    if force_post_curation
132✔
354
      # Always use the post-curation data regardless of the work's status
355
      post_curation_s3_query_service = S3QueryService.new(self, "postcuration")
5✔
356
      post_curation_s3_query_service.data_profile.fetch(:objects, [])
5✔
357
    else
358
      # Return the list based of files honoring the work status
359
      post_curation_s3_resources
127✔
360
    end
361
  end
362

363
  def s3_files
1✔
UNCOV
364
    pre_curation_uploads_fast
×
365
  end
366

367
  def s3_client
1✔
368
    s3_query_service.client
21✔
369
  end
370

371
  delegate :bucket_name, :prefix, to: :s3_query_service
1✔
372
  delegate :doi_attribute_url, :curator_or_current_uid, to: :datacite_service
1✔
373

374
  # Generates the S3 Object key
375
  # @return [String]
376
  def s3_object_key
1✔
377
    "#{doi}/#{id}"
55✔
378
  end
379

380
  # Transmit a HEAD request for the S3 Bucket directory for this Work
381
  # @param bucket_name location to be checked to be found
382
  # @return [Aws::S3::Types::HeadObjectOutput]
383
  def find_post_curation_s3_dir(bucket_name:)
1✔
384
    # TODO: Directories really do not exists in S3
385
    #      if we really need this check then we need to do something else to check the bucket
386
    s3_client.head_object({
21✔
387
                            bucket: bucket_name,
388
                            key: s3_object_key
389
                          })
UNCOV
390
    true
×
391
  rescue Aws::S3::Errors::NotFound
392
    nil
21✔
393
  end
394

395
  # Generates the JSON serialized expression of the Work
396
  # @param args [Array<Hash>]
397
  # @option args [Boolean] :force_post_curation Force the request of AWS S3
398
  #   Resources, clearing the in-memory cache
399
  # @return [String]
400
  def as_json(*args)
1✔
401
    files = files_as_json(*args)
42✔
402

403
    # to_json returns a string of serialized JSON.
404
    # as_json returns the corresponding hash.
405
    {
406
      "resource" => resource.as_json,
42✔
407
      "files" => files,
408
      "group" => group.as_json.except("id"),
409
      "embargo_date" => embargo_date_as_json
410
    }
411
  end
412

413
  def pre_curation_uploads_count
1✔
414
    s3_query_service.file_count
2✔
415
  end
416

417
  delegate :ark, :doi, :resource_type, :resource_type=, :resource_type_general, :resource_type_general=,
1✔
418
           :to_xml, to: :resource
419

420
  # S3QueryService object associated with this Work
421
  # @return [S3QueryService]
422
  def s3_query_service
1✔
423
    mode = approved? ? "postcuration" : "precuration"
1,550✔
424
    @s3_query_service ||= S3QueryService.new(self, mode)
1,550✔
425
  end
426

427
  def past_snapshots
1✔
UNCOV
428
    UploadSnapshot.where(work: self)
×
429
  end
430

431
  # Build or find persisted UploadSnapshot models for this Work
432
  # @return [UploadSnapshot]
433
  def reload_snapshots
1✔
434
    work_changes = []
113✔
435
    s3_files = pre_curation_uploads_fast
113✔
436
    s3_filenames = s3_files.map(&:filename)
113✔
437

438
    upload_snapshot = latest_snapshot
113✔
439

440
    upload_snapshot.snapshot_deletions(work_changes, s3_filenames)
113✔
441

442
    upload_snapshot.snapshot_modifications(work_changes, s3_files)
113✔
443

444
    # Create WorkActivity models with the set of changes
445
    unless work_changes.empty?
113✔
446
      new_snapshot = UploadSnapshot.new(work: self, url: s3_query_service.prefix)
33✔
447
      new_snapshot.store_files(s3_files)
33✔
448
      new_snapshot.save!
33✔
449
      WorkActivity.add_work_activity(id, work_changes.to_json, nil, activity_type: WorkActivity::FILE_CHANGES)
33✔
450
    end
451
  end
452

453
  def self.presenter_class
1✔
454
    WorkPresenter
121✔
455
  end
456

457
  def presenter
1✔
458
    self.class.presenter_class.new(work: self)
121✔
459
  end
460

461
  def changes
1✔
462
    @changes ||= []
154✔
463
  end
464

465
  def track_change(action, filename)
1✔
466
    changes << { action:, filename: }
31✔
467
  end
468

469
  # rubocop:disable Naming/PredicateName
470
  def has_rights?(rights_id)
1✔
471
    resource.rights_many.index { |rights| rights.identifier == rights_id } != nil
1,195✔
472
  end
473
  # rubocop:enable Naming/PredicateName
474

475
  # This is the solr id / work show page in PDC Discovery
476
  def pdc_discovery_url
1✔
477
    "https://datacommons.princeton.edu/discovery/catalog/doi-#{doi.tr('/', '-').tr('.', '-')}"
183✔
478
  end
479

480
  # Determine whether or not the Work is under active embargo
481
  # @return [Boolean]
482
  def embargoed?
1✔
483
    return false if embargo_date.blank?
143✔
484

485
    current_date = Time.zone.now
6✔
486
    embargo_date >= current_date
6✔
487
  end
488

489
  protected
1✔
490

491
    def work_validator
1✔
492
      @work_validator ||= WorkValidator.new(self)
1,168✔
493
    end
494

495
    # This must be protected, NOT private for ActiveRecord to work properly with this attribute.
496
    #   Protected will still keep others from setting the metatdata, but allows ActiveRecord the access it needs
497
    def metadata=(metadata)
1✔
498
      super
1,763✔
499
      @resource = PDCMetadata::Resource.new_from_jsonb(metadata)
1,763✔
500
    end
501

502
  private
1✔
503

504
    def publish(user)
1✔
505
      datacite_service.publish_doi(user)
27✔
506
      update_ark_information
27✔
507
      publish_precurated_files(user)
27✔
508
      save!
27✔
509
    end
510

511
    # Update EZID (our provider of ARKs) with the new information for this work.
512
    def update_ark_information
1✔
513
      # We only want to update the ark url under certain conditions.
514
      # Set this value in config/update_ark_url.yml
515
      if Rails.configuration.update_ark_url
27✔
516
        if ark.present?
25✔
517
          Ark.update(ark, datacite_service.doi_attribute_url)
3✔
518
        end
519
      end
520
    end
521

522
    # Generates the key for ActiveStorage::Attachment and Attachment::Blob objects
523
    # @param attachment [ActiveStorage::Attachment]
524
    # @return [String]
525
    def generate_attachment_key(attachment)
1✔
526
      attachment_filename = attachment.filename.to_s
×
527
      attachment_key = attachment.key
×
528

529
      # Files actually coming from S3 include the DOI and bucket as part of the file name
530
      #  Files being attached in another manner may not have it, so we should include it.
531
      #  This is really for testing only.
UNCOV
532
      key_base = "#{doi}/#{id}"
×
UNCOV
533
      attachment_key = [key_base, attachment_filename].join("/") unless attachment_key.include?(key_base)
×
534

UNCOV
535
      attachment_ext = File.extname(attachment_filename)
×
UNCOV
536
      attachment_query = attachment_key.gsub(attachment_ext, "")
×
UNCOV
537
      results = ActiveStorage::Blob.where("key LIKE :query", query: "%#{attachment_query}%")
×
UNCOV
538
      blobs = results.to_a
×
539

UNCOV
540
      if blobs.present?
×
UNCOV
541
        index = blobs.length + 1
×
UNCOV
542
        attachment_key = attachment_key.gsub(/\.([a-zA-Z0-9\.]+)$/, "_#{index}.\\1")
×
543
      end
544

545
      attachment_key
×
546
    end
547

548
    def track_state_change(user, state = aasm.to_state)
1✔
549
      uw = UserWork.new(user_id: user.id, work_id: id, state:)
160✔
550
      uw.save!
160✔
551
      WorkActivity.add_work_activity(id, "marked as #{state.to_s.titleize}", user.id, activity_type: WorkActivity::SYSTEM)
160✔
552
      WorkStateTransitionNotification.new(self, user.id).send
160✔
553
    end
554

555
    # This needs to be called #before_save
556
    # This ensures that new ActiveStorage::Attachment objects are persisted with custom keys (which are generated from the file name and DOI)
557
    # @param new_attachments [Array<ActiveStorage::Attachment>]
558
    def save_new_attachments(new_attachments:)
1✔
UNCOV
559
      new_attachments.each do |attachment|
×
560
        # There are cases (race conditions?) where the ActiveStorage::Blob objects are not persisted
UNCOV
561
        next if attachment.frozen?
×
562

563
        # This ensures that the custom key for the ActiveStorage::Attachment and ActiveStorage::Blob objects are generated
UNCOV
564
        generated_key = generate_attachment_key(attachment)
×
UNCOV
565
        attachment.blob.key = generated_key
×
UNCOV
566
        attachment.blob.save
×
567

568
        attachment.save
×
569
      end
570
    end
571

572
    # Request S3 Bucket Objects associated with this Work
573
    # @return [Array<S3File>]
574
    def s3_resources
1✔
575
      data_profile = s3_query_service.data_profile
99✔
576
      data_profile.fetch(:objects, [])
99✔
577
    end
578
    alias pre_curation_s3_resources s3_resources
1✔
579

580
    def s3_object_persisted?(s3_file)
1✔
UNCOV
581
      uploads_keys = uploads.map(&:key)
×
UNCOV
582
      uploads_keys.include?(s3_file.key)
×
583
    end
584

585
    def publish_precurated_files(user)
1✔
586
      # An error is raised if there are no files to be moved
587
      raise(StandardError, "Attempting to publish a Work without attached uploads for #{s3_object_key}") if pre_curation_uploads_fast.empty? && post_curation_uploads.empty?
21✔
588

589
      # We need to explicitly access to post-curation services here.
590
      # Lets explicitly create it so the state of the work does not have any impact.
591
      s3_post_curation_query_service = S3QueryService.new(self, "postcuration")
21✔
592

593
      s3_dir = find_post_curation_s3_dir(bucket_name: s3_post_curation_query_service.bucket_name)
21✔
594
      raise(StandardError, "Attempting to publish a Work with an existing S3 Bucket directory for: #{s3_object_key}") unless s3_dir.nil?
21✔
595

596
      # Copy the pre-curation S3 Objects to the post-curation S3 Bucket...
597
      s3_query_service.publish_files(user)
21✔
598
    end
599

600
    def latest_snapshot
1✔
601
      return upload_snapshots.first unless upload_snapshots.empty?
113✔
602

603
      UploadSnapshot.new(work: self, files: [])
94✔
604
    end
605

606
    def datacite_service
1✔
607
      @datacite_service ||= PULDatacite.new(self)
57✔
608
    end
609

610
    def files_as_json(*args)
1✔
611
      return [] if embargoed?
42✔
612

613
      force_post_curation = args.any? { |arg| arg[:force_post_curation] == true }
72✔
614

615
      # Pre-curation files are not accessible externally,
616
      # so we are not interested in listing them in JSON.
617
      post_curation_uploads(force_post_curation:).map do |upload|
39✔
618
        {
619
          "filename": upload.filename,
19✔
620
          "size": upload.size,
621
          "display_size": upload.display_size,
622
          "url": upload.globus_url
623
        }
624
      end
625
    end
626

627
    def embargo_date_as_json
1✔
628
      if embargo_date.present?
42✔
629
        embargo_datetime = embargo_date.to_datetime
5✔
630
        embargo_date_iso8601 = embargo_datetime.iso8601
5✔
631
        # Apache Solr timestamps require the following format:
632
        # 1972-05-20T17:33:18Z
633
        # https://solr.apache.org/guide/solr/latest/indexing-guide/date-formatting-math.html
634
        embargo_date_iso8601.gsub(/\+.+$/, "Z")
5✔
635
      end
636
    end
637
end
638
# 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

© 2025 Coveralls, Inc