bulkrax 8.0.0 → 8.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6ca767b1e461d5830774bb4d7c49eade799d14298625bdbdccdcbdc0ac997f88
4
- data.tar.gz: b5024ea772132123fcb922fbe183d22950428ea50b7a8b4152c6bd781b94510f
3
+ metadata.gz: 7886fb5eefc500cc8e567d9097e6930e67cee2908fa7fa0d79ce05fc714db392
4
+ data.tar.gz: c6e720816dd152931df91e06be37977216a7ef06875a98386350706e4df1e3ea
5
5
  SHA512:
6
- metadata.gz: 91004f88a97054107a3bbbcbe7f3b3dd84226a286df47992c13bfc5fb483c3bfd4aaa9190e08e567258e5105a317a37bba58fa7857ee6c534153ba930b608ad7
7
- data.tar.gz: 89a32c155c3931e486b2d41215942b291eb724c59833a031fdc4815a72d26a2df13b015c1b1e2c0dafd608e58ec481a574e20da7bcf95ed76b3e45c3d5f19287
6
+ metadata.gz: 62971f4eab58de239643d016ec8f8701b6e71953bbb8ccc4bf1d74fd8a88d3798c506004704203107e6f676218bc0dcd337776ecccc423b6c932aeddcd0dacf5
7
+ data.tar.gz: f3710a5e394a470753ea83d5e007505afd91a8fcdc3c383465fdaa7f2eb9c0901b3bef7cb69ae2c9bfa6d508231095e44ac7781cc9c52565162adfe2e58a5c3d
@@ -67,7 +67,7 @@ Blacklight.onLoad(function() {
67
67
  { "data": "name" },
68
68
  { "data": "status_message" },
69
69
  { "data": "created_at" },
70
- { "data": "download" },
70
+ { "data": "download", "orderable": false },
71
71
  { "data": "actions", "orderable": false }
72
72
  ],
73
73
  initComplete: function () {
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Loggable
4
+ extend ActiveSupport::Concern
5
+
6
+ def log_created(obj)
7
+ log_action('Created', obj)
8
+ end
9
+
10
+ def log_updated(obj)
11
+ log_action('Updated', obj)
12
+ end
13
+
14
+ def log_deleted_fs(obj)
15
+ msg = "Deleted All Files from #{obj.id}"
16
+ Rails.logger.info("#{msg} (#{Array(obj.attributes[work_identifier]).first})")
17
+ end
18
+
19
+ private
20
+
21
+ def log_action(action, obj)
22
+ msg = "#{action} #{obj.class.model_name.human} #{obj.id}"
23
+ Rails.logger.info("#{msg} (#{Array(obj.attributes[work_identifier]).first})")
24
+ end
25
+ end
@@ -13,7 +13,7 @@ module Bulkrax
13
13
  before_action :token_authenticate!, if: -> { api_request? }, only: [:create, :update, :delete]
14
14
  before_action :authenticate_user!, unless: -> { api_request? }
15
15
  before_action :check_permissions
16
- before_action :set_importer, only: [:show, :entry_table, :edit, :update, :destroy]
16
+ before_action :set_importer, only: [:show, :entry_table, :edit, :update, :destroy, :original_file]
17
17
  with_themed_layout 'dashboard' if defined?(::Hyrax)
18
18
 
19
19
  # GET /importers
@@ -201,6 +201,14 @@ module Bulkrax
201
201
  end
202
202
  end
203
203
 
204
+ def original_file
205
+ if @importer.original_file?
206
+ send_file @importer.original_file
207
+ else
208
+ redirect_to @importer, alert: 'Importer does not support file re-download or the imported file is not found on the server.'
209
+ end
210
+ end
211
+
204
212
  # GET /importers/1/export_errors
205
213
  def export_errors
206
214
  @importer = Importer.find(params[:importer_id])
@@ -12,7 +12,7 @@ module Bulkrax
12
12
  # @note This does not save either object. We need to do that in another
13
13
  # loop. Why? Because we might be adding many items to the parent.
14
14
  def self.add_child_to_parent_work(parent:, child:)
15
- return true if parent.ordered_members.to_a.include?(child_record)
15
+ return true if parent.ordered_members.to_a.include?(child)
16
16
 
17
17
  parent.ordered_members << child
18
18
  end
@@ -228,33 +228,29 @@ module Bulkrax
228
228
  actor.create_metadata(attrs)
229
229
  actor.create_content(uploaded_file) if uploaded_file
230
230
  actor.attach_to_work(work, attrs)
231
- handle_remote_file(remote_file: remote_file, actor: actor, update: false) if remote_file
231
+ handle_remote_file(remote_file: remote_file, actor: actor) if remote_file
232
232
  end
233
233
 
234
234
  def update_file_set(attrs)
235
235
  file_set_attrs = attrs.slice(*object.attributes.keys)
236
236
  actor = ::Hyrax::Actors::FileSetActor.new(object, @user)
237
237
  attrs['remote_files']&.each do |remote_file|
238
- handle_remote_file(remote_file: remote_file, actor: actor, update: true)
238
+ handle_remote_file(remote_file: remote_file, actor: actor)
239
239
  end
240
240
  actor.update_metadata(file_set_attrs)
241
241
  end
242
242
 
243
- def handle_remote_file(remote_file:, actor:, update: false)
243
+ def handle_remote_file(remote_file:, actor:)
244
244
  actor.file_set.label = remote_file['file_name']
245
245
  actor.file_set.import_url = remote_file['url']
246
+ auth_header = remote_file.fetch('auth_header', {})
246
247
 
247
- url = remote_file['url']
248
- tmp_file = Tempfile.new(remote_file['file_name'].split('.').first)
249
- tmp_file.binmode
250
-
251
- URI.open(url) do |url_file|
252
- tmp_file.write(url_file.read)
253
- end
248
+ ImportUrlJob.perform_now(actor.file_set, file_set_operation_for(user: @user), auth_header)
249
+ end
254
250
 
255
- tmp_file.rewind
256
- update == true ? actor.update_content(tmp_file) : actor.create_content(tmp_file, from_url: true)
257
- tmp_file.close
251
+ def file_set_operation_for(user:)
252
+ Hyrax::Operation.create!(user: user,
253
+ operation_type: "Attach Remote File")
258
254
  end
259
255
  end
260
256
  # rubocop:enable Metrics/ClassLength
@@ -20,6 +20,7 @@ module Bulkrax
20
20
  class ObjectFactoryInterface
21
21
  extend ActiveModel::Callbacks
22
22
  include DynamicRecordLookup
23
+ include Loggable
23
24
 
24
25
  # We're inheriting from an ActiveRecord exception as that is something we
25
26
  # know will be here; and something that the main_app will be expect to be
@@ -224,6 +225,12 @@ module Bulkrax
224
225
  id
225
226
  read_groups
226
227
  visibility
228
+ visibility_during_embargo
229
+ embargo_release_date
230
+ visibility_after_embargo
231
+ visibility_during_lease
232
+ lease_expiration_date
233
+ visibility_after_lease
227
234
  work_members_attributes
228
235
  ]
229
236
 
@@ -398,21 +405,6 @@ module Bulkrax
398
405
  self.class.add_user_to_collection_permissions(**arguments)
399
406
  end
400
407
 
401
- def log_created(obj)
402
- msg = "Created #{klass.model_name.human} #{obj.id}"
403
- Rails.logger.info("#{msg} (#{Array(attributes[work_identifier]).first})")
404
- end
405
-
406
- def log_updated(obj)
407
- msg = "Updated #{klass.model_name.human} #{obj.id}"
408
- Rails.logger.info("#{msg} (#{Array(attributes[work_identifier]).first})")
409
- end
410
-
411
- def log_deleted_fs(obj)
412
- msg = "Deleted All Files from #{obj.id}"
413
- Rails.logger.info("#{msg} (#{Array(attributes[work_identifier]).first})")
414
- end
415
-
416
408
  private
417
409
 
418
410
  def apply_depositor_metadata
@@ -217,7 +217,7 @@ module Bulkrax
217
217
  run
218
218
  # reload the object
219
219
  object = find
220
- return object if object.persisted?
220
+ return object if object&.persisted?
221
221
 
222
222
  raise(ObjectFactoryInterface::RecordInvalid, object)
223
223
  end
@@ -225,10 +225,10 @@ module Bulkrax
225
225
  private
226
226
 
227
227
  def apply_depositor_metadata
228
- return if object.depositor.present?
228
+ return if @object.depositor.present?
229
229
 
230
- object.depositor = @user.email
231
- object = Hyrax.persister.save(resource: object)
230
+ @object.depositor = @user.email
231
+ object = Hyrax.persister.save(resource: @object)
232
232
  self.class.publish(event: "object.metadata.updated", object: object, user: @user)
233
233
  object
234
234
  end
@@ -249,19 +249,77 @@ module Bulkrax
249
249
  end
250
250
 
251
251
  def create_work(attrs)
252
- # NOTE: We do not add relationships here; that is part of the create
253
- # relationships job.
252
+ # NOTE: We do not add relationships here; that is part of the create relationships job.
253
+ attrs = HashWithIndifferentAccess.new(attrs)
254
254
  perform_transaction_for(object: object, attrs: attrs) do
255
+ uploaded_files, file_set_params = prep_fileset_content(attrs)
255
256
  transactions["change_set.create_work"]
256
257
  .with_step_args(
257
- 'work_resource.add_file_sets' => { uploaded_files: uploaded_files_from(attrs) },
258
+ 'work_resource.add_file_sets' => { uploaded_files: uploaded_files, file_set_params: file_set_params },
258
259
  "change_set.set_user_as_depositor" => { user: @user },
259
260
  "work_resource.change_depositor" => { user: @user },
260
- 'work_resource.save_acl' => { permissions_params: [attrs['visibility'] || 'open'].compact }
261
+ 'work_resource.save_acl' => { permissions_params: [attrs.try('visibility') || 'open'].compact }
261
262
  )
262
263
  end
263
264
  end
264
265
 
266
+ ## Prepare fileset data in the required format for creating or updating a work
267
+ # TODO: Determine why attrs is different from attributes?
268
+ # TODO: Disabled s3 until we get additional details
269
+ def prep_fileset_content(attrs)
270
+ # combine remote_files + thumbnail_url [Array < { url:, file_name:, * }]
271
+ thumbnail_url = HashWithIndifferentAccess.new(self.attributes)['thumbnail_url']
272
+ all_remote_files = merge_thumbnails(remote_files: attrs["remote_files"], thumbnail_url: thumbnail_url)
273
+ # combine local & remote files [Array < Hash &/or String]
274
+ all_local_files = self.attributes['file'] || []
275
+ all_files = all_local_files + all_remote_files
276
+
277
+ # collect all uploaded files [Array < Hyrax::UploadedFile]
278
+ uploaded_local = uploaded_local_files(uploaded_files: attrs[:uploaded_files])
279
+ uploaded_remote = uploaded_remote_files(remote_files: all_remote_files)
280
+ # uploaded_s3 = uploaded_s3_files(remote_files: attrs[:remote_files])
281
+ uploaded_files = uploaded_local + uploaded_remote
282
+
283
+ # add in other attributes
284
+ file_set_params = file_set_params_for(uploads: uploaded_files, files: all_files)
285
+ # return data for filesets
286
+ [uploaded_files, file_set_params]
287
+ end
288
+
289
+ # supports using thumbnail_url to import a thumbnail separately from other remote_files
290
+ # in the format thumbnail_url: { url:, file_name: }
291
+ def merge_thumbnails(remote_files:, thumbnail_url:)
292
+ r = remote_files || []
293
+ thumbnail_url.present? ? r + [thumbnail_url] : r
294
+ end
295
+
296
+ # formats file info and facilitates additional custom file_set attributes
297
+ # To have the additional attributes appear on the file_set, they must be:
298
+ # - included in the file_set_metadata.yaml
299
+ # - overridden in file_set_args from Hyrax::WorkUploadsHandler
300
+ # @param uploads [Array < Hyrax::UploadedFile]
301
+ # @param files [Array < Hash or String]
302
+ # @return [Array < Hash]
303
+ def file_set_params_for(uploads:, files:)
304
+ # remove url, file_name and paths from attributes
305
+ additional_attributes = files.map do |f|
306
+ case f
307
+ when String
308
+ {}
309
+ else
310
+ temp = f.reject { |key, _| key.to_s == 'url' || key.to_s == 'file_name' }
311
+ temp['import_url'] = f['url']
312
+ temp
313
+ end
314
+ end
315
+
316
+ file_attrs = []
317
+ uploads.each_with_index do |f, index|
318
+ file_attrs << ({ uploaded_file_id: f["id"].to_s, filename: files[index]["file_name"] }).merge(additional_attributes[index])
319
+ end
320
+ file_attrs.compact.uniq
321
+ end
322
+
265
323
  def create_collection(attrs)
266
324
  # TODO: Handle Collection Type
267
325
  #
@@ -328,10 +386,12 @@ module Bulkrax
328
386
  end
329
387
 
330
388
  def update_work(attrs)
389
+ attrs = HashWithIndifferentAccess.new(attrs)
331
390
  perform_transaction_for(object: object, attrs: attrs) do
391
+ uploaded_files, file_set_params = prep_fileset_content(attrs)
332
392
  transactions["change_set.update_work"]
333
393
  .with_step_args(
334
- 'work_resource.add_file_sets' => { uploaded_files: uploaded_files_from(attrs) },
394
+ 'work_resource.add_file_sets' => { uploaded_files: uploaded_files, file_set_params: file_set_params },
335
395
  'work_resource.save_acl' => { permissions_params: [attrs.try('visibility') || 'open'].compact }
336
396
  )
337
397
  end
@@ -349,17 +409,13 @@ module Bulkrax
349
409
  # TODO: Make it work
350
410
  end
351
411
 
352
- def uploaded_files_from(attrs)
353
- uploaded_local_files(uploaded_files: attrs[:uploaded_files]) + uploaded_s3_files(remote_files: attrs[:remote_files])
354
- end
355
-
356
412
  def uploaded_local_files(uploaded_files: [])
357
413
  Array.wrap(uploaded_files).map do |file_id|
358
414
  Hyrax::UploadedFile.find(file_id)
359
415
  end
360
416
  end
361
417
 
362
- def uploaded_s3_files(remote_files: {})
418
+ def uploaded_s3_files(remote_files: [])
363
419
  return [] if remote_files.blank?
364
420
 
365
421
  s3_bucket_name = ENV.fetch("STAGING_AREA_S3_BUCKET", "comet-staging-area-#{Rails.env}")
@@ -371,6 +427,41 @@ module Bulkrax
371
427
  end.compact
372
428
  end
373
429
 
430
+ def uploaded_remote_files(remote_files: [])
431
+ remote_files.map do |r|
432
+ file_path = download_file(r["url"])
433
+ next unless file_path
434
+
435
+ create_uploaded_file(file_path, r["file_name"])
436
+ end.compact
437
+ end
438
+
439
+ def download_file(url)
440
+ require 'open-uri'
441
+ require 'tempfile'
442
+
443
+ begin
444
+ file = Tempfile.new
445
+ file.binmode
446
+ file.write(URI.open(url).read)
447
+ file.rewind
448
+ file.path
449
+ rescue => e
450
+ Rails.logger.debug "Failed to download file from #{url}: #{e.message}"
451
+ nil
452
+ end
453
+ end
454
+
455
+ def create_uploaded_file(file_path, file_name)
456
+ file = File.open(file_path)
457
+ uploaded_file = Hyrax::UploadedFile.create(file: file, user: @user, filename: file_name)
458
+ file.close
459
+ uploaded_file
460
+ rescue => e
461
+ Rails.logger.debug "Failed to create Hyrax::UploadedFile for #{file_name}: #{e.message}"
462
+ nil
463
+ end
464
+
374
465
  # @Override Destroy existing files with Hyrax::Transactions
375
466
  def destroy_existing_files
376
467
  existing_files = Hyrax.custom_queries.find_child_file_sets(resource: object)
@@ -38,11 +38,13 @@ module Bulkrax
38
38
  #
39
39
  # @see https://github.com/scientist-softserv/louisville-hyku/commit/128a9ef
40
40
  class_attribute :update_child_records_works_file_sets, default: false
41
+ class_attribute :max_failure_count, default: 5
41
42
 
42
43
  include DynamicRecordLookup
43
44
 
44
45
  queue_as Bulkrax.config.ingest_queue_name
45
46
 
47
+ attr_accessor :user, :importer_run, :errors
46
48
  ##
47
49
  # @param parent_identifier [String] Work/Collection ID or Bulkrax::Entry source_identifiers
48
50
  # @param importer_run [Bulkrax::ImporterRun] current importer run (needed to properly update counters)
@@ -54,9 +56,10 @@ module Bulkrax
54
56
  # is the child in the relationship, and vice versa if a child_identifier is passed.
55
57
  #
56
58
  # rubocop:disable Metrics/MethodLength
57
- def perform(parent_identifier:, importer_run_id:) # rubocop:disable Metrics/AbcSize
58
- @importer_run = Bulkrax::ImporterRun.find(importer_run_id)
59
- ability = Ability.new(importer_run.user)
59
+ def perform(parent_identifier:, importer_run_id: nil, run_user: nil, failure_count: 0) # rubocop:disable Metrics/AbcSize
60
+ importer_run = Bulkrax::ImporterRun.find(importer_run_id) if importer_run_id
61
+ user = run_user || importer_run&.user
62
+ ability = Ability.new(user)
60
63
 
61
64
  parent_entry, parent_record = find_record(parent_identifier, importer_run_id)
62
65
 
@@ -69,19 +72,21 @@ module Bulkrax
69
72
  if parent_record
70
73
  conditionally_acquire_lock_for(parent_record.id) do
71
74
  ActiveRecord::Base.uncached do
72
- Bulkrax::PendingRelationship.where(parent_id: parent_identifier, importer_run_id: importer_run_id)
75
+ Bulkrax::PendingRelationship.where(parent_id: parent_identifier)
73
76
  .ordered.find_each do |rel|
74
77
  process(relationship: rel, importer_run_id: importer_run_id, parent_record: parent_record, ability: ability)
75
78
  number_of_successes += 1
79
+ @parent_record_members_added = true
76
80
  rescue => e
77
81
  number_of_failures += 1
82
+ rel.set_status_info(e, importer_run)
78
83
  errors << e
79
84
  end
80
85
  end
81
86
 
82
87
  # save record if members were added
83
88
  if @parent_record_members_added
84
- Bulkrax.object_factory.save!(resource: parent_record, user: importer_run.user)
89
+ Bulkrax.object_factory.save!(resource: parent_record, user: user)
85
90
  Bulkrax.object_factory.publish(event: 'object.membership.updated', object: parent_record)
86
91
  Bulkrax.object_factory.update_index(resources: @child_members_added)
87
92
  end
@@ -104,10 +109,17 @@ module Bulkrax
104
109
  # rubocop:enable Rails/SkipsModelValidations
105
110
 
106
111
  parent_entry&.set_status_info(errors.last, importer_run)
107
-
108
- # TODO: This can create an infinite job cycle, consider a time to live tracker.
109
- reschedule(parent_identifier: parent_identifier, importer_run_id: importer_run_id)
110
- return false # stop current job from continuing to run after rescheduling
112
+ failure_count += 1
113
+
114
+ if failure_count < max_failure_count
115
+ reschedule(
116
+ parent_identifier: parent_identifier,
117
+ importer_run_id: importer_run_id,
118
+ run_user: run_user,
119
+ failure_count: failure_count
120
+ )
121
+ end
122
+ return errors # stop current job from continuing to run after rescheduling
111
123
  else
112
124
  # rubocop:disable Rails/SkipsModelValidations
113
125
  ImporterRun.update_counters(importer_run_id, processed_relationships: number_of_successes)
@@ -116,8 +128,6 @@ module Bulkrax
116
128
  end
117
129
  # rubocop:enable Metrics/MethodLength
118
130
 
119
- attr_reader :importer_run
120
-
121
131
  private
122
132
 
123
133
  ##
@@ -170,7 +180,7 @@ module Bulkrax
170
180
  Bulkrax.object_factory.add_resource_to_collection(
171
181
  collection: parent_record,
172
182
  resource: child_record,
173
- user: importer_run.user
183
+ user: user
174
184
  )
175
185
  end
176
186
 
@@ -183,11 +193,8 @@ module Bulkrax
183
193
  )
