bulkrax 9.0.2 → 9.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.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +26 -0
  3. data/app/assets/javascripts/bulkrax/datatables.js +12 -0
  4. data/app/assets/javascripts/bulkrax/importers.js.erb +4 -1
  5. data/app/factories/bulkrax/object_factory.rb +36 -2
  6. data/app/factories/bulkrax/object_factory_interface.rb +26 -0
  7. data/app/factories/bulkrax/valkyrie_object_factory.rb +109 -27
  8. data/app/jobs/bulkrax/create_relationships_job.rb +123 -76
  9. data/app/jobs/bulkrax/delete_job.rb +11 -0
  10. data/app/jobs/bulkrax/importer_job.rb +1 -0
  11. data/app/matchers/bulkrax/application_matcher.rb +2 -1
  12. data/app/models/bulkrax/csv_entry.rb +41 -10
  13. data/app/models/bulkrax/importer.rb +9 -1
  14. data/app/models/bulkrax/status.rb +1 -1
  15. data/app/models/concerns/bulkrax/export_behavior.rb +28 -15
  16. data/app/models/concerns/bulkrax/file_set_entry_behavior.rb +13 -4
  17. data/app/models/concerns/bulkrax/importer_exporter_behavior.rb +1 -1
  18. data/app/parsers/bulkrax/application_parser.rb +22 -4
  19. data/app/parsers/bulkrax/csv_parser.rb +36 -6
  20. data/app/parsers/bulkrax/oai_dc_parser.rb +0 -2
  21. data/app/parsers/bulkrax/xml_parser.rb +1 -1
  22. data/app/services/bulkrax/factory_class_finder.rb +56 -15
  23. data/app/services/hyrax/custom_queries/find_by_source_identifier.rb +6 -11
  24. data/app/services/wings/custom_queries/find_by_source_identifier.rb +15 -6
  25. data/app/views/bulkrax/entries/show.html.erb +15 -9
  26. data/app/views/bulkrax/importers/_bagit_fields.html.erb +1 -1
  27. data/app/views/bulkrax/importers/_csv_fields.html.erb +1 -1
  28. data/app/views/bulkrax/importers/_oai_fields.html.erb +1 -1
  29. data/app/views/bulkrax/importers/_xml_fields.html.erb +1 -1
  30. data/app/views/bulkrax/importers/show.html.erb +4 -4
  31. data/app/views/bulkrax/shared/_entries_tab.html.erb +1 -1
  32. data/config/locales/bulkrax.en.yml +5 -3
  33. data/lib/bulkrax/engine.rb +1 -1
  34. data/lib/bulkrax/version.rb +1 -1
  35. data/lib/bulkrax.rb +6 -11
  36. data/lib/generators/bulkrax/templates/bin/importer +1 -5
  37. metadata +8 -3
  38. data/app/factories/bulkrax/valkyrize-hyku.code-workspace +0 -19
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ab6b7a8920333f225336a80591a32f401aef2cfbe466d961c6f1f7a731757625
4
- data.tar.gz: 6aa05fc7028e0f1a7f8eb40b2de8b23ba5108a254d0623bb9307a6dff2daf579
3
+ metadata.gz: fb04fb1689c90c0cb7b96d0fbab95f371e4e4718509638812e62b0bffa1cc9e0
4
+ data.tar.gz: b3df0b413a151f7c49c6a39e07a705ec0e8c87d181a66de2a0ae75d567297f08
5
5
  SHA512:
6
- metadata.gz: 33fb1b5b369b6efcc535b80887c74e919e8b8b431b2199dbef35093319f58017d1bd34498d0c1c6df8c86f94eae4f117af2b88c2753eb0065db7630e8f480128
7
- data.tar.gz: 214580d620b0c40edd3351ed315d041c86036a1453233a045b35b21991316aa9f1a397032d07d7d6a56a659aa871515c2e64535f238e02d8b591355c1b8f9760
6
+ metadata.gz: 89a766b0116729946c1ffba1252362fff3f29fd57ab774aebab39fa5b6f2b207a511de3da801b47bce7352a8ef2ca2e3811f306ca0f701ef592acd2a66103f34
7
+ data.tar.gz: b559f0441c55c23a64568d95d1bcb533dc5b11cf12c9ad8aaed3ab852a0f9ea88b1e9140968b6272af54d05d2edae3ff3dadb5433e07fa6c55d90e615f42447d
data/README.md CHANGED
@@ -208,6 +208,32 @@ We encourage everyone to help improve this project. Bug reports and pull reques
208
208
 
