bulkrax 4.4.0 → 5.1.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 (43) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/bulkrax/entries_controller.rb +9 -2
  3. data/app/controllers/bulkrax/exporters_controller.rb +18 -9
  4. data/app/controllers/bulkrax/importers_controller.rb +15 -6
  5. data/app/factories/bulkrax/object_factory.rb +52 -3
  6. data/app/helpers/bulkrax/application_helper.rb +1 -1
  7. data/app/helpers/bulkrax/importers_helper.rb +2 -2
  8. data/app/jobs/bulkrax/create_relationships_job.rb +75 -59
  9. data/app/jobs/bulkrax/delete_job.rb +1 -1
  10. data/app/jobs/bulkrax/export_work_job.rb +2 -2
  11. data/app/jobs/bulkrax/import_file_set_job.rb +2 -1
  12. data/app/jobs/bulkrax/import_work_job.rb +13 -5
  13. data/app/jobs/bulkrax/importer_job.rb +1 -1
  14. data/app/matchers/bulkrax/application_matcher.rb +4 -2
  15. data/app/models/bulkrax/csv_entry.rb +15 -3
  16. data/app/models/bulkrax/entry.rb +2 -1
  17. data/app/models/bulkrax/exporter.rb +15 -7
  18. data/app/models/bulkrax/importer.rb +4 -4
  19. data/app/models/bulkrax/importer_run.rb +6 -0
  20. data/app/models/bulkrax/oai_entry.rb +54 -8
  21. data/app/models/bulkrax/pending_relationship.rb +4 -0
  22. data/app/models/bulkrax/rdf_entry.rb +1 -1
  23. data/app/models/bulkrax/xml_entry.rb +54 -12
  24. data/app/models/concerns/bulkrax/dynamic_record_lookup.rb +2 -0
  25. data/app/models/concerns/bulkrax/file_factory.rb +9 -3
  26. data/app/models/concerns/bulkrax/import_behavior.rb +17 -10
  27. data/app/models/concerns/bulkrax/status_info.rb +9 -4
  28. data/app/parsers/bulkrax/application_parser.rb +7 -1
  29. data/app/parsers/bulkrax/bagit_parser.rb +1 -1
  30. data/app/parsers/bulkrax/csv_parser.rb +10 -3
  31. data/app/parsers/bulkrax/xml_parser.rb +6 -0
  32. data/app/views/bulkrax/exporters/_form.html.erb +33 -17
  33. data/app/views/bulkrax/exporters/edit.html.erb +1 -1
  34. data/app/views/bulkrax/exporters/new.html.erb +1 -1
  35. data/app/views/bulkrax/importers/_form.html.erb +5 -5
  36. data/app/views/hyrax/dashboard/sidebar/_bulkrax_sidebar_additions.html.erb +3 -1
  37. data/app/views/hyrax/dashboard/sidebar/_repository_content.html.erb +24 -21
  38. data/lib/bulkrax/entry_spec_helper.rb +173 -0
  39. data/lib/bulkrax/version.rb +1 -1
  40. data/lib/bulkrax.rb +53 -0
  41. data/lib/generators/bulkrax/install_generator.rb +20 -0
  42. data/lib/generators/bulkrax/templates/config/initializers/bulkrax.rb +3 -1
  43. metadata +9 -8
@@ -1,27 +1,30 @@
1
- <li class="h5 nav-item"><%= t('hyrax.admin.sidebar.repository_objects') %></li>
1
+ <li class="h5 nav-item"><%= t('hyrax.admin.sidebar.repository_objects') %></li>
2
2
 
3
- <%= menu.nav_link(hyrax.my_collections_path,
4
- class: "nav-link",
5
- onclick: "dontChangeAccordion(event);",
6
- also_active_for: hyrax.dashboard_collections_path) do %>
7
- <span class="fa fa-folder-open" aria-hidden="true"></span> <span class="sidebar-action-text"><%= t('hyrax.admin.sidebar.collections') %></span>
8
- <% end %>
3
+ <%= menu.nav_link(hyrax.my_collections_path,
4
+ class: "nav-link",
5
+ onclick: "dontChangeAccordion(event);",
6
+ also_active_for: hyrax.dashboard_collections_path) do %>
7
+ <span class="fa fa-folder-open" aria-hidden="true"></span> <span class="sidebar-action-text"><%= t('hyrax.admin.sidebar.collections') %></span>
8
+ <% end %>
9
9
 
