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.
Files changed (93) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +11 -1
  3. data/app/assets/javascripts/bulkrax/application.js +2 -1
  4. data/app/assets/javascripts/bulkrax/bulkrax.js +13 -4
  5. data/app/assets/javascripts/bulkrax/bulkrax_utils.js +96 -0
  6. data/app/assets/javascripts/bulkrax/datatables.js +1 -0
  7. data/app/assets/javascripts/bulkrax/entries.js +17 -10
  8. data/app/assets/javascripts/bulkrax/importers.js.erb +9 -2
  9. data/app/assets/javascripts/bulkrax/importers_stepper.js +2420 -0
  10. data/app/assets/stylesheets/bulkrax/application.css +1 -1
  11. data/app/assets/stylesheets/bulkrax/stepper/_header.scss +83 -0
  12. data/app/assets/stylesheets/bulkrax/stepper/_mixins.scss +26 -0
  13. data/app/assets/stylesheets/bulkrax/stepper/_navigation.scss +103 -0
  14. data/app/assets/stylesheets/bulkrax/stepper/_responsive.scss +46 -0
  15. data/app/assets/stylesheets/bulkrax/stepper/_review.scss +92 -0
  16. data/app/assets/stylesheets/bulkrax/stepper/_settings.scss +106 -0
  17. data/app/assets/stylesheets/bulkrax/stepper/_success.scss +26 -0
  18. data/app/assets/stylesheets/bulkrax/stepper/_summary.scss +171 -0
  19. data/app/assets/stylesheets/bulkrax/stepper/_upload.scss +339 -0
  20. data/app/assets/stylesheets/bulkrax/stepper/_validation.scss +237 -0
  21. data/app/assets/stylesheets/bulkrax/stepper/_variables.scss +46 -0
  22. data/app/assets/stylesheets/bulkrax/stepper.scss +32 -0
  23. data/app/controllers/bulkrax/guided_imports_controller.rb +175 -0
  24. data/app/controllers/bulkrax/importers_controller.rb +28 -31
  25. data/app/controllers/concerns/bulkrax/guided_import_demo_scenarios.rb +201 -0
  26. data/app/controllers/concerns/bulkrax/importer_file_handler.rb +217 -0
  27. data/app/factories/bulkrax/object_factory.rb +3 -2
  28. data/app/factories/bulkrax/valkyrie_object_factory.rb +61 -17
  29. data/app/jobs/bulkrax/importer_job.rb +11 -4
  30. data/app/models/bulkrax/csv_entry.rb +27 -7
  31. data/app/models/bulkrax/entry.rb +4 -0
  32. data/app/models/bulkrax/importer.rb +31 -1
  33. data/app/models/concerns/bulkrax/has_matchers.rb +2 -2
  34. data/app/models/concerns/bulkrax/importer_exporter_behavior.rb +6 -5
  35. data/app/parsers/bulkrax/application_parser.rb +31 -5
  36. data/app/parsers/bulkrax/csv_parser.rb +42 -10
  37. data/app/parsers/concerns/bulkrax/csv_parser/csv_template_generation.rb +73 -0
  38. data/app/parsers/concerns/bulkrax/csv_parser/csv_validation.rb +133 -0
  39. data/app/parsers/concerns/bulkrax/csv_parser/csv_validation_helpers.rb +282 -0
  40. data/app/parsers/concerns/bulkrax/csv_parser/csv_validation_hierarchy.rb +96 -0
  41. data/app/services/bulkrax/csv_template/column_builder.rb +60 -0
  42. data/app/services/bulkrax/csv_template/column_descriptor.rb +58 -0
  43. data/app/services/bulkrax/csv_template/csv_builder.rb +83 -0
  44. data/app/services/bulkrax/csv_template/explanation_builder.rb +57 -0
  45. data/app/services/bulkrax/csv_template/field_analyzer.rb +56 -0
  46. data/app/services/bulkrax/csv_template/file_path_generator.rb +47 -0
  47. data/app/services/bulkrax/csv_template/file_validator.rb +68 -0
  48. data/app/services/bulkrax/csv_template/mapping_manager.rb +55 -0
  49. data/app/services/bulkrax/csv_template/model_loader.rb +50 -0
  50. data/app/services/bulkrax/csv_template/row_builder.rb +35 -0
  51. data/app/services/bulkrax/csv_template/schema_analyzer.rb +70 -0
  52. data/app/services/bulkrax/csv_template/split_formatter.rb +44 -0
  53. data/app/services/bulkrax/csv_template/value_determiner.rb +68 -0
  54. data/app/services/bulkrax/stepper_response_formatter.rb +347 -0
  55. data/app/services/bulkrax/validation_error_csv_builder.rb +99 -0
  56. data/app/validators/bulkrax/csv_row/child_reference.rb +56 -0
  57. data/app/validators/bulkrax/csv_row/circular_reference.rb +71 -0
  58. data/app/validators/bulkrax/csv_row/controlled_vocabulary.rb +74 -0
  59. data/app/validators/bulkrax/csv_row/duplicate_identifier.rb +63 -0
  60. data/app/validators/bulkrax/csv_row/missing_source_identifier.rb +31 -0
  61. data/app/validators/bulkrax/csv_row/parent_reference.rb +59 -0
  62. data/app/validators/bulkrax/csv_row/required_values.rb +64 -0
  63. data/app/views/bulkrax/guided_imports/new.html.erb +567 -0
  64. data/app/views/bulkrax/importers/index.html.erb +6 -1
  65. data/app/views/bulkrax/importers/new.html.erb +1 -1
  66. data/app/views/bulkrax/importers/show.html.erb +17 -1
  67. data/config/i18n-tasks.yml +195 -0
  68. data/config/locales/bulkrax.de.yml +504 -0
  69. data/config/locales/bulkrax.en.yml +459 -233
  70. data/config/locales/bulkrax.es.yml +504 -0
  71. data/config/locales/bulkrax.fr.yml +504 -0
  72. data/config/locales/bulkrax.it.yml +504 -0
  73. data/config/locales/bulkrax.pt-BR.yml +504 -0
  74. data/config/locales/bulkrax.zh.yml +503 -0
  75. data/config/routes.rb +10 -1
  76. data/lib/bulkrax/data/demo_scenarios.json +2235 -0
  77. data/lib/bulkrax/version.rb +1 -1
  78. data/lib/bulkrax.rb +31 -0
  79. metadata +55 -16
  80. data/app/services/bulkrax/sample_csv_service/column_builder.rb +0 -58
  81. data/app/services/bulkrax/sample_csv_service/column_descriptor.rb +0 -56
  82. data/app/services/bulkrax/sample_csv_service/csv_builder.rb +0 -82
  83. data/app/services/bulkrax/sample_csv_service/explanation_builder.rb +0 -51
  84. data/app/services/bulkrax/sample_csv_service/field_analyzer.rb +0 -54
  85. data/app/services/bulkrax/sample_csv_service/file_path_generator.rb +0 -16
  86. data/app/services/bulkrax/sample_csv_service/mapping_manager.rb +0 -36
  87. data/app/services/bulkrax/sample_csv_service/model_loader.rb +0 -40
  88. data/app/services/bulkrax/sample_csv_service/row_builder.rb +0 -33
  89. data/app/services/bulkrax/sample_csv_service/schema_analyzer.rb +0 -69
  90. data/app/services/bulkrax/sample_csv_service/split_formatter.rb +0 -42
  91. data/app/services/bulkrax/sample_csv_service/value_determiner.rb +0 -67
  92. data/app/services/bulkrax/sample_csv_service.rb +0 -78
  93. /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