184
194
  end
185
195
 
186
- def reschedule(parent_identifier:, importer_run_id:)
187
- CreateRelationshipsJob.set(wait: 10.minutes).perform_later(
188
- parent_identifier: parent_identifier,
189
- importer_run_id: importer_run_id
190
- )
196
+ def reschedule(**kargs)
197
+ CreateRelationshipsJob.set(wait: 10.minutes).perform_later(**kargs)
191
198
  end
192
199
  end
193
200
  end
@@ -1,5 +1,23 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Bulkrax
4
- class DeleteFileSetJob < DeleteJob; end
4
+ class DeleteFileSetJob < DeleteJob
5
+ def perform(entry, importer_run)
6
+ file_set = entry.factory.find
7
+ if file_set
8
+ parent = file_set.parent
9
+ if parent&.respond_to?(:ordered_members)
10
+ om = parent.ordered_members.to_a
11
+ om.delete(file_set)
12
+ parent.ordered_members = om
13
+ elsif parent.respond_to?(:member_ids)
14
+ parent.member_ids.delete(file_set.id)
15
+ Hyrax.persister.save(resource: parent)
16
+ end
17
+ parent.save
18
+ end
19
+
20
+ super
21
+ end
22
+ end
5
23
  end
@@ -16,7 +16,12 @@ module Bulkrax
16
16
  # e.g. "parents" or "parents_1"