10
- <%= menu.nav_link(hyrax.my_works_path,
11
- class: "nav-link",
12
- onclick: "dontChangeAccordion(event);",
13
- also_active_for: hyrax.dashboard_works_path) do %>
14
- <span class="fa fa-file" aria-hidden="true"></span> <span class="sidebar-action-text"><%= t('hyrax.admin.sidebar.works') %></span>
15
- <% end %>
10
+ <%= menu.nav_link(hyrax.my_works_path,
11
+ class: "nav-link",
12
+ onclick: "dontChangeAccordion(event);",
13
+ also_active_for: hyrax.dashboard_works_path) do %>
14
+ <span class="fa fa-file" aria-hidden="true"></span> <span class="sidebar-action-text"><%= t('hyrax.admin.sidebar.works') %></span>
15
+ <% end %>
16
16
 
17
- <% if ::Hyrax::DashboardController&.respond_to?(:sidebar_partials) %>
17
+ <% if ::Hyrax::DashboardController&.respond_to?(:sidebar_partials) %>
18
18
  <%= render 'hyrax/dashboard/sidebar/menu_partials', menu: menu, section: :repository_content %>
19
- <% else %>
20
- <%= menu.nav_link(bulkrax.importers_path) do %>
21
- <span class="fa fa-cloud-upload" aria-hidden="true"></span> <span class="sidebar-action-text"><%= t('bulkrax.admin.sidebar.importers') %></span>
22
- <% end %>
23
-
24
- <%= menu.nav_link(bulkrax.exporters_path) do %>
25
- <span class="fa fa-cloud-download" aria-hidden="true"></span> <span class="sidebar-action-text"><%= t('bulkrax.admin.sidebar.exporters') %></span>
19
+ <% else %>
20
+ <% if current_ability.can_import_works? %>
21
+ <%= menu.nav_link(bulkrax.importers_path) do %>
22
+ <span class="fa fa-cloud-upload" aria-hidden="true"></span> <span class="sidebar-action-text"><%= t('bulkrax.admin.sidebar.importers') %></span>
23
+ <% end %>
26
24
  <% end %>
25
+ <% if current_ability.can_export_works? %>
26
+ <%= menu.nav_link(bulkrax.exporters_path) do %>
27
+ <span class="fa fa-cloud-download" aria-hidden="true"></span> <span class="sidebar-action-text"><%= t('bulkrax.admin.sidebar.exporters') %></span>
28
+ <% end %>
27
29
  <% end %>
