bulkrax 9.5.1 → 10.0.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/README.md +49 -0
- data/app/controllers/bulkrax/application_controller.rb +83 -0
- data/app/controllers/bulkrax/entries_controller.rb +16 -13
- data/app/controllers/bulkrax/exporters_controller.rb +21 -25
- data/app/controllers/bulkrax/guided_imports_controller.rb +4 -8
- data/app/controllers/bulkrax/importers_controller.rb +26 -29
- data/app/controllers/concerns/bulkrax/datatables_behavior.rb +18 -12
- data/app/controllers/concerns/bulkrax/importer_file_handler.rb +1 -1
- data/app/factories/bulkrax/object_factory.rb +2 -2
- data/app/factories/bulkrax/valkyrie_object_factory.rb +29 -17
- data/app/helpers/bulkrax/validation_helper.rb +1 -1
- data/app/jobs/bulkrax/create_relationships_job.rb +1 -1
- data/app/jobs/bulkrax/import_collection_job.rb +9 -2
- data/app/matchers/bulkrax/application_matcher.rb +5 -2
- data/app/models/bulkrax/csv_entry.rb +1 -1
- data/app/models/bulkrax/current.rb +7 -0
- data/app/models/bulkrax/export_scope.rb +21 -0
- data/app/models/bulkrax/exporter.rb +2 -2
- data/app/models/bulkrax/importer.rb +1 -1
- data/app/models/concerns/bulkrax/ability_behavior.rb +139 -0
- data/app/models/concerns/bulkrax/has_matchers.rb +1 -1
- data/app/models/concerns/bulkrax/import_behavior.rb +3 -3
- data/app/parsers/bulkrax/csv_parser.rb +13 -12
- data/app/parsers/bulkrax/oai_dc_parser.rb +1 -1
- data/app/parsers/bulkrax/parser_export_record_set.rb +26 -8
- data/app/parsers/concerns/bulkrax/csv_parser/csv_template_generation.rb +1 -1
- data/app/parsers/concerns/bulkrax/csv_parser/csv_validation.rb +1 -2
- data/app/parsers/concerns/bulkrax/csv_parser/csv_validation_helpers.rb +18 -1
- data/app/parsers/concerns/bulkrax/csv_parser/csv_validation_hierarchy.rb +2 -2
- data/app/search_builders/bulkrax/export_search_builder.rb +9 -0
- data/app/services/bulkrax/csv_template/column_builder.rb +2 -2
- data/app/services/bulkrax/csv_template/mapping_manager.rb +9 -7
- data/app/services/bulkrax/csv_template/schema_analyzer.rb +1 -5
- data/lib/bulkrax/engine.rb +1 -1
- data/lib/bulkrax/version.rb +1 -1
- data/lib/bulkrax.rb +76 -10
- data/lib/generators/bulkrax/install_generator.rb +22 -10
- data/lib/generators/bulkrax/templates/config/initializers/bulkrax.rb +1 -1
- metadata +33 -14
- data/app/factories/bulkrax/valkyrize-hyku.code-workspace +0 -19
- data/lib/wings/custom_queries/find_by_source_identifier.rb +0 -41
|
@@ -280,11 +280,14 @@ module Bulkrax
|
|
|
280
280
|
# @param admin_set_id [String, nil] admin set used to resolve contexts
|
|
281
281
|
# @return [Array<String>]
|
|
282
282
|
def self.schema_properties(klass:, admin_set_id: nil)
|
|
283
|
-
|
|
283
|
+
entry = schema_cache_entry(klass: klass, admin_set_id: admin_set_id)
|
|
284
|
+
entry[:properties] ||= entry[:schema].map { |k| k.name.to_s }.freeze
|
|
284
285
|
end
|
|
285
286
|
|
|
286
287
|
##
|
|
287
|
-
# Returns the schema for a model, memoized per (klass, admin_set_id)
|
|
288
|
+
# Returns the schema for a model, memoized per (tenant, klass, admin_set_id)
|
|
289
|
+
# for the current request or job only, so a new metadata profile or a
|
|
290
|
+
# change to an admin set's contexts is picked up without a restart.
|
|
288
291
|
# Delegates to +Hyrax.schema_for+ when available so that context-gated
|
|
289
292
|
# properties are included without Bulkrax knowing about flexibility internals.
|
|
290
293
|
#
|
|
@@ -292,16 +295,25 @@ module Bulkrax
|
|
|
292
295
|
# @param admin_set_id [String, nil]
|
|
293
296
|
# @return [Dry::Types::Hash]
|
|
294
297
|
def self.cached_schema_for(klass:, admin_set_id: nil)
|
|
295
|
-
|
|
296
|
-
key = [klass.name, admin_set_id].compact.join('|')
|
|
297
|
-
@cached_schema_map[key] ||=
|
|
298
|
-
if admin_set_id.present? && defined?(Hyrax) && Hyrax.respond_to?(:schema_for)
|
|
299
|
-
Hyrax.schema_for(klass: klass, admin_set_id: admin_set_id)
|
|
300
|
-
else
|
|
301
|
-
klass.new.singleton_class.schema || klass.schema
|
|
302
|
-
end
|
|
298
|
+
schema_cache_entry(klass: klass, admin_set_id: admin_set_id)[:schema]
|
|
303
299
|
end
|
|
304
300
|
|
|
301
|
+
def self.schema_cache_entry(klass:, admin_set_id:)
|
|
302
|
+
tenant = defined?(Apartment::Tenant) ? Apartment::Tenant.current : 'single'
|
|
303
|
+
Bulkrax::Current.schema_cache ||= {}
|
|
304
|
+
Bulkrax::Current.schema_cache[[tenant, klass, admin_set_id]] ||= { schema: resolve_schema(klass: klass, admin_set_id: admin_set_id) }
|
|
305
|
+
end
|
|
306
|
+
private_class_method :schema_cache_entry
|
|
307
|
+
|
|
308
|
+
def self.resolve_schema(klass:, admin_set_id:)
|
|
309
|
+
if admin_set_id.present? && defined?(Hyrax) && Hyrax.respond_to?(:schema_for)
|
|
310
|
+
Hyrax.schema_for(klass: klass, admin_set_id: admin_set_id)
|
|
311
|
+
else
|
|
312
|
+
klass.new.singleton_class.schema || klass.schema
|
|
313
|
+
end
|
|
314
|
+
end
|
|
315
|
+
private_class_method :resolve_schema
|
|
316
|
+
|
|
305
317
|
def self.ordered_file_sets_for(object)
|
|
306
318
|
return [] if object.blank?
|
|
307
319
|
|
|
@@ -434,7 +446,7 @@ module Bulkrax
|
|
|
434
446
|
|
|
435
447
|
file_attrs = []
|
|
436
448
|
uploads.each_with_index do |f, index|
|
|
437
|
-
file_attrs <<
|
|
449
|
+
file_attrs << { uploaded_file_id: f["id"].to_s, filename: files[index]["file_name"] }.merge(additional_attributes[index])
|
|
438
450
|
end
|
|
439
451
|
file_attrs.compact.uniq
|
|
440
452
|
end
|
|
@@ -501,7 +513,7 @@ module Bulkrax
|
|
|
501
513
|
@permitted_attributes ||= begin
|
|
502
514
|
bare = base_permitted_attributes + if klass.respond_to?(:schema)
|
|
503
515
|
admin_set_id = attributes[:admin_set_id] || attributes['admin_set_id']
|
|
504
|
-
Bulkrax::ValkyrieObjectFactory.schema_properties(klass: klass, admin_set_id: admin_set_id)
|
|
516
|
+
Bulkrax::ValkyrieObjectFactory.schema_properties(klass: klass, admin_set_id: admin_set_id).map(&:to_sym)
|
|
505
517
|
else
|
|
506
518
|
klass.properties.keys.map(&:to_sym)
|
|
507
519
|
end
|
|
@@ -590,18 +602,18 @@ module Bulkrax
|
|
|
590
602
|
|
|
591
603
|
# @Override Destroy existing files with Hyrax::Transactions
|
|
592
604
|
def destroy_existing_files(object: @object)
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
existing_files.each do |fs|
|
|
605
|
+
destroyed_ids = []
|
|
606
|
+
Hyrax.custom_queries.find_child_file_sets(resource: object).each do |fs|
|
|
597
607
|
transactions["file_set.destroy"]
|
|
598
608
|
.with_step_args("file_set.remove_from_work" => { user: @user },
|
|
599
609
|
"file_set.delete" => { user: @user })
|
|
600
610
|
.call(fs)
|
|
601
611
|
.value!
|
|
612
|
+
destroyed_ids << fs.id
|
|
602
613
|
end
|
|
614
|
+
return if destroyed_ids.empty?
|
|
603
615
|
|
|
604
|
-
object.member_ids
|
|
616
|
+
object.member_ids -= destroyed_ids
|
|
605
617
|
object.rendering_ids = []
|
|
606
618
|
object.representative_id = nil
|
|
607
619
|
object.thumbnail_id = nil
|
|
@@ -114,7 +114,7 @@ module Bulkrax
|
|
|
114
114
|
# We can use Hyrax's lock manager when we have one available.
|
|
115
115
|
# However it's not certain that this is actually working, so to be
|
|
116
116
|
# as safe as possible, we will reload resources before we update.
|
|
117
|
-
if
|
|
117
|
+
if Bulkrax.hyrax_loaded?
|
|
118
118
|
include Hyrax::Lockable
|
|
119
119
|
|
|
120
120
|
def conditionally_acquire_lock_for(*args, &block)
|
|
@@ -10,8 +10,15 @@ module Bulkrax
|
|
|
10
10
|
begin
|
|
11
11
|
entry.build
|
|
12
12
|
entry.save!
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
# An entry that fails inside #build records the error on itself rather
|
|
14
|
+
# than raising, so its status is what says whether this run succeeded.
|
|
15
|
+
if entry.succeeded?
|
|
16
|
+
ImporterRun.increment_counter(:processed_records, args[1])
|
|
17
|
+
ImporterRun.increment_counter(:processed_collections, args[1])
|
|
18
|
+
else
|
|
19
|
+
ImporterRun.increment_counter(:failed_records, args[1])
|
|
20
|
+
ImporterRun.increment_counter(:failed_collections, args[1])
|
|
21
|
+
end
|
|
15
22
|
ImporterRun.decrement_counter(:enqueued_records, args[1]) unless ImporterRun.find(args[1]).enqueued_records <= 0 # rubocop:disable Style/IdenticalConditionalBranches
|
|
16
23
|
rescue => e
|
|
17
24
|
ImporterRun.increment_counter(:failed_records, args[1])
|
|
@@ -67,10 +67,13 @@ module Bulkrax
|
|
|
67
67
|
end
|
|
68
68
|
|
|
69
69
|
def parse_subject(src)
|
|
70
|
-
string = src.strip
|
|
70
|
+
string = src.strip
|
|
71
71
|
return if string.blank?
|
|
72
72
|
|
|
73
|
-
string.
|
|
73
|
+
return string.sub(%r{\Ahttps?}i, &:downcase) if string.match?(%r{\Ahttps?://}i)
|
|
74
|
+
|
|
75
|
+
downcased = string.downcase
|
|
76
|
+
downcased.slice(0, 1).capitalize + downcased.slice(1..-1)
|
|
74
77
|
end
|
|
75
78
|
|
|
76
79
|
def parse_types(src)
|
|
@@ -141,7 +141,7 @@ module Bulkrax
|
|
|
141
141
|
|
|
142
142
|
def add_metadata_for_model
|
|
143
143
|
if factory_class.present? && factory_class == Bulkrax.collection_model_class
|
|
144
|
-
add_collection_type_gid if
|
|
144
|
+
add_collection_type_gid if Bulkrax.hyrax_loaded?
|
|
145
145
|
# add any additional collection metadata methods here
|
|
146
146
|
elsif factory_class == Bulkrax.file_model_class
|
|
147
147
|
validate_presence_of_filename!
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Bulkrax
|
|
4
|
+
# A minimal scope adapter that satisfies the Blacklight::SearchBuilder and
|
|
5
|
+
# Hydra::AccessControlsEnforcement interface for export permission filtering.
|
|
6
|
+
#
|
|
7
|
+
# Blacklight::SearchBuilder expects the scope to respond to +blacklight_config+
|
|
8
|
+
# and +current_ability+. In the Hyrax catalog this is the controller; for
|
|
9
|
+
# Bulkrax exports a lightweight PORO is sufficient.
|
|
10
|
+
class ExportScope
|
|
11
|
+
attr_reader :current_ability
|
|
12
|
+
|
|
13
|
+
def initialize(ability)
|
|
14
|
+
@current_ability = ability
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def blacklight_config
|
|
18
|
+
@blacklight_config ||= Blacklight::Configuration.new
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -69,7 +69,7 @@ module Bulkrax
|
|
|
69
69
|
end
|
|
70
70
|
|
|
71
71
|
def workflow_status_list
|
|
72
|
-
Sipity::WorkflowState.all.map { |s| [s.name&.titleize, s.name] }.uniq if
|
|
72
|
+
Sipity::WorkflowState.all.map { |s| [s.name&.titleize, s.name] }.uniq if Bulkrax.hyrax_loaded?
|
|
73
73
|
end
|
|
74
74
|
|
|
75
75
|
# If field_mapping is empty, setup a default based on the export_properties
|
|
@@ -84,7 +84,7 @@ module Bulkrax
|
|
|
84
84
|
end
|
|
85
85
|
|
|
86
86
|
def export_from_list
|
|
87
|
-
if
|
|
87
|
+
if Bulkrax.hyrax_loaded?
|
|
88
88
|
[
|
|
89
89
|
[I18n.t('bulkrax.exporter.labels.importer'), 'importer'],
|
|
90
90
|
[I18n.t('bulkrax.exporter.labels.collection'), 'collection'],
|
|
@@ -18,7 +18,7 @@ module Bulkrax
|
|
|
18
18
|
has_many :entries, as: :importerexporter, dependent: :destroy
|
|
19
19
|
|
|
20
20
|
validates :name, presence: true
|
|
21
|
-
validates :admin_set_id, presence: true if
|
|
21
|
+
validates :admin_set_id, presence: true if Bulkrax.hyrax_loaded?
|
|
22
22
|
validates :parser_klass, presence: true
|
|
23
23
|
|
|
24
24
|
delegate :create_parent_child_relationships, :valid_import?, :write_errored_entries_file, :visibility, to: :parser
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Bulkrax
|
|
4
|
+
# Concern providing Bulkrax-specific ability methods.
|
|
5
|
+
#
|
|
6
|
+
# Include this in your application's Ability class to gain default
|
|
7
|
+
# implementations of all Bulkrax authorization methods. Override any
|
|
8
|
+
# method to customize authorization behavior for your application.
|
|
9
|
+
#
|
|
10
|
+
# ## Wiring CanCan rules
|
|
11
|
+
#
|
|
12
|
+
# Call +bulkrax_default_abilities+ from your Ability's +initialize+ (or add
|
|
13
|
+
# it to +self.ability_logic+) to register CanCan +can+ rules for all
|
|
14
|
+
# Bulkrax resources:
|
|
15
|
+
#
|
|
16
|
+
# class Ability
|
|
17
|
+
# include Hydra::Ability
|
|
18
|
+
# include Bulkrax::AbilityBehavior
|
|
19
|
+
# self.ability_logic += [:bulkrax_default_abilities]
|
|
20
|
+
# end
|
|
21
|
+
#
|
|
22
|
+
# The method uses the four predicate hooks below as guards, so you can
|
|
23
|
+
# control who gets which rules simply by overriding those predicates.
|
|
24
|
+
#
|
|
25
|
+
# Example:
|
|
26
|
+
#
|
|
27
|
+
# class Ability
|
|
28
|
+
# include Hydra::Ability
|
|
29
|
+
# include Bulkrax::AbilityBehavior
|
|
30
|
+
# self.ability_logic += [:bulkrax_default_abilities]
|
|
31
|
+
#
|
|
32
|
+
# # Grant import/export access to users who can create works
|
|
33
|
+
# def can_import_works?
|
|
34
|
+
# can_create_any_work?
|
|
35
|
+
# end
|
|
36
|
+
#
|
|
37
|
+
# def can_export_works?
|
|
38
|
+
# can_create_any_work?
|
|
39
|
+
# end
|
|
40
|
+
#
|
|
41
|
+
# # Grant admin-level importer access to site admins
|
|
42
|
+
# def can_admin_importers?
|
|
43
|
+
# current_user.admin?
|
|
44
|
+
# end
|
|
45
|
+
# end
|
|
46
|
+
module AbilityBehavior
|
|
47
|
+
extend ActiveSupport::Concern
|
|
48
|
+
|
|
49
|
+
# Returns true if the current user may use importer functionality at all.
|
|
50
|
+
# Override in your Ability class to customize.
|
|
51
|
+
def can_import_works?
|
|
52
|
+
false
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Returns true if the current user may use exporter functionality at all.
|
|
56
|
+
# Override in your Ability class to customize.
|
|
57
|
+
def can_export_works?
|
|
58
|
+
false
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Returns true if the current user may administer ALL importers —
|
|
62
|
+
# i.e., view, edit, update, or destroy any importer regardless of ownership.
|
|
63
|
+
# Defaults to false; override to grant admin access.
|
|
64
|
+
def can_admin_importers?
|
|
65
|
+
false
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Returns true if the current user may administer ALL exporters —
|
|
69
|
+
# i.e., view, edit, update, or destroy any exporter regardless of ownership.
|
|
70
|
+
# Defaults to false; override to grant admin access.
|
|
71
|
+
def can_admin_exporters?
|
|
72
|
+
false
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Registers CanCan +can+ rules for all Bulkrax resources.
|
|
76
|
+
#
|
|
77
|
+
# Call this from your Ability's +initialize+ or add it to
|
|
78
|
+
# +self.ability_logic+. The predicate hooks above control which rules are
|
|
79
|
+
# granted; override them to tailor access.
|
|
80
|
+
#
|
|
81
|
+
# Rules for Importer and Exporter use hash-form conditions (SQL-generatable)
|
|
82
|
+
# so they work with +Model.accessible_by(current_ability)+. Rules for Entry
|
|
83
|
+
# use block-form conditions (checked in Ruby) because authorization must
|
|
84
|
+
# traverse the parent association — do not call +Entry.accessible_by()+;
|
|
85
|
+
# scope entries through the parent instead (e.g. +@importer.entries+).
|
|
86
|
+
def bulkrax_default_abilities
|
|
87
|
+
# Skip all rules when there is no authenticated user; avoids nil-id bugs
|
|
88
|
+
# and prevents block rules from matching NULL user_id records.
|
|
89
|
+
return unless current_user&.id
|
|
90
|
+
|
|
91
|
+
# Map Bulkrax-specific non-CRUD actions to their closest CanCan equivalents
|
|
92
|
+
# so that load_and_authorize_resource can authorize them without requiring
|
|
93
|
+
# a dedicated `can` declaration for each custom action name.
|
|
94
|
+
alias_action :entry_table, :importer_table, :exporter_table,
|
|
95
|
+
:original_file, :export_errors, :upload_corrected_entries, to: :read
|
|
96
|
+
alias_action :continue, :upload_corrected_entries_file, to: :update
|
|
97
|
+
|
|
98
|
+
grant_importer_abilities
|
|
99
|
+
grant_exporter_abilities
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def grant_importer_abilities
|
|
103
|
+
if can_admin_importers?
|
|
104
|
+
# Prefer a single unconditional rule for admins. Older CanCanCan/
|
|
105
|
+
# ActiveRecord combinations used by the Hyrax 4 appraisal can fail when
|
|
106
|
+
# accessible_by has to merge unconditional and hash-conditioned rules.
|
|
107
|
+
can :manage, Bulkrax::Importer
|
|
108
|
+
can [:read, :update, :destroy], Bulkrax::Entry do |entry|
|
|
109
|
+
entry.importerexporter.is_a?(Bulkrax::Importer)
|
|
110
|
+
end
|
|
111
|
+
elsif can_import_works?
|
|
112
|
+
can :create, Bulkrax::Importer
|
|
113
|
+
can [:read, :update, :destroy], Bulkrax::Importer, user_id: current_user.id
|
|
114
|
+
can [:read, :update, :destroy], Bulkrax::Entry do |entry|
|
|
115
|
+
entry.importerexporter.is_a?(Bulkrax::Importer) &&
|
|
116
|
+
entry.importerexporter.user_id == current_user.id
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def grant_exporter_abilities
|
|
122
|
+
if can_admin_exporters?
|
|
123
|
+
# Prefer a single unconditional rule for admins. See importer rule
|
|
124
|
+
# above for the Hyrax 4 accessible_by compatibility note.
|
|
125
|
+
can :manage, Bulkrax::Exporter
|
|
126
|
+
can [:read, :update, :destroy], Bulkrax::Entry do |entry|
|
|
127
|
+
entry.importerexporter.is_a?(Bulkrax::Exporter)
|
|
128
|
+
end
|
|
129
|
+
elsif can_export_works?
|
|
130
|
+
can :create, Bulkrax::Exporter
|
|
131
|
+
can [:read, :update, :destroy], Bulkrax::Exporter, user_id: current_user.id
|
|
132
|
+
can [:read, :update, :destroy], Bulkrax::Entry do |entry|
|
|
133
|
+
entry.importerexporter.is_a?(Bulkrax::Exporter) &&
|
|
134
|
+
entry.importerexporter.user_id == current_user.id
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
@@ -230,7 +230,7 @@ module Bulkrax
|
|
|
230
230
|
# @return [Array] hyrax fields
|
|
231
231
|
def field_to(field)
|
|
232
232
|
fields = mapping&.map do |key, value|
|
|
233
|
-
|
|
233
|
+
next unless value
|
|
234
234
|
|
|
235
235
|
if value['from'].instance_of?(Array)
|
|
236
236
|
key if value['from'].include?(field) || key == field
|
|
@@ -34,7 +34,7 @@ module Bulkrax
|
|
|
34
34
|
# TODO: This guard clause is not necessary as we can handle it in the
|
|
35
35
|
# underlying factory. However, to do that requires adjusting about 7
|
|
36
36
|
# failing specs. So for now this refactor appears acceptable
|
|
37
|
-
return unless
|
|
37
|
+
return unless Bulkrax.hyrax_loaded?
|
|
38
38
|
return unless self.class.to_s.include?("Collection")
|
|
39
39
|
factory.add_user_to_collection_permissions(collection: @item, user: user)
|
|
40
40
|
end
|
|
@@ -87,7 +87,7 @@ module Bulkrax
|
|
|
87
87
|
end
|
|
88
88
|
|
|
89
89
|
def add_admin_set_id
|
|
90
|
-
return unless
|
|
90
|
+
return unless Bulkrax.hyrax_loaded?
|
|
91
91
|
|
|
92
92
|
self.parsed_metadata['admin_set_id'] = importerexporter.admin_set_id if self.parsed_metadata['admin_set_id'].blank?
|
|
93
93
|
end
|
|
@@ -161,7 +161,7 @@ module Bulkrax
|
|
|
161
161
|
# @param field [String] name of the controlled property
|
|
162
162
|
# @return [Boolean] provided value is a present, active authority ID for the provided field
|
|
163
163
|
def active_id_for_authority?(value, field)
|
|
164
|
-
return false unless
|
|
164
|
+
return false unless Bulkrax.hyrax_loaded?
|
|
165
165
|
field_service = ('Hyrax::' + "#{field}_service".camelcase).constantize
|
|
166
166
|
active_authority_ids = field_service.new.active_elements.map { |ae| ae['id'] }
|
|
167
167
|
|
|
@@ -37,7 +37,6 @@ module Bulkrax
|
|
|
37
37
|
@records
|
|
38
38
|
end
|
|
39
39
|
|
|
40
|
-
# rubocop:disable Metrics/AbcSize
|
|
41
40
|
def build_records
|
|
42
41
|
@collections = []
|
|
43
42
|
@works = []
|
|
@@ -49,11 +48,9 @@ module Bulkrax
|
|
|
49
48
|
next unless r.key?(model_mapping)
|
|
50
49
|
|
|
51
50
|
model = r[model_mapping].nil? ? "" : r[model_mapping].strip
|
|
52
|
-
|
|
53
|
-
# We aren't right now because so many Bulkrax users are in between Fedora and Valkyrie
|
|
54
|
-
if model.casecmp('collection').zero? || model.casecmp('collectionresource').zero?
|
|
51
|
+
if Bulkrax.collection_model_name?(model)
|
|
55
52
|
@collections << r
|
|
56
|
-
elsif
|
|
53
|
+
elsif Bulkrax.file_model_name?(model)
|
|
57
54
|
@file_sets << r
|
|
58
55
|
else
|
|
59
56
|
@works << r
|
|
@@ -69,7 +66,6 @@ module Bulkrax
|
|
|
69
66
|
|
|
70
67
|
true
|
|
71
68
|
end
|
|
72
|
-
# rubocop:enabled Metrics/AbcSize
|
|
73
69
|
|
|
74
70
|
def collections
|
|
75
71
|
build_records if @collections.nil?
|
|
@@ -372,12 +368,17 @@ module Bulkrax
|
|
|
372
368
|
raise StandardError, "Record references local files but no files directory could be resolved from the import path" if files_dir.nil?
|
|
373
369
|
|
|
374
370
|
r[file_mapping].split(split_pattern).map do |f|
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
371
|
+
reference = f.strip
|
|
372
|
+
# Try the reference as written first, then the space->underscore
|
|
373
|
+
# variant produced by #remove_spaces_from_filenames. The rename only
|
|
374
|
+
# rewrites the top level of files/ (see unzip_spec), so nested
|
|
375
|
+
# attachments keep their spaces on disk and only match as written.
|
|
376
|
+
file = [reference, reference.tr(' ', '_')].uniq
|
|
377
|
+
.map { |candidate| File.join(files_dir, candidate) }
|
|
378
|
+
.find { |candidate| File.exist?(candidate) }
|
|
379
|
+
raise "File #{File.join(files_dir, reference)} does not exist" if file.nil?
|
|
380
|
+
|
|
381
|
+
file
|
|
381
382
|
end
|
|
382
383
|
end.flatten.compact.uniq
|
|
383
384
|
end
|
|
@@ -71,7 +71,7 @@ module Bulkrax
|
|
|
71
71
|
metadata = {
|
|
72
72
|
visibility: 'open'
|
|
73
73
|
}
|
|
74
|
-
metadata[:collection_type_gid] = Hyrax::CollectionType.find_or_create_default_collection_type.to_global_id.to_s if
|
|
74
|
+
metadata[:collection_type_gid] = Hyrax::CollectionType.find_or_create_default_collection_type.to_global_id.to_s if Bulkrax.hyrax_loaded?
|
|
75
75
|
|
|
76
76
|
collections.each_with_index do |set, index|
|
|
77
77
|
next unless collection_name == 'all' || collection_name == set.spec
|
|
@@ -120,7 +120,10 @@ module Bulkrax
|
|
|
120
120
|
#
|
|
121
121
|
# No sense attempting to query for more than the limit.
|
|
122
122
|
def query_kwargs
|
|
123
|
-
{ fl: "id,#{Bulkrax.solr_key_for_member_file_ids}", method: :post, rows: row_limit }
|
|
123
|
+
base = { fl: "id,#{Bulkrax.solr_key_for_member_file_ids}", method: :post, rows: row_limit }
|
|
124
|
+
filters = permission_filters
|
|
125
|
+
return base if filters.empty?
|
|
126
|
+
base.merge(fq: filters)
|
|
124
127
|
end
|
|
125
128
|
|
|
126
129
|
# If we have a limit, we need not query beyond that limit
|
|
@@ -175,10 +178,25 @@ module Bulkrax
|
|
|
175
178
|
@file_sets ||= ParserExportRecordSet.in_batches(candidate_file_set_ids) do |batch_of_ids|
|
|
176
179
|
fsq = "has_model_ssim:\"#{Bulkrax.file_model_internal_resource.demodulize}\" AND id:(\"" + batch_of_ids.join('" OR "') + "\")"
|
|
177
180
|
fsq += extra_filters if extra_filters.present?
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
)
|
|
181
|
+
fq = permission_filters
|
|
182
|
+
kwargs = { fl: "id", method: :post, rows: batch_of_ids.size }
|
|
183
|
+
kwargs[:fq] = fq unless fq.empty?
|
|
184
|
+
Bulkrax.object_factory.query(fsq, **kwargs)
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def exporting_user
|
|
189
|
+
importerexporter.user || Bulkrax.fallback_user_for_importer_exporter_processing
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
def permission_filters
|
|
193
|
+
@permission_filters ||= begin
|
|
194
|
+
ability = Ability.new(exporting_user)
|
|
195
|
+
return [] if ability.can_admin_exporters?
|
|
196
|
+
|
|
197
|
+
scope = Bulkrax::ExportScope.new(ability)
|
|
198
|
+
builder = Bulkrax::ExportSearchBuilder.new(scope)
|
|
199
|
+
builder.to_h.fetch(:fq, [])
|
|
182
200
|
end
|
|
183
201
|
end
|
|
184
202
|
|
|
@@ -246,7 +264,7 @@ module Bulkrax
|
|
|
246
264
|
Bulkrax.object_factory.query(
|
|
247
265
|
extra_filters.to_s,
|
|
248
266
|
**query_kwargs.merge(
|
|
249
|
-
fq: [
|
|
267
|
+
fq: (query_kwargs[:fq] || []) + [
|
|
250
268
|
%(#{solr_name(work_identifier)}:("#{ids.join('" OR "')}")),
|
|
251
269
|
"has_model_ssim:(#{Bulkrax.curation_concern_internal_resources.join(' OR ')})"
|
|
252
270
|
],
|
|
@@ -261,7 +279,7 @@ module Bulkrax
|
|
|
261
279
|
Bulkrax.object_factory.query(
|
|
262
280
|
"has_model_ssim:#{Bulkrax.collection_model_internal_resource} #{extra_filters}",
|
|
263
281
|
**query_kwargs.merge(
|
|
264
|
-
fq: [
|
|
282
|
+
fq: (query_kwargs[:fq] || []) + [
|
|
265
283
|
%(#{solr_name(work_identifier)}:("#{ids.join('" OR "')}")),
|
|
266
284
|
"has_model_ssim:#{Bulkrax.collection_model_internal_resource}"
|
|
267
285
|
],
|
|
@@ -280,7 +298,7 @@ module Bulkrax
|
|
|
280
298
|
Bulkrax.object_factory.query(
|
|
281
299
|
extra_filters,
|
|
282
300
|
**query_kwargs.merge(
|
|
283
|
-
fq: [
|
|
301
|
+
fq: (query_kwargs[:fq] || []) + [
|
|
284
302
|
%(#{solr_name(work_identifier)}:("#{ids.join('" OR "')}")),
|
|
285
303
|
"has_model_ssim:#{Bulkrax.file_model_internal_resource}"
|
|
286
304
|
],
|
|
@@ -14,7 +14,7 @@ module Bulkrax
|
|
|
14
14
|
# @param args [Hash] Additional arguments passed to output method (e.g., file_path)
|
|
15
15
|
# @return [String] File path (for 'file' output) or CSV string (for 'csv_string' output)
|
|
16
16
|
def generate_template(models: [], output: 'file', admin_set_id: nil, **args)
|
|
17
|
-
raise NameError, "Hyrax is not defined" unless
|
|
17
|
+
raise NameError, "Hyrax is not defined" unless Bulkrax.hyrax_loaded?
|
|
18
18
|
TemplateContext.new(models: models, admin_set_id: admin_set_id).send("to_#{output}", **args)
|
|
19
19
|
end
|
|
20
20
|
end
|
|
@@ -71,8 +71,7 @@ module Bulkrax
|
|
|
71
71
|
file_key = resolve_validation_key(mapping_manager, key: 'file', default: :file)
|
|
72
72
|
|
|
73
73
|
csv_data = parse_validation_rows(raw_csv, source_id_key, parent_key, children_key, file_key)
|
|
74
|
-
all_models = csv_data
|
|
75
|
-
all_models |= [Bulkrax.default_work_type] if Bulkrax.default_work_type.present?
|
|
74
|
+
all_models = models_to_validate(csv_data, headers)
|
|
76
75
|
field_analyzer = CsvTemplate::FieldAnalyzer.new(mappings, admin_set_id)
|
|
77
76
|
field_metadata = build_validation_field_metadata(all_models, field_analyzer)
|
|
78
77
|
|
|
@@ -64,7 +64,7 @@ module Bulkrax
|
|
|
64
64
|
Rails.logger.error("CsvParser.validate_csv: error building valid headers – #{e.message}")
|
|
65
65
|
standard = %w[model source_identifier parents children file]
|
|
66
66
|
model_fields = field_metadata.values.flat_map { |m| m[:properties] }
|
|
67
|
-
|
|
67
|
+
model_fields = model_fields.map { |prop| mapping_manager.key_to_mapped_column(prop) }
|
|
68
68
|
(standard + model_fields).uniq
|
|
69
69
|
end
|
|
70
70
|
|
|
@@ -129,6 +129,23 @@ module Bulkrax
|
|
|
129
129
|
all_models.each { |model| missing_required << { model: model, field: source_id_key.to_s } }
|
|
130
130
|
end
|
|
131
131
|
|
|
132
|
+
# Adding the default work type unconditionally makes every sheet answer
|
|
133
|
+
# for that model's required fields, so a collection-only sheet gets asked
|
|
134
|
+
# for properties belonging to a work type it never mentions.
|
|
135
|
+
def models_to_validate(csv_data, headers)
|
|
136
|
+
row_models = csv_data.map { |r| r[:model].to_s }.reject(&:blank?).uniq
|
|
137
|
+
return row_models if Bulkrax.default_work_type.blank?
|
|
138
|
+
return row_models unless default_work_type_needed?(csv_data, headers)
|
|
139
|
+
|
|
140
|
+
row_models | [Bulkrax.default_work_type]
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def default_work_type_needed?(csv_data, headers)
|
|
144
|
+
return true unless headers.map(&:to_s).include?('model')
|
|
145
|
+
|
|
146
|
+
csv_data.empty? || csv_data.any? { |r| r[:model].blank? }
|
|
147
|
+
end
|
|
148
|
+
|
|
132
149
|
# Adds a file-level notice when the model column is absent or every row has a blank
|
|
133
150
|
# model value, indicating that the default work type will be used for all rows.
|
|
134
151
|
# When this notice is present the per-row default_work_type_used warnings are
|
|
@@ -36,9 +36,9 @@ module Bulkrax
|
|
|
36
36
|
model_str = item[:model].to_s
|
|
37
37
|
|
|
38
38
|
opts = { type: nil, find_record: find_record, parent: parent_split_pattern, child: child_split_pattern }
|
|
39
|
-
if
|
|
39
|
+
if Bulkrax.collection_model_name?(model_str)
|
|
40
40
|
collections << build_item_hash(item, child_to_parents, all_ids, opts.merge(type: 'collection'))
|
|
41
|
-
elsif
|
|
41
|
+
elsif Bulkrax.file_model_name?(model_str)
|
|
42
42
|
file_sets << { id: item_id, title: item[:raw_row]['title'] || item_id, type: 'file_set' }
|
|
43
43
|
else
|
|
44
44
|
works << build_item_hash(item, child_to_parents, all_ids, opts.merge(type: 'work'))
|
|
@@ -43,8 +43,8 @@ module Bulkrax
|
|
|
43
43
|
|
|
44
44
|
# When a property is the target of one or more `object:` field mappings,
|
|
45
45
|
# emit each of those mappings' `from:` columns (e.g. redirect_path,
|
|
46
|
-
#
|
|
47
|
-
#
|
|
46
|
+
# redirect_display_url) rather than the bare property name (redirects).
|
|
47
|
+
# Otherwise fall back to the standard 1:1 mapping.
|
|
48
48
|
def columns_for_property(property)
|
|
49
49
|
nested = @service.mapping_manager.object_columns_for(property)
|
|
50
50
|
return nested if nested.any?
|
|
@@ -35,16 +35,18 @@ module Bulkrax
|
|
|
35
35
|
|
|
36
36
|
# Returns the column names that target a given object name via the
|
|
37
37
|
# `object:` field-mapping pattern. The template generator uses this to
|
|
38
|
-
# emit the per-child columns (e.g. redirect_path,
|
|
39
|
-
#
|
|
40
|
-
#
|
|
41
|
-
#
|
|
42
|
-
#
|
|
38
|
+
# emit the per-child columns (e.g. redirect_path, redirect_display_url)
|
|
39
|
+
# instead of the bare property name (redirects). Numbering is
|
|
40
|
+
# intentionally omitted — the template shows the column shape once;
|
|
41
|
+
# CSV rows can repeat the column with numeric suffixes (e.g.
|
|
42
|
+
# redirect_path_1, redirect_path_2) at import time.
|
|
43
|
+
# An import accepts every `from:` alias, but offering them all here would
|
|
44
|
+
# put two columns for the same field on the template.
|
|
43
45
|
def object_columns_for(object_name)
|
|
44
46
|
@mappings
|
|
45
47
|
.select { |_k, v| v.is_a?(Hash) && v["object"] == object_name }
|
|
46
|
-
.
|
|
47
|
-
.
|
|
48
|
+
.keys
|
|
49
|
+
.map { |key| key_to_mapped_column(key) }
|
|
48
50
|
.uniq
|
|
49
51
|
end
|
|
50
52
|
|