17
17
  parent_identifier = (entry.raw_metadata[entry.related_parents_raw_mapping] || entry.raw_metadata["#{entry.related_parents_raw_mapping}_1"])&.strip
18
18
 
19
- validate_parent!(parent_identifier)
19
+ begin
20
+ validate_parent!(parent_identifier)
21
+ rescue MissingParentError => e
22
+ handle_retry(entry, importer_run_id, e)
23
+ return
24
+ end
20
25
 
21
26
  entry.build
22
27
  if entry.succeeded?
@@ -32,17 +37,6 @@ module Bulkrax
32
37
  entry.save!
33
38
  entry.importer.current_run = ImporterRun.find(importer_run_id)
34
39
  entry.importer.record_status
35
-
36
- rescue MissingParentError => e
37
- # try waiting for the parent record to be created
38
- entry.import_attempts += 1
39
- entry.save!
40
- if entry.import_attempts < 5
41
- ImportFileSetJob.set(wait: (entry.import_attempts + 1).minutes).perform_later(entry_id, importer_run_id)
42
- else
43
- ImporterRun.decrement_counter(:enqueued_records, importer_run_id) # rubocop:disable Rails/SkipsModelValidations
44
- entry.set_status_info(e)
45
- end
46
40
  end
47
41
 
48
42
  private
