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.
- checksums.yaml +4 -4
- data/app/controllers/bulkrax/entries_controller.rb +9 -2
- data/app/controllers/bulkrax/exporters_controller.rb +18 -9
- data/app/controllers/bulkrax/importers_controller.rb +15 -6
- data/app/factories/bulkrax/object_factory.rb +52 -3
- data/app/helpers/bulkrax/application_helper.rb +1 -1
- data/app/helpers/bulkrax/importers_helper.rb +2 -2
- data/app/jobs/bulkrax/create_relationships_job.rb +75 -59
- data/app/jobs/bulkrax/delete_job.rb +1 -1
- data/app/jobs/bulkrax/export_work_job.rb +2 -2
- data/app/jobs/bulkrax/import_file_set_job.rb +2 -1
- data/app/jobs/bulkrax/import_work_job.rb +13 -5
- data/app/jobs/bulkrax/importer_job.rb +1 -1
- data/app/matchers/bulkrax/application_matcher.rb +4 -2
- data/app/models/bulkrax/csv_entry.rb +15 -3
- data/app/models/bulkrax/entry.rb +2 -1
- data/app/models/bulkrax/exporter.rb +15 -7
- data/app/models/bulkrax/importer.rb +4 -4
- data/app/models/bulkrax/importer_run.rb +6 -0
- data/app/models/bulkrax/oai_entry.rb +54 -8
- data/app/models/bulkrax/pending_relationship.rb +4 -0
- data/app/models/bulkrax/rdf_entry.rb +1 -1
- data/app/models/bulkrax/xml_entry.rb +54 -12
- data/app/models/concerns/bulkrax/dynamic_record_lookup.rb +2 -0
- data/app/models/concerns/bulkrax/file_factory.rb +9 -3
- data/app/models/concerns/bulkrax/import_behavior.rb +17 -10
- data/app/models/concerns/bulkrax/status_info.rb +9 -4
- data/app/parsers/bulkrax/application_parser.rb +7 -1
- data/app/parsers/bulkrax/bagit_parser.rb +1 -1
- data/app/parsers/bulkrax/csv_parser.rb +10 -3
- data/app/parsers/bulkrax/xml_parser.rb +6 -0
- data/app/views/bulkrax/exporters/_form.html.erb +33 -17
- data/app/views/bulkrax/exporters/edit.html.erb +1 -1
- data/app/views/bulkrax/exporters/new.html.erb +1 -1
- data/app/views/bulkrax/importers/_form.html.erb +5 -5
- data/app/views/hyrax/dashboard/sidebar/_bulkrax_sidebar_additions.html.erb +3 -1
- data/app/views/hyrax/dashboard/sidebar/_repository_content.html.erb +24 -21
- data/lib/bulkrax/entry_spec_helper.rb +173 -0
- data/lib/bulkrax/version.rb +1 -1
- data/lib/bulkrax.rb +53 -0
- data/lib/generators/bulkrax/install_generator.rb +20 -0
- data/lib/generators/bulkrax/templates/config/initializers/bulkrax.rb +3 -1
- metadata +9 -8
@@ -1,27 +1,30 @@
|
|
1
|
-
|
1
|
+
<li class="h5 nav-item"><%= t('hyrax.admin.sidebar.repository_objects') %></li>
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
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
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
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
|
-
|
17
|
+
<% if ::Hyrax::DashboardController&.respond_to?(:sidebar_partials) %>
|
18
18
|
<%= render 'hyrax/dashboard/sidebar/menu_partials', menu: menu, section: :repository_content %>
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
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
|
data/lib/bulkrax/version.rb
CHANGED
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
|
-
|
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
|
+
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:
|
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.
|
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.
|
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: []
|