30
+ <% end %>
@@ -0,0 +1,173 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'oai'
4
+ require 'xml/libxml'
5
+
6
+ module Bulkrax
7
+ ##
8
+ # The purpose of this module is to provide some testing facilities for those that include the
9
+ # Bulkrax gem in their application.
10
+ #
11
+ # This module came about through a desire to expose a quick means of vetting the accuracy of the
12
+ # different parsers.
13
+ module EntrySpecHelper
14
+ ##
15
+ # @api public
16
+ # @since v5.0.1
17
+ #
18
+ # The purpose of this method is encapsulate the logic of creating the appropriate Bulkrax::Entry
19
+ # object based on the given data, identifier, and parser_class_name.
20
+ #
21
+ # From that entry, you should be able to test how {Bulkrax::Entry#build_metadata} populates the
22
+ # {Bulkrax::Entry#parsed_metadata} variable. Other uses may emerge.
23
+ #
24
+ # @param data [Object] the data that we use to populate the raw metadata. Due to implementation
25
+ # details of each entry, the data will be of different formats.
26
+ #
27
+ # @param identifier [String, Integer] The identifier of the entry. This might also be found in
28
+ # the metadata of the entry, but for instantiation purposes we need this value.
29
+ # @param parser_class_name [String] The name of the parser class you're wanting to test.
30
+ # @param type [Sybmol] The type of entry (e.g. :entry, :collection, :file_set) for testing.
31
+ # @param options [Hash<Symbol,Object>] these are to be passed along into the instantiation of
32
+ # the various classes. See implementation details.
33
+ #
34
+ # @return [Bulkrax::Entry]
35
+ def self.entry_for(data:, identifier:, parser_class_name:, type: :entry, **options)
36
+ importer = importer_for(parser_class_name: parser_class_name, **options)
37
+ entry_type_method_name = ENTRY_TYPE_TO_METHOD_NAME_MAP.fetch(type)
38
+ entry_class = importer.parser.public_send(entry_type_method_name)
39
+
40
+ # Using an instance of the entry_class to dispatch to different
41
+ entry_for_dispatch = entry_class.new
42
+
43
+ # Using the {is_a?} test we get the benefit of inspecting an object's inheritance path
44
+ # (e.g. ancestry). The logic, as implemented, also provides a mechanism for folks in their
45
+ # applications to add a {class_name_entry_for}; something that I suspect isn't likely
46
+ # but given the wide variety of underlying needs I could see happening and I want to encourage
47
+ # patterned thinking to fold that different build method into this structure.
48
+ key = entry_class_to_symbol_map.keys.detect { |class_name| entry_for_dispatch.is_a?(class_name.constantize) }
49
+
50
+ # Yes, we'll raise an error if we didn't find a corresponding key. And that's okay.
51
+ symbol = entry_class_to_symbol_map.fetch(key)
52
+
53
+ send("build_#{symbol}_entry_for",
54
+ importer: importer,
55
+ identifier: identifier,
56
+ entry_class: entry_class,
57
+ data: data,
58
+ **options)
59
+ end
60
+
61
+ ENTRY_TYPE_TO_METHOD_NAME_MAP = {
62
+ entry: :entry_class,
63
+ collection: :collection_entry_class,
64
+ file_set: :file_set_entry_class
65
+ }.freeze
66
+
67
+ DEFAULT_ENTRY_CLASS_TO_SYMBOL_MAP = {
68
+ 'Bulkrax::OaiEntry' => :oai,
69
+ 'Bulkrax::XmlEntry' => :xml,
70
+ 'Bulkrax::CsvEntry' => :csv
71
+ }.freeze
72
+
73
+ # Present implementations of entry classes tend to inherit from the below listed class names.
74
+ # We're not looking to register all descendents of the {Bulkrax::Entry} class, but instead find
75
+ # the ancestor where there is significant deviation.
76
+ def self.entry_class_to_symbol_map
77
+ @entry_class_to_symbol_map || DEFAULT_ENTRY_CLASS_TO_SYMBOL_MAP
78
+ end
79
+
80
+ def self.entry_class_to_symbol_map=(value)
81
+ @entry_class_to_symbol_map = value
82
+ end
83
+
84
+ def self.importer_for(parser_class_name:, parser_fields: {}, **options)
85
+ # Ideally, we could pass in the field_mapping. However, there is logic that ignores the
86
+ # parser's field_mapping and directly asks for Bulkrax's field_mapping (e.g. model_mapping
87
+ # method).
88
+ Rails.logger.warn("You passed :importer_field_mapping as an option. This may not fully work as desired.") if options.key?(:importer_field_mapping)
89
+ Bulkrax::Importer.new(
90
+ name: options.fetch(:importer_name, "Test importer for identifier"),
91
+ admin_set_id: options.fetch(:importer_admin_set_id, "admin_set/default"),
92
+ user: options.fetch(:importer_user, User.new(email: "hello@world.com")),
93
+ limit: options.fetch(:importer_limits, 1),
94
+ parser_klass: parser_class_name,
95
+ field_mapping: options.fetch(:importer_field_mappings) { Bulkrax.field_mappings.fetch(parser_class_name) },
96
+ parser_fields: parser_fields
97
+ ).tap do |importer|
98
+ # Why are we saving the importer and a run? We might want to delve deeper into the call
99
+ # stack. See https://github.com/scientist-softserv/adventist-dl/pull/266
100
+ importer.save!
101
+ # Later on, we might to want a current run
102
+ importer.importer_runs.create!
103
+ end
104
+ end
105
+ private_class_method :importer_for
106
+
107
+ ##
108
+ # @api private
109
+ #
110
+ # @param data [Hash<Symbol,String>] we're expecting a hash with keys that are symbols and then
111
+ # values that are strings.
112
+ #
113
+ # @return [Bulkrax::CsvEntry]
114
+ #
115
+ # @note As a foible of this implementation, you'll need to include along a CSV to establish the
116
+ # columns that you'll parse (e.g. the first row
117
+ def self.build_csv_entry_for(importer:, data:, identifier:, entry_class:, **_options)
118
+ entry_class.new(
119
+ importerexporter: importer,
120
+ identifier: identifier,
121
+ raw_metadata: data
122
+ )
123
+ end
124
+
125
+ ##
126
+ # @api private
127
+ #
128
+ # @param data [String] we're expecting a string that is well-formed XML for OAI parsing.
129
+ #
130
+ # @return [Bulkrax::OaiEntry]
131
+ def self.build_oai_entry_for(importer:, data:, identifier:, entry_class:, **options)
132
+ # The raw record assumes we take the XML data, parse it and then send that to the
133
+ # OAI::GetRecordResponse object.
134
+ doc = XML::Parser.string(data)
135
+ raw_record = OAI::GetRecordResponse.new(doc.parse)
136
+
137
+ raw_metadata = {
138
+ importer.parser.source_identifier.to_s => identifier,
139
+ "data" => data,
140
+ "collections" => options.fetch(:raw_metadata_collections, []),
141
+ "children" => options.fetch(:raw_metadata_children, [])
142
+ }
143
+
144
+ entry_class.new(
145
+ raw_record: raw_record,
146
+ importerexporter: importer,
147
+ identifier: identifier,
148
+ raw_metadata: raw_metadata
149
+ )
150
+ end
151
+
152
+ ##
153
+ # @api private
154
+ #
155
+ # @param data [String] we're expecting a string that is well-formed XML.
156
+ #
157
+ # @return [Bulkrax::XmlEntry]
158
+ def self.build_xml_entry_for(importer:, data:, identifier:, entry_class:, **options)
159
+ raw_metadata = {
160
+ importer.parser.source_identifier.to_s => identifier,
161
+ "data" => data,
162
+ "collections" => options.fetch(:raw_metadata_collections, []),
163
+ "children" => options.fetch(:raw_metadata_children, [])
164
+ }
165
+
166
+ entry_class.new(
167
+ importerexporter: importer,
168
+ identifier: identifier,
169
+ raw_metadata: raw_metadata
170
+ )
171
+ end
172
+ end
173
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Bulkrax
4
- VERSION = '4.4.0'
4
+ VERSION = '5.1.0'
5
5
  end