@@ -54,14 +48,9 @@ module Bulkrax
54
48
  return if parent_identifier.blank?
55
49
 
56
50
  find_parent_record(parent_identifier)
57
- check_parent_exists!(parent_identifier)
58
51
  check_parent_is_a_work!(parent_identifier)
59
52
  end
60
53
 
61
- def check_parent_exists!(parent_identifier)
62
- raise MissingParentError, %(Unable to find a record with the identifier "#{parent_identifier}") if parent_record.nil?
63
- end
64
-
65
54
  def check_parent_is_a_work!(parent_identifier)
66
55
  case parent_record
67
56
  when Bulkrax.collection_model_class, Bulkrax.file_model_class
@@ -72,6 +61,18 @@ module Bulkrax
72
61
 
73
62
  def find_parent_record(parent_identifier)
74
63
  _, @parent_record = find_record(parent_identifier, importer_run_id)
64
+ raise MissingParentError, %(Unable to find a record with the identifier "#{parent_identifier}") unless parent_record
65
+ end
66
+
67
+ def handle_retry(entry, importer_run_id, e)
68
+ entry.import_attempts += 1
69
+ entry.save!
70
+ if entry.import_attempts < 5
71
+ ImportFileSetJob.set(wait: (entry.import_attempts + 1).minutes).perform_later(entry.id, importer_run_id)
72
+ else
73
+ ImporterRun.decrement_counter(:enqueued_records, importer_run_id) # rubocop:disable Rails/SkipsModelValidations
74
+ entry.set_status_info(e)
75
+ end
75
76
  end
