bulkrax 9.3.5 → 9.4.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 +11 -1
- data/app/assets/javascripts/bulkrax/application.js +2 -1
- data/app/assets/javascripts/bulkrax/bulkrax.js +13 -4
- data/app/assets/javascripts/bulkrax/bulkrax_utils.js +96 -0
- data/app/assets/javascripts/bulkrax/datatables.js +1 -0
- data/app/assets/javascripts/bulkrax/entries.js +17 -10
- data/app/assets/javascripts/bulkrax/importers.js.erb +9 -2
- data/app/assets/javascripts/bulkrax/importers_stepper.js +2420 -0
- data/app/assets/stylesheets/bulkrax/application.css +1 -1
- data/app/assets/stylesheets/bulkrax/stepper/_header.scss +83 -0
- data/app/assets/stylesheets/bulkrax/stepper/_mixins.scss +26 -0
- data/app/assets/stylesheets/bulkrax/stepper/_navigation.scss +103 -0
- data/app/assets/stylesheets/bulkrax/stepper/_responsive.scss +46 -0
- data/app/assets/stylesheets/bulkrax/stepper/_review.scss +92 -0
- data/app/assets/stylesheets/bulkrax/stepper/_settings.scss +106 -0
- data/app/assets/stylesheets/bulkrax/stepper/_success.scss +26 -0
- data/app/assets/stylesheets/bulkrax/stepper/_summary.scss +171 -0
- data/app/assets/stylesheets/bulkrax/stepper/_upload.scss +339 -0
- data/app/assets/stylesheets/bulkrax/stepper/_validation.scss +237 -0
- data/app/assets/stylesheets/bulkrax/stepper/_variables.scss +46 -0
- data/app/assets/stylesheets/bulkrax/stepper.scss +32 -0
- data/app/controllers/bulkrax/guided_imports_controller.rb +175 -0
- data/app/controllers/bulkrax/importers_controller.rb +28 -31
- data/app/controllers/concerns/bulkrax/guided_import_demo_scenarios.rb +201 -0
- data/app/controllers/concerns/bulkrax/importer_file_handler.rb +217 -0
- data/app/factories/bulkrax/object_factory.rb +3 -2
- data/app/factories/bulkrax/valkyrie_object_factory.rb +61 -17
- data/app/jobs/bulkrax/importer_job.rb +11 -4
- data/app/models/bulkrax/csv_entry.rb +27 -7
- data/app/models/bulkrax/entry.rb +4 -0
- data/app/models/bulkrax/importer.rb +31 -1
- data/app/models/concerns/bulkrax/has_matchers.rb +2 -2
- data/app/models/concerns/bulkrax/importer_exporter_behavior.rb +6 -5
- data/app/parsers/bulkrax/application_parser.rb +31 -5
- data/app/parsers/bulkrax/csv_parser.rb +42 -10
- data/app/parsers/concerns/bulkrax/csv_parser/csv_template_generation.rb +73 -0
- data/app/parsers/concerns/bulkrax/csv_parser/csv_validation.rb +133 -0
- data/app/parsers/concerns/bulkrax/csv_parser/csv_validation_helpers.rb +282 -0
- data/app/parsers/concerns/bulkrax/csv_parser/csv_validation_hierarchy.rb +96 -0
- data/app/services/bulkrax/csv_template/column_builder.rb +60 -0
- data/app/services/bulkrax/csv_template/column_descriptor.rb +58 -0
- data/app/services/bulkrax/csv_template/csv_builder.rb +83 -0
- data/app/services/bulkrax/csv_template/explanation_builder.rb +57 -0
- data/app/services/bulkrax/csv_template/field_analyzer.rb +56 -0
- data/app/services/bulkrax/csv_template/file_path_generator.rb +47 -0
- data/app/services/bulkrax/csv_template/file_validator.rb +68 -0
- data/app/services/bulkrax/csv_template/mapping_manager.rb +55 -0
- data/app/services/bulkrax/csv_template/model_loader.rb +50 -0
- data/app/services/bulkrax/csv_template/row_builder.rb +35 -0
- data/app/services/bulkrax/csv_template/schema_analyzer.rb +70 -0
- data/app/services/bulkrax/csv_template/split_formatter.rb +44 -0
- data/app/services/bulkrax/csv_template/value_determiner.rb +68 -0
- data/app/services/bulkrax/stepper_response_formatter.rb +347 -0
- data/app/services/bulkrax/validation_error_csv_builder.rb +99 -0
- data/app/validators/bulkrax/csv_row/child_reference.rb +56 -0
- data/app/validators/bulkrax/csv_row/circular_reference.rb +71 -0
- data/app/validators/bulkrax/csv_row/controlled_vocabulary.rb +74 -0
- data/app/validators/bulkrax/csv_row/duplicate_identifier.rb +63 -0
- data/app/validators/bulkrax/csv_row/missing_source_identifier.rb +31 -0
- data/app/validators/bulkrax/csv_row/parent_reference.rb +59 -0
- data/app/validators/bulkrax/csv_row/required_values.rb +64 -0
- data/app/views/bulkrax/guided_imports/new.html.erb +567 -0
- data/app/views/bulkrax/importers/index.html.erb +6 -1
- data/app/views/bulkrax/importers/new.html.erb +1 -1
- data/app/views/bulkrax/importers/show.html.erb +17 -1
- data/config/i18n-tasks.yml +195 -0
- data/config/locales/bulkrax.de.yml +504 -0
- data/config/locales/bulkrax.en.yml +459 -233
- data/config/locales/bulkrax.es.yml +504 -0
- data/config/locales/bulkrax.fr.yml +504 -0
- data/config/locales/bulkrax.it.yml +504 -0
- data/config/locales/bulkrax.pt-BR.yml +504 -0
- data/config/locales/bulkrax.zh.yml +503 -0
- data/config/routes.rb +10 -1
- data/lib/bulkrax/data/demo_scenarios.json +2235 -0
- data/lib/bulkrax/version.rb +1 -1
- data/lib/bulkrax.rb +31 -0
- metadata +55 -16
- data/app/services/bulkrax/sample_csv_service/column_builder.rb +0 -58
- data/app/services/bulkrax/sample_csv_service/column_descriptor.rb +0 -56
- data/app/services/bulkrax/sample_csv_service/csv_builder.rb +0 -82
- data/app/services/bulkrax/sample_csv_service/explanation_builder.rb +0 -51
- data/app/services/bulkrax/sample_csv_service/field_analyzer.rb +0 -54
- data/app/services/bulkrax/sample_csv_service/file_path_generator.rb +0 -16
- data/app/services/bulkrax/sample_csv_service/mapping_manager.rb +0 -36
- data/app/services/bulkrax/sample_csv_service/model_loader.rb +0 -40
- data/app/services/bulkrax/sample_csv_service/row_builder.rb +0 -33
- data/app/services/bulkrax/sample_csv_service/schema_analyzer.rb +0 -69
- data/app/services/bulkrax/sample_csv_service/split_formatter.rb +0 -42
- data/app/services/bulkrax/sample_csv_service/value_determiner.rb +0 -67
- data/app/services/bulkrax/sample_csv_service.rb +0 -78
- /data/{app/services → lib}/wings/custom_queries/find_by_source_identifier.rb +0 -0
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Bulkrax
|
|
4
|
-
# Analyzes model schemas for required and controlled vocabulary fields
|
|
5
|
-
class SampleCsvService::SchemaAnalyzer
|
|
6
|
-
def initialize(klass)
|
|
7
|
-
@klass = klass
|
|
8
|
-
@schema = load_schema
|
|
9
|
-
end
|
|
10
|
-
|
|
11
|
-
def required_terms
|
|
12
|
-
return [] if @schema.blank?
|
|
13
|
-
|
|
14
|
-
@schema.select do |field|
|
|
15
|
-
field.respond_to?(:meta) &&
|
|
16
|
-
field.meta["form"].is_a?(Hash) &&
|
|
17
|
-
field.meta["form"]["required"] == true
|
|
18
|
-
end.map(&:name).map(&:to_s)
|
|
19
|
-
rescue StandardError
|
|
20
|
-
[]
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
def controlled_vocab_terms
|
|
24
|
-
return [] unless @schema
|
|
25
|
-
|
|
26
|
-
controlled_properties = extract_controlled_properties
|
|
27
|
-
controlled_properties.empty? ? registered_controlled_vocab_fields : controlled_properties
|
|
28
|
-
rescue StandardError
|
|
29
|
-
[]
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
private
|
|
33
|
-
|
|
34
|
-
def load_schema
|
|
35
|
-
return nil unless @klass.respond_to?(:schema)
|
|
36
|
-
# Yes, this looks strange. The fallback is intentional.
|
|
37
|
-
# At the point in time when this service is being created, Hyrax behaves
|
|
38
|
-
# differently between flexible metadata setting on & off. This may be modified
|
|
39
|
-
# in the future, and this code can be revisited then.
|
|
40
|
-
# flexible=true: @klass.new.singleton_class.schema would return the full schema,
|
|
41
|
-
# but @klass.schema doesn't get the flexible metadata terms
|
|
42
|
-
# flexible=false: @klass.new.singleton_class.schema returns nil so it will fallback
|
|
43
|
-
@klass.new.singleton_class.schema || @klass.schema
|
|
44
|
-
rescue StandardError
|
|
45
|
-
nil
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
def extract_controlled_properties
|
|
49
|
-
return [] unless @schema
|
|
50
|
-
|
|
51
|
-
@schema.filter_map do |property|
|
|
52
|
-
next unless property.respond_to?(:meta)
|
|
53
|
-
sources = property.meta&.dig('controlled_values', 'sources')
|
|
54
|
-
next if sources.nil? || sources == ['null'] || sources == 'null'
|
|
55
|
-
property.name.to_s
|
|
56
|
-
end
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
def registered_controlled_vocab_fields
|
|
60
|
-
qa_registry.filter_map do |k, v|
|
|
61
|
-
k.singularize if v.klass == Qa::Authorities::Local::FileBasedAuthority
|
|
62
|
-
end
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
def qa_registry
|
|
66
|
-
@qa_registry ||= Qa::Authorities::Local.registry.instance_variable_get('@hash')
|
|
67
|
-
end
|
|
68
|
-
end
|
|
69
|
-
end
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Bulkrax
|
|
4
|
-
# Formats split pattern descriptions
|
|
5
|
-
class SampleCsvService::SplitFormatter
|
|
6
|
-
def format(split_value)
|
|
7
|
-
return "Property does not split." if split_value.nil?
|
|
8
|
-
|
|
9
|
-
if split_value == true
|
|
10
|
-
parse_pattern(Bulkrax.multi_value_element_split_on.source)
|
|
11
|
-
elsif split_value.is_a?(String)
|
|
12
|
-
parse_pattern(split_value)
|
|
13
|
-
else
|
|
14
|
-
split_value
|
|
15
|
-
end
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
private
|
|
19
|
-
|
|
20
|
-
def parse_pattern(pattern)
|
|
21
|
-
chars = extract_characters(pattern)
|
|
22
|
-
format_message(chars)
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
def extract_characters(pattern)
|
|
26
|
-
if (match = pattern.match(/\[([^\]]+)\]/))
|
|
27
|
-
match[1]
|
|
28
|
-
elsif (single = pattern.match(/\\(.)/))
|
|
29
|
-
single[1]
|
|
30
|
-
else
|
|
31
|
-
pattern
|
|
32
|
-
end
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
def format_message(chars)
|
|
36
|
-
formatted = chars.chars.then do |c|
|
|
37
|
-
c.length > 1 ? "#{c[0..-2].join(' ')}, or #{c.last}" : c.first
|
|
38
|
-
end
|
|
39
|
-
"Split multiple values with #{formatted}"
|
|
40
|
-
end
|
|
41
|
-
end
|
|
42
|
-
end
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Bulkrax
|
|
4
|
-
# Determines values for CSV cells
|
|
5
|
-
class SampleCsvService::ValueDeterminer
|
|
6
|
-
def initialize(service)
|
|
7
|
-
@service = service
|
|
8
|
-
@column_builder = SampleCsvService::ColumnBuilder.new(service)
|
|
9
|
-
end
|
|
10
|
-
|
|
11
|
-
def determine_value(column, model_name, field_list)
|
|
12
|
-
key = @service.mapping_manager.mapped_to_key(column)
|
|
13
|
-
required_terms = field_list.dig(model_name, 'required_terms')
|
|
14
|
-
|
|
15
|
-
if field_list.dig(model_name, "properties")&.include?(key)
|
|
16
|
-
mark_required_or_optional(key, required_terms)
|
|
17
|
-
elsif special_column?(column, key)
|
|
18
|
-
special_value(column, key, model_name, required_terms)
|
|
19
|
-
end
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
private
|
|
23
|
-
|
|
24
|
-
def special_column?(column, key)
|
|
25
|
-
descriptor = SampleCsvService::ColumnDescriptor.new
|
|
26
|
-
visibility_cols = descriptor.send(:extract_column_names, :visibility)
|
|
27
|
-
|
|
28
|
-
key.in?(['model', 'work_type']) ||
|
|
29
|
-
column.in?(visibility_cols) ||
|
|
30
|
-
column == 'source_identifier' ||
|
|
31
|
-
column == 'rights_statement' ||
|
|
32
|
-
relationship_column?(column) ||
|
|
33
|
-
file_column?(column)
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
def special_value(column, key, model_name, required_terms)
|
|
37
|
-
return SampleCsvService::ModelLoader.determine_klass_for(model_name).to_s if key.in?(['model', 'work_type'])
|
|
38
|
-
return 'Required' if column == 'source_identifier'
|
|
39
|
-
return mark_required_or_optional(key, required_terms) if column == 'rights_statement'
|
|
40
|
-
# collections do not have files
|
|
41
|
-
return nil if file_column?(column) && model_name.in?([Bulkrax.collection_model_class].compact.map(&:to_s))
|
|
42
|
-
'Optional'
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
def mark_required_or_optional(field, required_terms)
|
|
46
|
-
return 'Unknown' unless required_terms
|
|
47
|
-
required_terms.include?(field) ? 'Required' : 'Optional'
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
def relationship_column?(column)
|
|
51
|
-
relationships = [
|
|
52
|
-
@service.mapping_manager.find_by_flag("related_children_field_mapping", 'children'),
|
|
53
|
-
@service.mapping_manager.find_by_flag("related_parents_field_mapping", 'parents')
|
|
54
|
-
]
|
|
55
|
-
column.in?(relationships)
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
def file_column?(column)
|
|
59
|
-
file_cols = SampleCsvService::ColumnDescriptor::COLUMN_DESCRIPTIONS[:files].flat_map do |property_hash|
|
|
60
|
-
property_hash.keys.filter_map do |key|
|
|
61
|
-
@service.mappings.dig(key, "from")&.first
|
|
62
|
-
end
|
|
63
|
-
end
|
|
64
|
-
column.in?(file_cols)
|
|
65
|
-
end
|
|
66
|
-
end
|
|
67
|
-
end
|
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Bulkrax
|
|
4
|
-
## Main service class that orchestrates CSV generation
|
|
5
|
-
#
|
|
6
|
-
## Purpose:
|
|
7
|
-
# Generates CSV import templates that help users understand what fields are available for bulk importing content into a Hyrax repository.
|
|
8
|
-
# The service uses a modular architecture with specialized components that work together to create intelligent, model-specific CSV templates:
|
|
9
|
-
|
|
10
|
-
## 1. Initialization & Setup
|
|
11
|
-
# Loads Bulkrax field mappings (excluding auto-generated fields) via MappingManager
|
|
12
|
-
# Determines which models to include via ModelLoader (can target a specific model, all models, or none)
|
|
13
|
-
# Supports both ActiveFedora and Valkyrie object factories
|
|
14
|
-
|
|
15
|
-
## 2. Field Analysis
|
|
16
|
-
# FieldAnalyzer examines each model's schema to extract all available properties, and identifies:
|
|
17
|
-
# Required fields
|
|
18
|
-
# Controlled vocabulary terms
|
|
19
|
-
# If a term column can be split into multiple values during import
|
|
20
|
-
# SchemaAnalyzer introspects model schemas differently based on whether they're Valkyrie or ActiveFedora models
|
|
21
|
-
|
|
22
|
-
## 3. Column Building
|
|
23
|
-
# ColumnBuilder assembles the complete list of valid CSV columns by combining:
|
|
24
|
-
# Bulkrax-specific fields (model, source_identifier, parent, etc.)
|
|
25
|
-
# Model properties mapped through the field mapping system
|
|
26
|
-
# Controlled vocabulary information
|
|
27
|
-
# Filters out internal/system properties (created_at, updated_at, file_ids, embargo_id, etc.)
|
|
28
|
-
|
|
29
|
-
## 4. Row Generation
|
|
30
|
-
# The service creates three types of rows:
|
|
31
|
-
# Header Row: Column names mapped through Bulkrax's field mappings
|
|
32
|
-
# Explanation Row: Descriptions of what each column expects (via ExplanationBuilder and ColumnDescriptor)
|
|
33
|
-
# Data Rows: One row per model showing appropriate sample values (via RowBuilder and ValueDeterminer)
|
|
34
|
-
|
|
35
|
-
## 5. Intelligent Filtering
|
|
36
|
-
# Removes completely empty columns to keep the template clean and focused
|
|
37
|
-
# Preserves columns that contain any data across any model row
|
|
38
|
-
|
|
39
|
-
# 6. Output
|
|
40
|
-
# Can generate either a CSV file (default location: tmp/sample_csv_import_template.csv) or a CSV string
|
|
41
|
-
# Uses Ruby's CSV library for proper formatting and escaping
|
|
42
|
-
|
|
43
|
-
## Sample Usage:
|
|
44
|
-
# Bulkrax::SampleCsvService.call(model_name: 'GenericWork', output: 'file', file_path: 'path/to/output.csv')
|
|
45
|
-
# Bulkrax::SampleCsvService.call(model_name: nil, output: 'csv_string')
|
|
46
|
-
class SampleCsvService
|
|
47
|
-
attr_reader :model_name, :mappings, :all_models
|
|
48
|
-
|
|
49
|
-
def initialize(model_name: nil)
|
|
50
|
-
@model_name = model_name
|
|
51
|
-
@mapping_manager = MappingManager.new
|
|
52
|
-
@mappings = @mapping_manager.mappings
|
|
53
|
-
@all_models = ModelLoader.new(model_name).models
|
|
54
|
-
@field_analyzer = FieldAnalyzer.new(@mappings)
|
|
55
|
-
@csv_builder = CsvBuilder.new(self)
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
def self.call(model_name: nil, output: 'file', **args)
|
|
59
|
-
raise NameError, "Hyrax is not defined" unless defined?(::Hyrax)
|
|
60
|
-
new(model_name: model_name).send("to_#{output}", **args)
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
def to_file(file_path: nil)
|
|
64
|
-
file_path ||= FilePathGenerator.default_path
|
|
65
|
-
@csv_builder.write_to_file(file_path)
|
|
66
|
-
file_path
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
def to_csv_string
|
|
70
|
-
@csv_builder.generate_string
|
|
71
|
-
end
|
|
72
|
-
|
|
73
|
-
# Delegate methods to appropriate components
|
|
74
|
-
attr_reader :field_analyzer
|
|
75
|
-
|
|
76
|
-
attr_reader :mapping_manager
|
|
77
|
-
end
|
|
78
|
-
end
|
|
File without changes
|