data/lib/bulkrax.rb CHANGED
@@ -4,8 +4,13 @@ require "bulkrax/version"
4
4
  require "bulkrax/engine"
5
5
  require 'active_support/all'
6
6
 
7
+ # rubocop:disable Metrics/ModuleLength
7
8
  module Bulkrax
8
9
  class << self
10
+ # @todo Move from module attribute methods to a configuration class. With module attributes,
11
+ # when we make a change we are polluting the global space. This means that our tests that
12
+ # modify these config values are modifying global state. Which is not desirous, as it can
13
+ # introduce unexpected flakey tests.
9
14
  mattr_accessor :api_definition,
10
15
  :default_field_mapping,
11
16
  :default_work_type,
@@ -168,4 +173,52 @@ module Bulkrax
168
173
  def self.setup
169
174
  yield self
170
175
  end
176
+
177
+ # Responsible for stripping hidden characters from the given string.
178
+ #
179
+ # @param value [#to_s]
180
+ # @return [String] with hidden characters removed
181
+ #
182
+ # @see https://github.com/samvera-labs/bulkrax/issues/688
183
+ def self.normalize_string(value)
184
+ # Removing [Byte Order Mark (BOM)](https://en.wikipedia.org/wiki/Byte_order_mark)
185
+ value.to_s.delete("\xEF\xBB\xBF")
186
+ end
187
+
188
+ def self.fallback_user_for_importer_exporter_processing
189
+ return User.batch_user if defined?(Hyrax) && User.respond_to?(:batch_user)
190
+
191
+ raise "We have no fallback user available for Bulkrax.fallback_user_for_importer_exporter_processing"
192
+ end
193
+
194
+ # This class confirms to the Active::Support.serialze interface. It's job is to ensure that we
195
+ # don't have keys with the tricksy Byte Order Mark character.
196
+ #
197
+ # @see https://api.rubyonrails.org/classes/ActiveRecord/AttributeMethods/Serialization/ClassMethods.html#method-i-serialize
198
+ class NormalizedJson
199
+ def self.normalize_keys(hash)
200
+ return hash unless hash.respond_to?(:each_pair)
201
+ returning_value = {}
202
+ hash.each_pair do |key, value|
203
+ returning_value[Bulkrax.normalize_string(key)] = value
204
+ end
205
+ returning_value
206
+ end
207
+
208
+ # When we write the serialized data to the database, we "dump" the value into that database
209
+ # column.
210
+ def self.dump(value)
211
+ JSON.dump(normalize_keys(value))
212
+ end
213
+
214
+ # When we load the serialized data from the database, we pass the database's value into "load"
215
+ # function.
216
+ #
217
+ # rubocop:disable Security/JSONLoad
218
+ def self.load(string)
219
+ normalize_keys(JSON.load(string))
220
+ end
221
+ # rubocop:enable Security/JSONLoad
222
+ end
171
223
  end