76
77
  end
77
78
  end
@@ -18,6 +18,9 @@ module Bulkrax
18
18
 
19
19
  delegate :create_parent_child_relationships, :valid_import?, :write_errored_entries_file, :visibility, to: :parser
20
20
 
21
+ after_save :set_last_imported_at_from_importer_run
22
+ after_save :set_next_import_at_from_importer_run
23
+
21
24
  attr_accessor :only_updates, :file_style, :file
22
25
  attr_writer :current_run
23
26
 
@@ -149,6 +152,18 @@ module Bulkrax
149
152
  @seen ||= {}
150
153
  end
151
154
 
155
+ def import_file_path
156
+ self.parser_fields['import_file_path']
157
+ end
158
+
159
+ def original_file?
160
+ import_file_path && File.exist?(import_file_path)
161
+ end
162
+
163
+ def original_file
164
+ import_file_path if original_file?
165
+ end
166
+
152
167
  def replace_files
153
168
  self.parser_fields['replace_files']
154
169
  end
@@ -235,5 +250,27 @@ module Bulkrax
235
250
  rescue
236
251
  "#{self.id}_#{self.created_at.strftime('%Y%m%d%H%M%S')}"
237
252
  end
253
+
254
+ private
255
+
256
+ # Adding this here since we can update the importer without running the importer.
257
+ # When we simply save the importer (as in just updating the importer from the options),
258
+ # it does not trigger the after_save callback in the importer_run.
259
+ def set_last_imported_at_from_importer_run
260
+ return if @skip_set_last_imported_at # Prevent infinite loop
261
+
262
+ @skip_set_last_imported_at = true
263
+ importer_runs.last&.set_last_imported_at
264
+ @skip_set_last_imported_at = false
265
+ end
266
+
267
+ # @see #set_last_imported_at_from_importer_run
268
+ def set_next_import_at_from_importer_run
269
+ return if @skip_set_next_import_at # Prevent infinite loop
270
+
271
+ @skip_set_next_import_at = true
272
+ importer_runs.last&.set_next_import_at
273
+ @skip_set_next_import_at = false
274
+ end
238
275
  end