209
209
  This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](https://contributor-covenant.org) code of conduct.
210
210
 
211
+ ### Running tests
212
+ - The tests use sqlite as the database, so no extra dependencies are required.
213
+ - Ensure you are using a supported version of Ruby (`ruby -v` should be greater or equal to 2.7)
214
+ - If you are using a ruby version earlier than 3.0.0, install bundler explicitly (newer versions of ruby will automatically install an appropriate bundler version)
215
+ ```bash
216
+ /path/to/ruby/install/bin/gem install bundler -v '~> 2.4.0'
217
+ ```
218
+ - Decide on your version of Hyrax to test against and export it to your environment, then bundle install. The Hyrax version should be greater or equal to 2.3.
219
+ ```bash
220
+ export HYRAX_VERSION="~> 4.0.0"
221
+ bundle install
222
+ ```
223
+ - Run the test migrations
224
+ ```bash
225
+ bundle exec bin/rails db:migrate RAILS_ENV=test
226
+ ```
227
+ - Run the tests
228
+ ```bash
229
+ bundle exec rspec
230
+ ```
231
+
232
+ - Run the style checker / linter
233
+ ```bash
234
+ bundle exec rubocop
235
+ ```
236
+
211
237
  ## Questions
212
238
  Questions can be sent to support@notch8.com. Please make sure to include "Bulkrax" in the subject line of your email.
213
239
 
@@ -3,6 +3,10 @@ Blacklight.onLoad(function() {
3
3
  $('#importer-show-table').DataTable( {
4
4
  'processing': true,
5
5
  'serverSide': true,
6
+ 'width': '100%',
7
+ 'autoWidth': false,
8
+ 'scrollX': true,
9
+ 'scrollCollapse': true,
6
10
  "ajax": window.location.href.replace(/(\/(importers|exporters)\/\d+)/, "$1/entry_table.json"),
7
11
  "pageLength": 30,
8
12
  "lengthMenu": [[30, 100, 200], [30, 100, 200]],
@@ -15,6 +19,14 @@ Blacklight.onLoad(function() {
15
19
  { "data": "errors", "orderable": false },
16
20
  { "data": "actions", "orderable": false }
17
21
  ],
22
+ drawCallback: function() {
23
+ // Remove the inline styles that DataTables adds to the scrollHeadInner and table elements
24
+ // it's not perfect but better than the style being applied
25
+ setTimeout(function() {
26
+ $('.dataTables_scrollHeadInner').removeAttr('style');
27
+ $('.table.table-striped.dataTable.no-footer').removeAttr('style');
28
+ }, 100);
29
+ },
18
30
  initComplete: function () {
19
31
  // Add entry class filter
20
32
  entrySelect.bind(this)()
@@ -15,7 +15,10 @@ function prepBulkrax(event) {
15
15
  // Initialize the uploader only if hyraxUploader is defined
16
16
  if (typeof $.fn.hyraxUploader === 'function') {
17
17
  // Initialize the uploader
18
- $('.fileupload-bulkrax').hyraxUploader({ maxNumberOfFiles: 1 });
18
+ $('.fileupload-bulkrax').hyraxUploader({
19
+ maxNumberOfFiles: 1,
20
+ maxFileSize: <%= (defined?(Hyrax) && Hyrax.config.uploader[:maxFileSize]) || 524288000 %>
21
+ });
19
22
 
20
23
  // Function to toggle 'required' attribute based on uploaded files
21
24
  function toggleRequiredAttribute() {
@@ -28,6 +28,40 @@ module Bulkrax
28
28
  resource.file_sets.each(&:update_index) if resource.respond_to?(:file_sets)
29
29
  end
30
30
 
31
+ ##
32
+ # @return [String] the name of the model class for the given resource/object.
33
+ def self.model_name(resource:)
34
+ resource.has_model.first
35
+ end
36
+
37
+ ##
38
+ # A thumbnail is linked to a work rather than the file set itself.
39
+ # @return [File or FileMetadata] the thumbnail file for the given resource
40
+ def self.thumbnail_for(resource:)
41
+ return nil unless resource.respond_to?(:thumbnail)
42
+ return resource.thumbnail if resource.thumbnail.present?
43
+ return nil unless resource.respond_to?(:parent) && resource.parent.present?
44
+ return nil unless resource.parent.respond_to?(:thumbnail)
45
+ resource.parent.thumbnail
46
+ end
47
+
48
+ ##
49
+ # @input [Fileset]
50
+ # @return [File] the original file.
51
+ def self.original_file(fileset:)
52
+ fileset.try(:original_file)
53
+ end
54
+
55
+ ##
56
+ # #input [Fileset or FileMetadata]
57
+ # @return [String] the file name for the given fileset
58
+ def self.filename_for(fileset:)
59
+ file = original_file(fileset: fileset)
60
+ file.file_name.first
61
+ rescue NoMethodError
62
+ nil
63
+ end
64
+
31
65
  ##
32
66
  # @see Bulkrax::ObjectFactoryInterface
33
67
  def self.export_properties
@@ -91,7 +125,7 @@ module Bulkrax
91
125
  # @note HEY WE'RE USING THIS FOR A WINGS CUSTOM QUERY. BE CAREFUL WITH
92
126
  # REMOVING IT.
93
127
  #
94
- # @see # {Wings::CustomQueries::FindBySourceIdentifier#find_by_model_and_property_value}
128
+ # @see # {Wings::CustomQueries::FindBySourceIdentifier#find_by_property_value}
95
129
  def self.search_by_property(value:, klass:, field: nil, search_field: nil, name_field: nil, verify_property: false)
96
130
  return nil unless klass.respond_to?(:where)
97
131
  # We're not going to try to match nil nor "".
@@ -166,7 +200,7 @@ module Bulkrax
166
200
 
167
201
  def delete(_user)
168
202
  obj = find
169
- return false unless obj
203
+ raise ObjectFactoryInterface::ObjectNotFoundError, "Object not found to delete" unless obj
170
204
 
171
205
  obj.delete(eradicate: true)
172
206
  end
@@ -209,6 +209,32 @@ module Bulkrax
209
209
  end
210
210
  # rubocop:enable Metrics/ParameterLists
211
211
 
212
+ ##
213
+ # @return [String] the name of the model class for the given resource/object.
214
+ def self.model_name(resource:)
215
+ raise NotImplementedError, "#{self}.#{__method__}"
216
+ end
217
+
218
+ ##
219
+ # @return [File or FileMetadata] the thumbnail file for the given resource
220
+ def self.thumbnail_for(resource:)
221
+ raise NotImplementedError, "#{self}.#{__method__}"
222
+ end
223
+
224
+ ##
225
+ # @input [Fileset or FileMetadata]
226
+ # @return [File or FileMetadata] the original file
227
+ def self.original_file(fileset:)
228
+ raise NotImplementedError, "#{self}.#{__method__}"
229
+ end
230
+
231
+ ##
232
+ # #input [Fileset or FileMetadata]
233
+ # @return [String] the file name for the given fileset
234
+ def self.filename_for(fileset:)
235
+ raise NotImplementedError, "#{self}.#{__method__}"
236
+ end
237
+
212
238
  ##
213
239
  # @api private
214
240
  #
@@ -38,12 +38,54 @@ module Bulkrax
38
38
  end
39
39
  end
40
40
 
41
+ # Customized create method for Valkyrie so that @object gets set
42
+ def create
43
+ attrs = transform_attributes
44
+ @object = klass.new
45
+ conditionally_set_reindex_extent
46
+ run_callbacks :save do
47
+ run_callbacks :create do
48
+ @object = if klass == Bulkrax.collection_model_class
49
+ create_collection(attrs)
50
+ elsif klass == Bulkrax.file_model_class
51
+ create_file_set(attrs)
52
+ else
53
+ create_work(attrs)
54
+ end
55
+ end
56
+ end
57
+
58
+ apply_depositor_metadata
59
+ log_created(@object)
60
+ end
61
+
62
+ # Customized update method for Valkyrie so that @object gets set
63
+ def update
64
+ raise "Object doesn't exist" unless object
65
+ conditionally_destroy_existing_files
66
+
67
+ attrs = transform_attributes(update: true)
68
+ run_callbacks :save do
69
+ @object = if klass == Bulkrax.collection_model_class
70
+ update_collection(attrs)
71
+ elsif klass == Bulkrax.file_model_class
72
+ update_file_set(attrs)
73
+ else
74
+ update_work(attrs)
75
+ end
76
+ end
77
+ apply_depositor_metadata
78
+ log_updated(@object)
79
+ end
80
+
41
81
  # TODO: the following module needs revisiting for Valkyrie work.
42
82
  # proposal is to create Bulkrax::ValkyrieFileFactory.
43
83
  include Bulkrax::FileFactory
44
84
 
45
85
  self.file_set_factory_inner_workings_class = Bulkrax::ValkyrieObjectFactory::FileFactoryInnerWorkings
46
86
 
87
+ delegate :transactions, to: :class
88
+
47
89
  ##
48
90
  # When you want a different set of transactions you can change the
49
91
  # container.
@@ -55,24 +97,25 @@ module Bulkrax
55
97
  @transactions || Hyrax::Transactions::Container
56
98
  end
57
99
 
58
- def transactions
59
- self.class.transactions
60
- end
61
-
62
100
  ##
63
101
  # @!group Class Method Interface
64
102
 
65
103
  ##
66
- # @note This does not save either object. We need to do that in another
67
- # loop. Why? Because we might be adding many items to the parent.
104
+ # When adding a child to a parent work, we save the parent.
105
+ # Locking appears inconsistent, so we are finding the parent and
106
+ # saving it with each child, but waiting until the end to reindex.
107
+ # To do this we are bypassing the save! method defined below
68
108
  def self.add_child_to_parent_work(parent:, child:)
109
+ parent = self.find(parent.id)
69
110
  return true if parent.member_ids.include?(child.id)
70
-
71
111
  parent.member_ids << child.id
72
- parent.save
112
+ Hyrax.persister.save(resource: parent)
73
113
  end
74
114
 
115
+ ##
116
+ # The resource added to a collection can be either a work or another collection.
75
117
  def self.add_resource_to_collection(collection:, resource:, user:)
118
+ resource = self.find(resource.id)
76
119
  resource.member_of_collection_ids << collection.id
77
120
  save!(resource: resource, user: user)
78
121
  end
@@ -112,7 +155,7 @@ module Bulkrax
112
155
  Hyrax.query_service.find_by(id: id)
113
156
  # Because Hyrax is not a hard dependency, we need to transform the Hyrax exception into a
114
157
  # common exception so that callers can handle a generalize exception.
115
- rescue Hyrax::ObjectNotFoundError => e
158
+ rescue Hyrax::ObjectNotFoundError, Valkyrie::Persistence::ObjectNotFoundError => e
116
159
  raise ObjectFactoryInterface::ObjectNotFoundError, e.message
117
160
  end
118
161
 
@@ -127,6 +170,8 @@ module Bulkrax
127
170
  end
128
171
 
129
172
  def self.publish(event:, **kwargs)
173
+ # It's a bit unclear what this should be if we can't rely on Hyrax.
174
+ raise NotImplementedError, "#{self}.#{__method__}" unless defined?(Hyrax)
130
175
  Hyrax.publisher.publish(event, **kwargs)
131
176
  end
132
177
 
@@ -139,19 +184,19 @@ module Bulkrax
139
184
  end
140
185
 
141
186
  def self.save!(resource:, user:)
142
- if resource.respond_to?(:save!)
143
- resource.save!
144
- else
187
+ if defined?(Hyrax)
145
188
  result = Hyrax.persister.save(resource: resource)
146
189
  raise Valkyrie::Persistence::ObjectNotFoundError unless result
147
190
  Hyrax.index_adapter.save(resource: result)
148
191
  if result.collection?
149
- publish('collection.metadata.updated', collection: result, user: user)
192
+ self.publish(event: 'collection.metadata.updated', collection: result, user: user)
150
193
  else
151
- publish('object.metadata.updated', object: result, user: user)
194
+ self.publish(event: 'object.metadata.updated', object: result, user: user)
152
195
  end
153
- resource
196
+ else
197
+ resource.save!
154
198
  end
199
+ resource
155
200
  end
156
201
 
157
202
  def self.update_index(resources:)
@@ -165,6 +210,46 @@ module Bulkrax
165
210
  update_index(resources: file_sets)
166
211
  end
167
212
 
213
+ ##
214
+ # If we always want the valkyrized resource name, even for unmigrated objects, we can
215
+ # simply use resource.model_name.name. At this point, we are differentiating
216
+ # to help identify items which have been migrated to Valkyrie vs those which have not.
217
+ #
218
+ # @return [String] the name of the model class for the given resource/object.
219
+ def self.model_name(resource:)
220
+ resource.class.to_s
221
+ end
222
+
223
+ ##
224
+ # @return [File or FileMetadata] the thumbnail file for the given resource
225
+ def self.thumbnail_for(resource:)
226
+ # recursive call to parent if resource is a fileset - we want the work's thumbnail
227
+ return thumbnail_for(resource: resource&.parent) if resource.is_a?(Bulkrax.file_model_class)
228
+
229
+ return nil unless resource.respond_to?(:thumbnail_id) && resource.thumbnail_id.present?
230
+ Bulkrax.object_factory.find(resource.thumbnail_id.to_s)
231
+ rescue Bulkrax::ObjectFactoryInterface::ObjectNotFoundError
232
+ nil
233
+ end
234
+
235
+ ##
236
+ # @input [Fileset or FileMetadata]
237
+ # @return [FileMetadata] the original file
238
+ def self.original_file(fileset:)
239
+ fileset.try(:original_file) || fileset
240
+ end
241
+
242
+ ##
243
+ # #input [Fileset or FileMetadata]
244
+ # @return [String] the file name for the given fileset
245
+ def self.filename_for(fileset:)
246
+ file = original_file(fileset: fileset)
247
+ return nil unless file
248
+ file.original_filename
249
+ rescue NoMethodError
250
+ nil
251
+ end
252
+
168
253
  ##
169
254
  # @param value [String]
170
255
  # @param klass [Class, #where]
@@ -176,13 +261,12 @@ module Bulkrax
176
261
  # @return [Valkyrie::Resource] when a match is found, an instance of given
177
262
  # :klass
178
263
  # rubocop:disable Metrics/ParameterLists
179
- def self.search_by_property(value:, klass:, field: nil, name_field: nil, **)
264
+ def self.search_by_property(value:, field: nil, name_field: nil, search_field:, **)
180
265
  name_field ||= field
181
266
  raise "Expected named_field or field got nil" if name_field.blank?
182
267
  return if value.blank?
183
-
184
268
  # Return nil or a single object.
185
- Hyrax.query_service.custom_query.find_by_model_and_property_value(model: klass, property: name_field, value: value)
269
+ Hyrax.query_service.custom_queries.find_by_property_value(property: name_field, value: value, search_field: search_field)
186
270
  end
187
271
  # rubocop:enable Metrics/ParameterLists
188
272
 
@@ -208,11 +292,11 @@ module Bulkrax
208
292
 
209
293
  def delete(user)
210
294
  obj = find
211
- return false unless obj
295
+ raise ObjectFactoryInterface::ObjectNotFoundError, "Object not found to delete" unless obj
212
296
 
213
297
  Hyrax.persister.delete(resource: obj)
214
298
  Hyrax.index_adapter.delete(resource: obj)
215
- self.class.publish(event: 'object.deleted', object: obj, user: user)
299
+ Hyrax.publisher.publish('object.deleted', object: obj, user: user)
216
300
  end
217
301
 
218
302
  def run!
@@ -231,7 +315,7 @@ module Bulkrax
231
315
 
232
316
  @object.depositor = @user.email
233
317
  object = Hyrax.persister.save(resource: @object)
234
- self.class.publish(event: "object.metadata.updated", object: object, user: @user)
318
+ Hyrax.publisher.publish("object.metadata.updated", object: object, user: @user)
235
319
  object
236
320
  end
237
321
 
@@ -248,6 +332,7 @@ module Bulkrax
248
332
 
249
333
  def create_file_set(attrs)
250
334
  # TODO: Make it work for Valkyrie
335
+ raise NotImplementedError, __method__.to_s
251
336
  end
252
337
 
253
338
  def create_work(attrs)
@@ -337,7 +422,7 @@ module Bulkrax
337
422
  end
338
423
 
339
424
  def find_by_id
340
- Hyrax.query_service.find_by(id: attributes[:id]) if attributes.key? :id
425
+ self.class.find(attributes[:id]) if attributes.key? :id
341
426
  end
342
427
 
343
428
  ##
@@ -433,7 +518,6 @@ module Bulkrax
433
518
  remote_files.map do |r|
434
519
  file_path = download_file(r["url"])
435
520
  next unless file_path
436
-
437
521
  create_uploaded_file(file_path, r["file_name"])
438
522
  end.compact
439
523
  end
@@ -449,8 +533,7 @@ module Bulkrax
449
533
  file.rewind
450
534
  file.path
451
535
  rescue => e
452
- Rails.logger.debug "Failed to download file from #{url}: #{e.message}"
453
- nil
536
+ raise "Failed to download file from #{url}: #{e.message}"
454
537
  end
455
538
  end
456
539
 
@@ -460,8 +543,7 @@ module Bulkrax
460
543
  file.close
461
544
  uploaded_file
462
545
  rescue => e
463
- Rails.logger.debug "Failed to create Hyrax::UploadedFile for #{file_name}: #{e.message}"
464
- nil
546
+ raise "Failed to create Hyrax::UploadedFile for #{file_name}: #{e.message}"
465
547
  end
466
548
 
467
549
  # @Override Destroy existing files with Hyrax::Transactions