224
+ # rubocop:disable Metrics/ModuleLength
@@ -55,6 +55,26 @@ class Bulkrax::InstallGenerator < Rails::Generators::Base
55
55
  end
56
56
  end
57
57
 
58
+ def add_ability
59
+ file = 'app/models/ability.rb'
60
+ file_text = File.read(file)
61
+ import_line = 'def can_import_works?'
62
+ export_line = 'def can_export_works?'
63
+ unless file_text.include?(import_line)
64
+ insert_into_file file, before: /^end/ do
65
+ " def can_import_works?\n can_create_any_work?\n end"
66
+ end
67
+ end
68
+
69
+ # rubocop:disable Style/GuardClause
70
+ unless file_text.include?(export_line)
71
+ insert_into_file file, before: /^end/ do
72
+ " def can_export_works?\n can_create_any_work?\n end"
73
+ end
74
+ end
75
+ # rubocop:enable Style/GuardClause
76
+ end
77
+
58
78
  def add_css
59
79
  ['css', 'scss', 'sass'].map do |ext|
60
80
  file = "app/assets/stylesheets/application.#{ext}"
@@ -80,4 +80,6 @@ Bulkrax.setup do |config|
80
80
  end
81
81
 
82
82
  # Sidebar for hyrax 3+ support
83
- Hyrax::DashboardController.sidebar_partials[:repository_content] << "hyrax/dashboard/sidebar/bulkrax_sidebar_additions" if Object.const_defined?(:Hyrax) && ::Hyrax::DashboardController&.respond_to?(:sidebar_partials)
83
+ if Object.const_defined?(:Hyrax) && ::Hyrax::DashboardController&.respond_to?(:sidebar_partials)
84
+ Hyrax::DashboardController.sidebar_partials[:repository_content] << "hyrax/dashboard/sidebar/bulkrax_sidebar_additions"
85
+ 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: 4.4.0
4
+ version: 5.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rob Kaufman
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-12-01 00:00:00.000000000 Z
11
+ date: 2023-02-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -106,14 +106,14 @@ dependencies:
106
106
  requirements:
107
107
  - - "~>"
108
108
  - !ruby/object:Gem::Version
109
- version: 3.1.0
109
+ version: 3.2.4
110
110
  type: :runtime
111
111
  prerelease: false
112
112
  version_requirements: !ruby/object:Gem::Requirement
113
113
  requirements:
114
114
  - - "~>"
115
115
  - !ruby/object:Gem::Version
116
- version: 3.1.0
116
+ version: 3.2.4
117
117
  - !ruby/object:Gem::Dependency
118
118
  name: loofah
119
119
  requirement: !ruby/object:Gem::Requirement
@@ -396,6 +396,7 @@ files:
396
396
  - db/migrate/20220609001128_rename_bulkrax_importer_run_to_importer_run.rb
397
397
  - lib/bulkrax.rb
398
398
  - lib/bulkrax/engine.rb
399
+ - lib/bulkrax/entry_spec_helper.rb
399
400
  - lib/bulkrax/version.rb
400
401
  - lib/generators/bulkrax/install_generator.rb
401
402
  - lib/generators/bulkrax/templates/README
@@ -410,7 +411,7 @@ homepage: https://github.com/samvera-labs/bulkrax
410
411
  licenses:
411
412
  - Apache-2.0
412
413
  metadata: {}
413
- post_install_message:
414
+ post_install_message:
414
415
  rdoc_options: []
415
416
  require_paths:
416
417
  - lib
@@ -425,8 +426,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
425
426
  - !ruby/object:Gem::Version
426
427
  version: '0'
427
428
  requirements: []
428
- rubygems_version: 3.0.3
429
- signing_key:
429
+ rubygems_version: 3.0.3.1
430
+ signing_key:
430
431
  specification_version: 4
431
432
  summary: Import and export tool for Hyrax and Hyku
432
433
  test_files: []