239
276
  end
@@ -6,6 +6,9 @@ module Bulkrax
6
6
  has_many :statuses, as: :runnable, dependent: :destroy
7
7
  has_many :pending_relationships, dependent: :destroy
8
8
 
9
+ after_save :set_last_imported_at
10
+ after_save :set_next_import_at
11
+
9
12
  def parents
10
13
  pending_relationships.pluck(:parent_id).uniq
11
14
  end
@@ -15,5 +18,13 @@ module Bulkrax
15
18
  # fallback to the configured user.
16
19
  importer.user || Bulkrax.fallback_user_for_importer_exporter_processing
17
20
  end
21
+
22
+ def set_last_imported_at
23
+ importer.update(last_imported_at: importer.last_imported_at)
24
+ end
25
+
26
+ def set_next_import_at
27
+ importer.update(next_import_at: importer.next_import_at)
28
+ end
18
29
  end
19
30
  end
@@ -2,6 +2,8 @@
2
2
 
3
3
  module Bulkrax
4
4
  class PendingRelationship < ApplicationRecord
5
+ include Bulkrax::StatusInfo
6
+
5
7
  belongs_to :importer_run
6
8
 
7
9
  # Ideally we wouldn't have a column named "order", as it is a reserved SQL term. However, if we
@@ -32,6 +32,8 @@ module Bulkrax
32
32
  end
33
33
 
34
34
  class InnerWorkings
35
+ include Loggable
36
+
35
37
  def initialize(object_factory:)
36
38
  @object_factory = object_factory
37
39
  end
@@ -119,7 +121,7 @@ module Bulkrax
119
121
  def destroy_existing_files
120
122
  return unless object.present? && object.file_sets.present?
121
123
  object.file_sets.each do |fs|
122
- Hyrax::Actors::FileSetActor.new(fs, @user).destroy
124
+ Hyrax::Actors::FileSetActor.new(fs, user).destroy
123
125
  end
124
126
  @object = object.reload
125
127
  log_deleted_fs(object)
@@ -155,6 +157,8 @@ module Bulkrax
155
157
  end
156
158
 
157
159
  def ordered_file_sets
160
+ return [] if object.blank?
161
+
158
162
  Bulkrax.object_factory.ordered_file_sets_for(object)
159
163
  end
160
164
 
@@ -65,6 +65,7 @@ module Bulkrax
65
65
 
66
66
  parsed_metadata[name] ||= []
67
67
  parsed_metadata[name] += Array.wrap(value).flatten
68
+ parsed_metadata[name].uniq!
68
69
  end
69
70
 
70
71
  def set_parsed_object_data(object_multiple, object_name, name, index, value)
@@ -148,7 +149,18 @@ module Bulkrax
148
149
  end
149
150
 
150
151
  def fields_that_are_always_multiple
151
- %w[id delete model visibility]
152
+ @fields_that_are_always_multiple = %w[
153
+ id
154
+ delete
155
+ model
156
+ visibility
157
+ visibility_during_embargo
158
+ embargo_release_date
159
+ visibility_after_embargo
160
+ visibility_during_lease
161
+ lease_expiration_date
162
+ visibility_after_lease
163
+ ]
152
164
  end
153
165
 
154
166
  def fields_that_are_always_singular
@@ -150,7 +150,7 @@ module Bulkrax
150
150
  end
151
151
  end
152
152
 
153
- # The visibility of the record. Acceptable values are: "open", "embaro", "lease", "authenticated", "restricted". The default is "open"
153
+ # The visibility of the record. Acceptable values are: "open", "embargo", "lease", "authenticated", "restricted". The default is "open"
154
154
  #
155
155
  # @return [String]
156
156
  # @see https://github.com/samvera/hydra-head/blob/main/hydra-access-controls/app/models/concerns/hydra/access_controls/access_right.rb Hydra::AccessControls::AccessRight for details on the range of values.
@@ -1,13 +1,15 @@
1
1
  <div class='csv_fields'>
2
2
 
3
3
  <%= fi.input :visibility,
4
+ label: 'Default Visibility',
4
5
  collection: [
5
6
  ['Public', 'open'],
6
7
  ['Private', 'restricted'],
7
8
  ['Institution', 'authenticated']
8
9
  ],
9
10
  selected: importer.parser_fields['visibility'] || 'open',
10
- input_html: { class: 'form-control' }
11
+ input_html: { class: 'form-control' },
12
+ hint: 'If your CSV includes the visibility field, it will override the default setting.'
11
13
  %>
12
14
 
13
15
  <% if defined?(::Hyrax) %>
@@ -1,12 +1,12 @@
1
1
  <div class="col-xs-12 main-header">
2
2
  <h1><span class="fa fa-cloud-upload" aria-hidden="true"></span> Importer: <%= @importer.name %></h1>
3
-
4
- <% if @importer.failed_entries? %>
5
- <div class="pull-right">
3
+ <div class="pull-right">
4
+ <%= link_to 'Download Original File', importer_original_file_path(@importer.id), class: 'btn btn-primary', data: { turbolinks: false } if @importer.original_file %>
5
+ <% if @importer.failed_entries? %>
6
6
  <%= link_to 'Export Errored Entries', importer_export_errors_path(@importer.id), class: 'btn btn-primary', data: { turbolinks: false }%>
7
7
  <%= link_to 'Upload Corrected Entries', importer_upload_corrected_entries_path(@importer.id), class: 'btn btn-primary' if @importer.parser.is_a?(Bulkrax::CsvParser) %>
8
- </div>
9
- <% end %>
8
+ <% end %>
9
+ </div>
10
10
  </div>
11
11
  <div class="panel panel-default bulkrax-align-text">
12
12
  <div class="panel-body">
data/config/routes.rb CHANGED
@@ -11,6 +11,7 @@ Bulkrax::Engine.routes.draw do
11
11
  end
12
12
  resources :importers do
13
13
  put :continue
14
+ get :original_file
14
15
  get :entry_table
15
16
  get :export_errors
16
17
  collection do
@@ -1,4 +1,4 @@
1
- class AddIndexToMetadataBulkraxIdentifier < ActiveRecord::Migration[6.1]
1
+ class AddIndexToMetadataBulkraxIdentifier < ActiveRecord::Migration[5.2]
2
2
  def up
3
3
  return unless table_exists?(:orm_resources)
4
4
  return if index_exists?(:orm_resources, "(((metadata -> 'bulkrax_identifier'::text) ->> 0))", name: 'index_on_bulkrax_identifier')
@@ -0,0 +1,5 @@
1
+ class AddFileNameToUploadedFiles < ActiveRecord::Migration[5.2]
2
+ def change
3
+ add_column :uploaded_files, :filename, :string unless column_exists?(:uploaded_files, :filename)
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ class AddErrorTrackingToPendingRelationships < ActiveRecord::Migration[5.1]
2
+ def change
3
+ add_column :bulkrax_pending_relationships, :status_message, :string, default: 'Pending' unless column_exists?(:bulkrax_pending_relationships, :status_message)
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ class AddLastImportedAtToBulkraxImporters < ActiveRecord::Migration[5.1]
2
+ def change
3
+ add_column :bulkrax_importers, :last_imported_at, :datetime unless column_exists?(:bulkrax_importers, :last_imported_at)
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ class AddNextImportAtToBulkraxImporters < ActiveRecord::Migration[5.1]
2
+ def change
3
+ add_column :bulkrax_importers, :next_import_at, :datetime unless column_exists?(:bulkrax_importers, :next_import_at)
4
+ end
5
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Bulkrax
4
- VERSION = '8.0.0'
4
+ VERSION = '8.2.0'
5
5
  end
@@ -141,4 +141,25 @@ namespace :bulkrax do
141
141
  rescue => e
142
142
  puts "(#{e.message})"
143
143
  end
144
+
145
+ desc "Resave importers"
146
+ task resave_importers: :environment do
147
+ if defined?(::Hyku)
148
+ Account.find_each do |account|
149
+ next if account.name == "search"
150
+ switch!(account)
151
+ puts "=============== updating #{account.name} ============"
152
+
153
+ resave_importers
154
+
155
+ puts "=============== finished updating #{account.name} ============"
156
+ end
157
+ else
158
+ resave_importers
159
+ end
160
+ end
161
+
162
+ def resave_importers
163
+ Bulkrax::Importer.find_each(&:save!)
164
+ end
144
165
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bulkrax
3
3
  version: !ruby/object:Gem::Version
4
- version: 8.0.0
4
+ version: 8.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rob Kaufman
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-04-02 00:00:00.000000000 Z
11
+ date: 2024-09-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -334,6 +334,7 @@ files:
334
334
  - app/assets/stylesheets/bulkrax/application.css
335
335
  - app/assets/stylesheets/bulkrax/coderay.scss
336
336
  - app/assets/stylesheets/bulkrax/import_export.scss
337
+ - app/concerns/loggable.rb
337
338
  - app/controllers/bulkrax/application_controller.rb
338
339
  - app/controllers/bulkrax/entries_controller.rb
339
340
  - app/controllers/bulkrax/exporters_controller.rb
@@ -481,6 +482,10 @@ files:
481
482
  - db/migrate/20240208005801_denormalize_status_message.rb
482
483
  - db/migrate/20240209070952_update_identifier_index.rb
483
484
  - db/migrate/20240307053156_add_index_to_metadata_bulkrax_identifier.rb
485
+ - db/migrate/20240806161142_add_file_name_to_uploaded_files.rb
486
+ - db/migrate/20240823173525_add_error_tracking_to_pending_relationships.rb
487
+ - db/migrate/20240916182737_add_last_imported_at_to_bulkrax_importers.rb
488
+ - db/migrate/20240916182823_add_next_import_at_to_bulkrax_importers.rb
484
489
  - lib/bulkrax.rb
485
490
  - lib/bulkrax/engine.rb
486
491
  - lib/bulkrax/entry_spec_helper.rb
@@ -513,7 +518,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
513
518
  - !ruby/object:Gem::Version
514
519
  version: '0'
515
520
  requirements: []
516
- rubygems_version: 3.5.5
521
+ rubygems_version: 3.4.10
517
522
  signing_key:
518
523
  specification_version: 4
519
524
  summary: Import and export tool for Hyrax and Hyku