bulkrax 2.0.2 → 2.2.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c862fc4eef8cf2d892ab2c3dd635ed6070d887b87104b007dc5dd7fffdd5fd51
4
- data.tar.gz: 7e735123366e3e7998d2e99ccdaf97047560e2e8fc8695a69e21da8abb6234a0
3
+ metadata.gz: b581b6768e5d7300546aec47d8cdfdde60cf462f37b51f6b434e92d1f9018334
4
+ data.tar.gz: 8d82be5129abaea32753bebc862cd5da8960cda8865e9749cbf5cfff1b49b012
5
5
  SHA512:
6
- metadata.gz: 5b6bb1cd44e421a22cb93df50aea16292da25e3d4e124797ec2024ad17fb8d239033c1c89d9cec7984fd3dff575278443489b59523fe46e328f63a8f265be826
7
- data.tar.gz: 43d65c3c6ef09d07cb677dfd35b2476eaa8e32c8d5ddcbddfd1a55b25541a882f3611414d38d351b134763723181ce2a15ab926e0a306cb8157e1417763b97b8
6
+ metadata.gz: f98c57a345d2bf08789877cc854bcc50680a97b7b2638965cd7940e311e4ff72c0db86ff4ca71e57c7058f2609035fa2ec402f298df439c5899190cfd8efc8c8
7
+ data.tar.gz: '09c7d9d8d51db04f5f01356c22f09e9d5041880effec9352b853d609668a58809dbb0f151d93f8c95c3f53bafcf3bbb57a6a4ab036616583dec1ed124d6d101b'
@@ -37,6 +37,7 @@ module Bulkrax
37
37
 
38
38
  @work_entries = @importer.entries.where(type: @importer.parser.entry_class.to_s).page(params[:work_entries_page]).per(30)
39
39
  @collection_entries = @importer.entries.where(type: @importer.parser.collection_entry_class.to_s).page(params[:collections_entries_page]).per(30)
40
+ @file_set_entries = @importer.entries.where(type: @importer.parser.file_set_entry_class.to_s).page(params[:file_set_entries_page]).per(30)
40
41
  end
41
42
  end
42
43
 
@@ -4,11 +4,13 @@ module Bulkrax
4
4
  class ObjectFactory
5
5
  extend ActiveModel::Callbacks
6
6
  include Bulkrax::FileFactory
7
+ include DynamicRecordLookup
8
+
7
9
  define_model_callbacks :save, :create
8
- attr_reader :attributes, :object, :source_identifier_value, :klass, :replace_files, :update_files, :work_identifier, :collection_field_mapping
10
+ attr_reader :attributes, :object, :source_identifier_value, :klass, :replace_files, :update_files, :work_identifier, :collection_field_mapping, :related_parents_parsed_mapping
9
11
 
10
12
  # rubocop:disable Metrics/ParameterLists
11
- def initialize(attributes:, source_identifier_value:, work_identifier:, collection_field_mapping:, replace_files: false, user: nil, klass: nil, update_files: false)
13
+ def initialize(attributes:, source_identifier_value:, work_identifier:, collection_field_mapping:, related_parents_parsed_mapping: nil, replace_files: false, user: nil, klass: nil, update_files: false)
12
14
  ActiveSupport::Deprecation.warn(
13
15
  'Creating Collections using the collection_field_mapping will no longer be supported as of Bulkrax version 3.0.' \
14
16
  ' Please configure Bulkrax to use related_parents_field_mapping and related_children_field_mapping instead.'
@@ -19,6 +21,7 @@ module Bulkrax
19
21
  @user = user || User.batch_user
20
22
  @work_identifier = work_identifier
21
23
  @collection_field_mapping = collection_field_mapping
24
+ @related_parents_parsed_mapping = related_parents_parsed_mapping
22
25
  @source_identifier_value = source_identifier_value
23
26
  @klass = klass || Bulkrax.default_work_type.constantize
24
27
  end
@@ -33,7 +36,7 @@ module Bulkrax
33
36
  arg_hash = { id: attributes[:id], name: 'UPDATE', klass: klass }
34
37
  @object = find
35
38
  if object
36
- object.reindex_extent = Hyrax::Adapters::NestingIndexAdapter::LIMITED_REINDEX
39
+ object.reindex_extent = Hyrax::Adapters::NestingIndexAdapter::LIMITED_REINDEX if object.respond_to?(:reindex_extent)
37
40
  ActiveSupport::Notifications.instrument('import.importer', arg_hash) { update }
38
41
  else
39
42
  ActiveSupport::Notifications.instrument('import.importer', arg_hash.merge(name: 'CREATE')) { create }
@@ -51,10 +54,16 @@ module Bulkrax
51
54
 
52
55
  def update
53
56
  raise "Object doesn't exist" unless object
54
- destroy_existing_files if @replace_files && klass != Collection
57
+ destroy_existing_files if @replace_files && ![Collection, FileSet].include?(klass)
55
58
  attrs = attribute_update
56
59
  run_callbacks :save do
57
- klass == Collection ? update_collection(attrs) : work_actor.update(environment(attrs))
60
+ if klass == Collection
61
+ update_collection(attrs)
62
+ elsif klass == FileSet
63
+ update_file_set(attrs)
64
+ else
65
+ work_actor.update(environment(attrs))
66
+ end
58
67
  end
59
68
  log_updated(object)
60
69
  end
@@ -90,10 +99,16 @@ module Bulkrax
90
99
  def create
91
100
  attrs = create_attributes
92
101
  @object = klass.new
93
- object.reindex_extent = Hyrax::Adapters::NestingIndexAdapter::LIMITED_REINDEX
102
+ object.reindex_extent = Hyrax::Adapters::NestingIndexAdapter::LIMITED_REINDEX if object.respond_to?(:reindex_extent)
94
103
  run_callbacks :save do
95
104
  run_callbacks :create do
96
- klass == Collection ? create_collection(attrs) : work_actor.create(environment(attrs))
105
+ if klass == Collection
106
+ create_collection(attrs)
107
+ elsif klass == FileSet
108
+ create_file_set(attrs)
109
+ else
110
+ work_actor.create(environment(attrs))
111
+ end
97
112
  end
98
113
  end
99
114
  log_created(object)
@@ -150,6 +165,35 @@ module Bulkrax
150
165
  object.save!
151
166
  end
152
167
 
168
+ # This method is heavily inspired by Hyrax's AttachFilesToWorkJob
169
+ def create_file_set(attrs)
170
+ work = find_record(attributes[related_parents_parsed_mapping].first)
171
+ work_permissions = work.permissions.map(&:to_hash)
172
+ file_set_attrs = attrs.slice(*object.attributes.keys)
173
+ object.assign_attributes(file_set_attrs)
174
+
175
+ attrs['uploaded_files'].each do |uploaded_file_id|
176
+ uploaded_file = ::Hyrax::UploadedFile.find(uploaded_file_id)
177
+ next if uploaded_file.file_set_uri.present?
178
+
179
+ actor = ::Hyrax::Actors::FileSetActor.new(object, @user)
180
+ uploaded_file.update(file_set_uri: actor.file_set.uri)
181
+ actor.file_set.permissions_attributes = work_permissions
182
+ actor.create_metadata
183
+ actor.create_content(uploaded_file)
184
+ actor.attach_to_work(work)
185
+ end
186
+
187
+ object.save!
188
+ end
189
+
190
+ def update_file_set(attrs)
191
+ file_set_attrs = attrs.slice(*object.attributes.keys)
192
+ actor = ::Hyrax::Actors::FileSetActor.new(object, @user)
193
+
194
+ actor.update_metadata(file_set_attrs)
195
+ end
196
+
153
197
  # Add child to parent's #member_collections
154
198
  # Add parent to child's #member_of_collections
155
199
  def persist_collection_memberships(parent:, child:)
@@ -17,6 +17,8 @@ module Bulkrax
17
17
  # NOTE: In the context of this job, "identifier" is used to generically refer
18
18
  # to either a record's ID or an Bulkrax::Entry's source_identifier.
19
19
  class CreateRelationshipsJob < ApplicationJob
20
+ include DynamicRecordLookup
21
+
20
22
  queue_as :import
21
23
 
22
24
  attr_accessor :base_entry, :child_record, :parent_record, :importer_run
@@ -74,39 +76,6 @@ module Bulkrax
74
76
  end
75
77
  end
76
78
 
77
- # This method allows us to create relationships with preexisting records (by their ID) OR
78
- # with records that are concurrently being imported (by their Bulkrax::Entry source_identifier).
79
- #
80
- # @param identifier [String] Work/Collection ID or Bulkrax::Entry source_identifier
81
- # @return [Work, Collection, nil] Work or Collection if found, otherwise nil
82
- def find_record(identifier)
83
- record = Entry.find_by(identifier: identifier)
84
- record ||= ::Collection.where(id: identifier).first
85
- if record.blank?
86
- available_work_types.each do |work_type|
87
- record ||= work_type.where(id: identifier).first
88
- end
89
- end
90
- record = record.factory.find if record.is_a?(Entry)
91
-
92
- record
93
- end
94
-
95
- # Check if the record is a Work
96
- def curation_concern?(record)
97
- available_work_types.include?(record.class)
98
- end
99
-
100
- # @return [Array<Class>] list of work type classes
101
- def available_work_types
102
- # If running in a Hyku app, do not reference disabled work types
103
- @available_work_types ||= if defined?(::Hyku)
104
- ::Site.instance.available_works.map(&:constantize)
105
- else
106
- ::Hyrax.config.curation_concerns
107
- end
108
- end
109
-
110
79
  def user
111
80
  @user ||= importer_run.importer.user
112
81
  end
@@ -11,9 +11,11 @@ module Bulkrax
11
11
  entry.build
12
12
  entry.save
13
13
  add_user_to_permission_template!(entry)
14
+ ImporterRun.find(args[1]).increment!(:processed_records)
14
15
  ImporterRun.find(args[1]).increment!(:processed_collections)
15
16
  ImporterRun.find(args[1]).decrement!(:enqueued_records)
16
17
  rescue => e
18
+ ImporterRun.find(args[1]).increment!(:failed_records)
17
19
  ImporterRun.find(args[1]).increment!(:failed_collections)
18
20
  ImporterRun.find(args[1]).decrement!(:enqueued_records)
19
21
  raise e
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bulkrax
4
+ class MissingParentError < ::StandardError; end
5
+ class ImportFileSetJob < ApplicationJob
6
+ include DynamicRecordLookup
7
+
8
+ queue_as :import
9
+
10
+ def perform(entry_id, importer_run_id)
11
+ entry = Entry.find(entry_id)
12
+ parent_identifier = entry.raw_metadata[entry.related_parents_raw_mapping]&.strip
13
+
14
+ validate_parent!(parent_identifier)
15
+
16
+ entry.build
17
+ if entry.succeeded?
18
+ # rubocop:disable Rails/SkipsModelValidations
19
+ ImporterRun.find(importer_run_id).increment!(:processed_records)
20
+ ImporterRun.find(importer_run_id).increment!(:processed_file_sets)
21
+ else
22
+ ImporterRun.find(importer_run_id).increment!(:failed_records)
23
+ ImporterRun.find(importer_run_id).increment!(:failed_file_sets)
24
+ # rubocop:enable Rails/SkipsModelValidations
25
+ end
26
+ ImporterRun.find(importer_run_id).decrement!(:enqueued_records) # rubocop:disable Rails/SkipsModelValidations
27
+ entry.save!
28
+
29
+ rescue MissingParentError => e
30
+ # try waiting for the parent record to be created
31
+ entry.import_attempts += 1
32
+ entry.save!
33
+ if entry.import_attempts < 5
34
+ ImportFileSetJob
35
+ .set(wait: (entry.import_attempts + 1).minutes)
36
+ .perform_later(entry_id, importer_run_id)
37
+ else
38
+ ImporterRun.find(importer_run_id).decrement!(:enqueued_records) # rubocop:disable Rails/SkipsModelValidations
39
+ entry.status_info(e)
40
+ end
41
+ end
42
+
43
+ private
44
+
45
+ attr_reader :parent_record
46
+
47
+ def validate_parent!(parent_identifier)
48
+ # if parent_identifier is missing, it will be caught by #validate_presence_of_parent!
49
+ return if parent_identifier.blank?
50
+
51
+ find_parent_record(parent_identifier)
52
+ check_parent_exists!(parent_identifier)
53
+ check_parent_is_a_work!(parent_identifier)
54
+ end
55
+
56
+ def check_parent_exists!(parent_identifier)
57
+ raise MissingParentError, %(Unable to find a record with the identifier "#{parent_identifier}") if parent_record.blank?
58
+ end
59
+
60
+ def check_parent_is_a_work!(parent_identifier)
61
+ error_msg = %(A record with the ID "#{parent_identifier}" was found, but it was a #{parent_record.class}, which is not an valid/available work type)
62
+ raise ::StandardError, error_msg unless curation_concern?(parent_record)
63
+ end
64
+
65
+ def find_parent_record(parent_identifier)
66
+ @parent_record ||= find_record(parent_identifier)
67
+ end
68
+ end
69
+ end
@@ -10,11 +10,13 @@ module Bulkrax
10
10
  entry.build
11
11
  if entry.status == "Complete"
12
12
  ImporterRun.find(args[1]).increment!(:processed_records)
13
+ ImporterRun.find(args[1]).increment!(:processed_works)
13
14
  ImporterRun.find(args[1]).decrement!(:enqueued_records) unless ImporterRun.find(args[1]).enqueued_records <= 0 # rubocop:disable Style/IdenticalConditionalBranches
14
15
  else
15
16
  # do not retry here because whatever parse error kept you from creating a work will likely
16
17
  # keep preventing you from doing so.
17
18
  ImporterRun.find(args[1]).increment!(:failed_records)
19
+ ImporterRun.find(args[1]).increment!(:failed_works)
18
20
  ImporterRun.find(args[1]).decrement!(:enqueued_records) unless ImporterRun.find(args[1]).enqueued_records <= 0 # rubocop:disable Style/IdenticalConditionalBranches
19
21
  end
20
22
  entry.save!
@@ -20,6 +20,7 @@ module Bulkrax
20
20
 
21
21
  importer.import_collections
22
22
  importer.import_works
23
+ importer.import_file_sets
23
24
  end
24
25
 
25
26
  def unzip_imported_file(parser)
@@ -31,6 +32,7 @@ module Bulkrax
31
32
  def update_current_run_counters(importer)
32
33
  importer.current_run.total_work_entries = importer.limit || importer.parser.works_total
33
34
  importer.current_run.total_collection_entries = importer.parser.collections_total
35
+ importer.current_run.total_file_set_entries = importer.parser.file_sets_total
34
36
  importer.current_run.save!
35
37
  end
36
38
 
@@ -40,9 +40,9 @@ module Bulkrax
40
40
 
41
41
  self.parsed_metadata = {}
42
42
  add_identifier
43
- add_metadata_for_model
44
43
  add_visibility
45
44
  add_ingested_metadata
45
+ add_metadata_for_model
46
46
  add_rights_statement
47
47
  add_collections
48
48
  add_local
@@ -57,6 +57,9 @@ module Bulkrax
57
57
  def add_metadata_for_model
58
58
  if factory_class == Collection
59
59
  add_collection_type_gid
60
+ elsif factory_class == FileSet
61
+ add_path_to_file
62
+ validate_presence_of_parent!
60
63
  else
61
64
  add_file unless importerexporter.metadata_only?
62
65
  add_admin_set_id
@@ -85,7 +88,11 @@ module Bulkrax
85
88
  elsif record['file'].is_a?(Array)
86
89
  self.parsed_metadata['file'] = record['file']
87
90
  end
88
- self.parsed_metadata['file'] = self.parsed_metadata['file'].map { |f| path_to_file(f.tr(' ', '_')) }
91
+ self.parsed_metadata['file'] = self.parsed_metadata['file'].map do |f|
92
+ next if f.blank?
93
+
94
+ path_to_file(f.tr(' ', '_'))
95
+ end.compact
89
96
  end
90
97
 
91
98
  def build_export_metadata
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bulkrax
4
+ class CsvFileSetEntry < CsvEntry
5
+ def factory_class
6
+ ::FileSet
7
+ end
8
+
9
+ def add_path_to_file
10
+ parsed_metadata['file'].each_with_index do |filename, i|
11
+ path_to_file = ::File.join(parser.path_to_files, filename)
12
+
13
+ parsed_metadata['file'][i] = path_to_file
14
+ end
15
+ raise ::StandardError, "one or more file paths are invalid: #{parsed_metadata['file'].join(', ')}" unless parsed_metadata['file'].map { |file_path| ::File.file?(file_path) }.all?
16
+
17
+ parsed_metadata['file']
18
+ end
19
+
20
+ def validate_presence_of_parent!
21
+ return if parsed_metadata[related_parents_parsed_mapping]&.map(&:present?)&.any?
22
+
23
+ raise StandardError, 'File set must be related to at least one work'
24
+ end
25
+ end
26
+ end
@@ -99,7 +99,12 @@ module Bulkrax
99
99
  @current_run ||= if file? && zip?
100
100
  self.importer_runs.create!
101
101
  else
102
- self.importer_runs.create!(total_work_entries: self.limit || parser.works_total, total_collection_entries: parser.collections_total)
102
+ entry_counts = {
103
+ total_work_entries: self.limit || parser.works_total,
104
+ total_collection_entries: parser.collections_total,
105
+ total_file_set_entries: parser.file_sets_total
106
+ }
107
+ self.importer_runs.create!(entry_counts)
103
108
  end
104
109
  end
105
110
 
@@ -134,6 +139,13 @@ module Bulkrax
134
139
  status_info(e)
135
140
  end
136
141
 
142
+ def import_file_sets
143
+ self.save if self.new_record? # Object needs to be saved for statuses
144
+ parser.create_file_sets
145
+ rescue StandardError => e
146
+ status_info(e)
147
+ end
148
+
137
149
  # Prepend the base_url to ensure unique set identifiers
138
150
  # @todo - move to parser, as this is OAI specific
139
151
  def unique_collection_identifier(id)
@@ -30,7 +30,7 @@ module Bulkrax
30
30
 
31
31
  # Create some headers for the datastream
32
32
  def content_options
33
- { disposition: 'inline', type: download_content_type, filename: file_name }
33
+ { disposition: 'attachment', type: download_content_type, filename: file_name }
34
34
  end
35
35
 
36
36
  # render an HTTP HEAD response
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bulkrax
4
+ module DynamicRecordLookup
5
+ # Search entries, collections, and every available work type for a record that
6
+ # has the provided identifier.
7
+ #
8
+ # @param identifier [String] Work/Collection ID or Bulkrax::Entry source_identifier
9
+ # @return [Work, Collection, nil] Work or Collection if found, otherwise nil
10
+ def find_record(identifier)
11
+ record = Entry.find_by(identifier: identifier)
12
+ record ||= ::Collection.where(id: identifier).first # rubocop:disable Rails/FindBy
13
+ if record.blank?
14
+ available_work_types.each do |work_type|
15
+ record ||= work_type.where(id: identifier).first # rubocop:disable Rails/FindBy
16
+ end
17
+ end
18
+
19
+ record.is_a?(Entry) ? record.factory.find : record
20
+ end
21
+
22
+ # Check if the record is a Work
23
+ def curation_concern?(record)
24
+ available_work_types.include?(record.class)
25
+ end
26
+
27
+ private
28
+
29
+ # @return [Array<Class>] list of work type classes
30
+ def available_work_types
31
+ # If running in a Hyku app, do not include disabled work types
32
+ @available_work_types ||= if defined?(::Hyku)
33
+ ::Site.instance.available_works.map(&:constantize)
34
+ else
35
+ ::Hyrax.config.curation_concerns
36
+ end
37
+ end
38
+ end
39
+ end
@@ -96,6 +96,7 @@ module Bulkrax
96
96
  source_identifier_value: identifier,
97
97
  work_identifier: parser.work_identifier,
98
98
  collection_field_mapping: parser.collection_field_mapping,
99
+ related_parents_parsed_mapping: related_parents_parsed_mapping,
99
100
  replace_files: replace_files,
100
101
  user: user,
101
102
  klass: factory_class,
@@ -20,15 +20,17 @@ module Bulkrax
20
20
  (last_imported_at || Time.current) + frequency.to_seconds if schedulable? && last_imported_at.present?
21
21
  end
22
22
 
23
- def increment_counters(index, collection = false)
23
+ def increment_counters(index, collection: false, file_set: false)
24
24
  # Only set the totals if they were not set on initialization
25
25
  if collection
26
26
  current_run.total_collection_entries = index + 1 unless parser.collections_total.positive?
27
+ elsif file_set
28
+ current_run.total_file_set_entries = index + 1 unless parser.file_sets_total.positive?
27
29
  else
28
30
  # TODO: differentiate between work and collection counts for exporters
29
31
  current_run.total_work_entries = index + 1 unless limit.to_i.positive? || parser.total.positive?
30
32
  end
31
- current_run.enqueued_records = index + 1
33
+ current_run.enqueued_records += 1
32
34
  current_run.save!
33
35
  end
34
36
 
@@ -114,6 +114,10 @@ module Bulkrax
114
114
  raise StandardError, 'must be defined' if importer?
115
115
  end
116
116
 
117
+ def create_file_sets
118
+ raise StandardError, 'must be defined' if importer?
119
+ end
120
+
117
121
  # Optional, define if using browse everything for file upload
118
122
  def retrieve_cloud_files(files); end
119
123
 
@@ -234,6 +238,10 @@ module Bulkrax
234
238
  0
235
239
  end
236
240
 
241
+ def file_sets_total
242
+ 0
243
+ end
244
+
237
245
  def write
238
246
  write_files
239
247
  zip
@@ -59,7 +59,7 @@ module Bulkrax
59
59
  }
60
60
  new_entry = find_or_create_entry(collection_entry_class, collection, 'Bulkrax::Importer', metadata)
61
61
  ImportCollectionJob.perform_now(new_entry.id, current_run.id)
62
- increment_counters(index, true)
62
+ increment_counters(index, collection: true)
63
63
  end
64
64
  end
65
65
 
@@ -38,13 +38,27 @@ module Bulkrax
38
38
  end
39
39
 
40
40
  def works
41
- records - collections
41
+ records - collections - file_sets
42
42
  end
43
43
 
44
44
  def works_total
45
45
  works.size
46
46
  end
47
47
 
48
+ def file_sets
49
+ records.map do |r|
50
+ file_sets = []
51
+ model_field_mappings.each do |model_mapping|
52
+ file_sets << r if r[model_mapping.to_sym]&.downcase == 'fileset'
53
+ end
54
+ file_sets
55
+ end.flatten.compact.uniq
56
+ end
57
+
58
+ def file_sets_total
59
+ file_sets.size
60
+ end
61
+
48
62
  # We could use CsvEntry#fields_from_data(data) but that would mean re-reading the data
49
63
  def import_fields
50
64
  @import_fields ||= records.inject(:merge).keys.compact.uniq
@@ -85,7 +99,7 @@ module Bulkrax
85
99
  metadata = if collection.delete(:from_collection_field_mapping)
86
100
  uci = unique_collection_identifier(collection)
87
101
  {
88
- title: [collection[:title]],
102
+ title: collection[:title],
89
103
  work_identifier => uci,
90
104
  source_identifier => uci,
91
105
  visibility: 'open',
@@ -96,9 +110,9 @@ module Bulkrax
96
110
  ## END
97
111
 
98
112
  new_entry = find_or_create_entry(collection_entry_class, collection_hash[source_identifier], 'Bulkrax::Importer', collection_hash)
113
+ increment_counters(index, collection: true)
99
114
  # TODO: add support for :delete option
100
115
  ImportCollectionJob.perform_now(new_entry.id, current_run.id)
101
- increment_counters(index, true)
102
116
  end
103
117
  importer.record_status
104
118
  rescue StandardError => e
@@ -124,6 +138,20 @@ module Bulkrax
124
138
  status_info(e)
125
139
  end
126
140
 
141
+ def create_file_sets
142
+ file_sets.each_with_index do |file_set, index|
143
+ next unless record_has_source_identifier(file_set, records.find_index(file_set))
144
+ break if limit_reached?(limit, records.find_index(file_set))
145
+
146
+ new_entry = find_or_create_entry(file_set_entry_class, file_set[source_identifier], 'Bulkrax::Importer', file_set.to_h)
147
+ ImportFileSetJob.perform_later(new_entry.id, current_run.id)
148
+ increment_counters(index, file_set: true)
149
+ end
150
+ importer.record_status
151
+ rescue StandardError => e
152
+ status_info(e)
153
+ end
154
+
127
155
  def write_partial_import_file(file)
128
156
  import_filename = import_file_path.split('/').last
129
157
  partial_import_filename = "#{File.basename(import_filename, '.csv')}_corrected_entries.csv"
@@ -149,40 +177,74 @@ module Bulkrax
149
177
  end
150
178
 
151
179
  def current_work_ids
180
+ ActiveSupport::Deprication.warn('Bulkrax::CsvParser#current_work_ids will be replaced with #current_record_ids in version 3.0')
181
+ current_record_ids
182
+ end
183
+
184
+ def current_record_ids
185
+ @work_ids = []
186
+ @collection_ids = []
187
+ @file_set_ids = []
188
+
152
189
  case importerexporter.export_from
153
190
  when 'all'
154
- ActiveFedora::SolrService.query("has_model_ssim:(#{Hyrax.config.curation_concerns.join(' OR ')}) #{extra_filters}", rows: 2_147_483_647).map(&:id)
191
+ @work_ids = ActiveFedora::SolrService.query("has_model_ssim:(#{Hyrax.config.curation_concerns.join(' OR ')}) #{extra_filters}", rows: 2_147_483_647).map(&:id)
192
+ @collection_ids = ActiveFedora::SolrService.query("has_model_ssim:Collection #{extra_filters}", rows: 2_147_483_647).map(&:id)
193
+ @file_set_ids = ActiveFedora::SolrService.query("has_model_ssim:FileSet #{extra_filters}", rows: 2_147_483_647).map(&:id)
155
194
  when 'collection'
156
- ActiveFedora::SolrService.query("member_of_collection_ids_ssim:#{importerexporter.export_source + extra_filters}", rows: 2_000_000_000).map(&:id)
195
+ @work_ids = ActiveFedora::SolrService.query("member_of_collection_ids_ssim:#{importerexporter.export_source + extra_filters}", rows: 2_000_000_000).map(&:id)
157
196
  when 'worktype'
158
- ActiveFedora::SolrService.query("has_model_ssim:#{importerexporter.export_source + extra_filters}", rows: 2_000_000_000).map(&:id)
197
+ @work_ids = ActiveFedora::SolrService.query("has_model_ssim:#{importerexporter.export_source + extra_filters}", rows: 2_000_000_000).map(&:id)
159
198
  when 'importer'
160
- entry_ids = Bulkrax::Importer.find(importerexporter.export_source).entries.pluck(:id)
161
- complete_statuses = Bulkrax::Status.latest_by_statusable
162
- .includes(:statusable)
163
- .where('bulkrax_statuses.statusable_id IN (?) AND bulkrax_statuses.statusable_type = ? AND status_message = ?', entry_ids, 'Bulkrax::Entry', 'Complete')
199
+ set_ids_for_exporting_from_importer
200
+ end
201
+
202
+ @work_ids + @collection_ids + @file_set_ids
203
+ end
164
204
 
165
- complete_entry_identifiers = complete_statuses.map { |s| s.statusable&.identifier&.gsub(':', '\:') }
166
- extra_filters = extra_filters.presence || '*:*'
205
+ # Set the following instance variables: @work_ids, @collection_ids, @file_set_ids
206
+ # @see #current_record_ids
207
+ def set_ids_for_exporting_from_importer
208
+ entry_ids = Importer.find(importerexporter.export_source).entries.pluck(:id)
209
+ complete_statuses = Status.latest_by_statusable
210
+ .includes(:statusable)
211
+ .where('bulkrax_statuses.statusable_id IN (?) AND bulkrax_statuses.statusable_type = ? AND status_message = ?', entry_ids, 'Bulkrax::Entry', 'Complete')
167
212
 
168
- ActiveFedora::SolrService.get(
213
+ complete_entry_identifiers = complete_statuses.map { |s| s.statusable&.identifier&.gsub(':', '\:') }
214
+ extra_filters = extra_filters.presence || '*:*'
215
+
216
+ { :@work_ids => ::Hyrax.config.curation_concerns, :@collection_ids => [::Collection], :@file_set_ids => [::FileSet] }.each do |instance_var, models_to_search|
217
+ instance_variable_set(instance_var, ActiveFedora::SolrService.get(
169
218
  extra_filters.to_s,
170
- fq: "#{work_identifier}_sim:(#{complete_entry_identifiers.join(' OR ')})",
219
+ fq: [
220
+ "#{work_identifier}_sim:(#{complete_entry_identifiers.join(' OR ')})",
221
+ "has_model_ssim:(#{models_to_search.join(' OR ')})"
222
+ ],
171
223
  fl: 'id',
172
224
  rows: 2_000_000_000
173
- )['response']['docs'].map { |obj| obj['id'] }
225
+ )['response']['docs'].map { |obj| obj['id'] })
174
226
  end
175
227
  end
176
228
 
177
229
  def create_new_entries
178
- current_work_ids.each_with_index do |wid, index|
230
+ current_record_ids.each_with_index do |id, index|
179
231
  break if limit_reached?(limit, index)
180
- new_entry = find_or_create_entry(entry_class, wid, 'Bulkrax::Exporter')
232
+
233
+ this_entry_class = if @collection_ids.include?(id)
234
+ collection_entry_class
235
+ elsif @file_set_ids.include?(id)
236
+ file_set_entry_class
237
+ else
238
+ entry_class
239
+ end
240
+ new_entry = find_or_create_entry(this_entry_class, id, 'Bulkrax::Exporter')
241
+
181
242
  begin
182
- entry = Bulkrax::ExportWorkJob.perform_now(new_entry.id, current_run.id)
243
+ entry = ExportWorkJob.perform_now(new_entry.id, current_run.id)
183
244
  rescue => e
184
245
  Rails.logger.info("#{e.message} was detected during export")
185
246
  end
247
+
186
248
  self.headers |= entry.parsed_metadata.keys if entry
187
249
  end
188
250
  end
@@ -199,6 +261,10 @@ module Bulkrax
199
261
  CsvCollectionEntry
200
262
  end
201
263
 
264
+ def file_set_entry_class
265
+ CsvFileSetEntry
266
+ end
267
+
202
268
  # See https://stackoverflow.com/questions/2650517/count-the-number-of-lines-in-a-file-without-reading-entire-file-into-memory
203
269
  # Changed to grep as wc -l counts blank lines, and ignores the final unescaped line (which may or may not contain data)
204
270
  def total
@@ -235,7 +301,7 @@ module Bulkrax
235
301
 
236
302
  def write_files
237
303
  CSV.open(setup_export_file, "w", headers: export_headers, write_headers: true) do |csv|
238
- importerexporter.entries.where(identifier: current_work_ids)[0..limit || total].each do |e|
304
+ importerexporter.entries.where(identifier: current_record_ids)[0..limit || total].each do |e|
239
305
  csv << e.parsed_metadata
240
306
  end
241
307
  end
@@ -76,7 +76,7 @@ module Bulkrax
76
76
  new_entry = collection_entry_class.where(importerexporter: importerexporter, identifier: unique_collection_identifier, raw_metadata: metadata).first_or_create!
77
77
  # perform now to ensure this gets created before work imports start
78
78
  ImportCollectionJob.perform_now(new_entry.id, importerexporter.current_run.id)
79
- increment_counters(index, true)
79
+ increment_counters(index, collection: true)
80
80
  end
81
81
  end
82
82
 
@@ -24,6 +24,7 @@
24
24
  <th scope="col">Entries Deleted Upstream</th>
25
25
  <th scope="col">Total Collection Entries</th>
26
26
  <th scope="col">Total Work Entries</th>
27
+ <th scope="col">Total File Set Entries</th>
27
28
  <th scope="col"></th>
28
29
  <th scope="col"></th>
29
30
  <th scope="col"></th>
@@ -36,12 +37,13 @@
36
37
  <td><%= importer.status %></td>
37
38
  <td><%= importer.last_imported_at.strftime("%b %d, %Y") if importer.last_imported_at %></td>
38
39
  <td><%= importer.next_import_at.strftime("%b %d, %Y") if importer.next_import_at %></td>
39
- <td><%= importer.importer_runs.last&.enqueued_records %></td>
40
- <td><%= (importer.importer_runs.last&.processed_collections || 0) + (importer.importer_runs.last&.processed_records || 0) %></td>
41
- <td><%= (importer.importer_runs.last&.failed_collections || 0) + (importer.importer_runs.last&.failed_records || 0) %></td>
42
- <td><%= importer.importer_runs.last&.deleted_records %></td>
43
- <td><%= importer.importer_runs.last&.total_collection_entries %></td>
44
- <td><%= importer.importer_runs.last&.total_work_entries %></td>
40
+ <td><%= importer.last_run&.enqueued_records %></td>
41
+ <td><%= (importer.last_run&.processed_records || 0) %></td>
42
+ <td><%= (importer.last_run&.failed_records || 0) %></td>
43
+ <td><%= importer.last_run&.deleted_records %></td>
44
+ <td><%= importer.last_run&.total_collection_entries %></td>
45
+ <td><%= importer.last_run&.total_work_entries %></td>
46
+ <td><%= importer.last_run&.total_file_set_entries %></td>
45
47
  <td><%= link_to raw('<span class="glyphicon glyphicon-info-sign"></span>'), importer_path(importer) %></td>
46
48
  <td><%= link_to raw('<span class="glyphicon glyphicon-pencil"></span>'), edit_importer_path(importer) %></td>
47
49
  <td><%= link_to raw('<span class="glyphicon glyphicon-remove"></span>'), importer, method: :delete, data: { confirm: 'Are you sure?' } %></td>
@@ -56,14 +56,19 @@
56
56
 
57
57
  <%= render partial: 'bulkrax/shared/bulkrax_field_mapping', locals: {item: @importer} %>
58
58
 
59
- <p class="bulkrax-p-align">
59
+ <p class="bulkrax-p-align" title="<%= @importer.last_run&.processed_works %> processed, <%= @importer.last_run&.failed_works %> failed">
60
60
  <strong>Total Works:</strong>
61
- <%= @importer.importer_runs.last&.total_work_entries %>
61
+ <%= @importer.last_run&.total_work_entries %>
62
62
  </p>
63
63
 
64
- <p class="bulkrax-p-align">
64
+ <p class="bulkrax-p-align" title="<%= @importer.last_run&.processed_collections %> processed, <%= @importer.last_run&.failed_collections %> failed">
65
65
  <strong>Total Collections:</strong>
66
- <%= @importer.importer_runs.last&.total_collection_entries %>
66
+ <%= @importer.last_run&.total_collection_entries %>
67
+ </p>
68
+
69
+ <p class="bulkrax-p-align" title="<%= @importer.last_run&.processed_file_sets %> processed, <%= @importer.last_run&.failed_file_sets %> failed">
70
+ <strong>Total File Sets:</strong>
71
+ <%= @importer.last_run&.total_file_set_entries %>
67
72
  </p>
68
73
 
69
74
  <div class="bulkrax-nav-tab-bottom-margin">
@@ -71,6 +76,7 @@
71
76
  <ul class="bulkrax-nav-tab-top-margin tab-nav nav nav-tabs" role="tablist">
72
77
  <li role="presentation" class='active'><a href="#work-entries" aria-controls="work-entries" role="tab" data-toggle="tab">Work Entries</a></li>
73
78
  <li role="presentation"><a href="#collection-entries" aria-controls="collection-entries" role="tab" data-toggle="tab">Collection Entries</a></li>
79
+ <li role="presentation"><a href="#file-set-entries" aria-controls="file-set-entries" role="tab" data-toggle="tab">File Set Entries</a></li>
74
80
  </ul>
75
81
  <!-- Tab panes -->
76
82
  <div class="tab-content outline">
@@ -158,6 +164,42 @@
158
164
  <%= page_entries_info(@collection_entries) %><br />
159
165
  <%= paginate(@collection_entries, theme: 'blacklight', param_name: :collections_entries_page, params: {anchor: 'collection-entries'}) %>
160
166
  </div>
167
+ <div role="tabpanel" class="tab-pane bulkrax-nav-tab-table-left-align" id="file-set-entries">
168
+ <table class='table table-striped'>
169
+ <thead>
170
+ <tr>
171
+ <th>Identifier</th>
172
+ <th>Entry ID</th>
173
+ <th>Status</th>
174
+ <th>Errors</th>
175
+ <th>Status Set At</th>
176
+ <th>Actions</th>
177
+ </tr>
178
+ </thead>
179
+ <tbody>
180
+ <% @file_set_entries.each do |e| %>
181
+ <tr>
182
+ <td><%= link_to e.identifier, bulkrax.importer_entry_path(@importer.id, e.id) %></td>
183
+ <td><%= e.id %></td>
184
+ <% if e.status == "Complete" %>
185
+ <td><span class="glyphicon glyphicon-ok" style="color: green;"></span> <%= e.status %></td>
186
+ <% else %>
187
+ <td><span class="glyphicon glyphicon-remove" style="color: red;"></span> <%= e.status %></td>
188
+ <% end %>
189
+ <% if e.last_error.present? %>
190
+ <td><%= link_to e.last_error.dig("error_class"), bulkrax.importer_entry_path(@importer.id, e.id) %></td>
191
+ <% else %>
192
+ <td></td>
193
+ <% end %>
194
+ <td><%= e.status_at %></td>
195
+ <td><%= link_to raw('<span class="glyphicon glyphicon-info-sign"></span>'), bulkrax.importer_entry_path(@importer.id, e.id) %></td>
196
+ </tr>
197
+ <% end %>
198
+ </tbody>
199
+ </table>
200
+ <%= page_entries_info(@file_set_entries) %><br />
201
+ <%= paginate(@file_set_entries, theme: 'blacklight', param_name: :file_set_entries_page, params: {anchor: 'file-set-entries'}) %>
202
+ </div>
161
203
  </div>
162
204
  </div>
163
205
 
@@ -0,0 +1,7 @@
1
+ class AddFileSetCountersToImporterRuns < ActiveRecord::Migration[5.2]
2
+ def change
3
+ add_column :bulkrax_importer_runs, :processed_file_sets, :integer, default: 0 unless column_exists?(:bulkrax_importer_runs, :processed_file_sets)
4
+ add_column :bulkrax_importer_runs, :failed_file_sets, :integer, default: 0 unless column_exists?(:bulkrax_importer_runs, :failed_file_sets)
5
+ add_column :bulkrax_importer_runs, :total_file_set_entries, :integer, default: 0 unless column_exists?(:bulkrax_importer_runs, :total_file_set_entries)
6
+ end
7
+ end
@@ -0,0 +1,5 @@
1
+ class AddImportAttemptsToEntries < ActiveRecord::Migration[5.2]
2
+ def change
3
+ add_column :bulkrax_entries, :import_attempts, :integer, default: 0 unless column_exists?(:bulkrax_entries, :import_attempts)
4
+ end
5
+ end
@@ -0,0 +1,6 @@
1
+ class AddWorkCountersToImporterRuns < ActiveRecord::Migration[5.2]
2
+ def change
3
+ add_column :bulkrax_importer_runs, :processed_works, :integer, default: 0 unless column_exists?(:bulkrax_importer_runs, :processed_works)
4
+ add_column :bulkrax_importer_runs, :failed_works, :integer, default: 0 unless column_exists?(:bulkrax_importer_runs, :failed_works)
5
+ end
6
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Bulkrax
4
- VERSION = '2.0.2'
4
+ VERSION = '2.2.1'
5
5
  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: 2.0.2
4
+ version: 2.2.1
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-01-31 00:00:00.000000000 Z
11
+ date: 2022-02-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -265,6 +265,7 @@ files:
265
265
  - app/jobs/bulkrax/export_work_job.rb
266
266
  - app/jobs/bulkrax/exporter_job.rb
267
267
  - app/jobs/bulkrax/import_collection_job.rb
268
+ - app/jobs/bulkrax/import_file_set_job.rb
268
269
  - app/jobs/bulkrax/import_work_job.rb
269
270
  - app/jobs/bulkrax/importer_job.rb
270
271
  - app/mailers/bulkrax/application_mailer.rb
@@ -275,6 +276,7 @@ files:
275
276
  - app/models/bulkrax/application_record.rb
276
277
  - app/models/bulkrax/csv_collection_entry.rb
277
278
  - app/models/bulkrax/csv_entry.rb
279
+ - app/models/bulkrax/csv_file_set_entry.rb
278
280
  - app/models/bulkrax/entry.rb
279
281
  - app/models/bulkrax/exporter.rb
280
282
  - app/models/bulkrax/exporter_run.rb
@@ -290,6 +292,7 @@ files:
290
292
  - app/models/bulkrax/status.rb
291
293
  - app/models/bulkrax/xml_entry.rb
292
294
  - app/models/concerns/bulkrax/download_behavior.rb
295
+ - app/models/concerns/bulkrax/dynamic_record_lookup.rb
293
296
  - app/models/concerns/bulkrax/errored_entries.rb
294
297
  - app/models/concerns/bulkrax/export_behavior.rb
295
298
  - app/models/concerns/bulkrax/file_factory.rb
@@ -357,6 +360,9 @@ files:
357
360
  - db/migrate/20210806065737_increase_text_sizes.rb
358
361
  - db/migrate/20211004170708_change_bulkrax_statuses_error_message_column_type_to_text.rb
359
362
  - db/migrate/20211203195233_rename_children_counters_to_relationships.rb
363
+ - db/migrate/20211220195027_add_file_set_counters_to_importer_runs.rb
364
+ - db/migrate/20220118001339_add_import_attempts_to_entries.rb
365
+ - db/migrate/20220119213325_add_work_counters_to_importer_runs.rb
360
366
  - lib/bulkrax.rb
361
367
  - lib/bulkrax/engine.rb
362
368
  - lib/bulkrax/version.rb
@@ -372,7 +378,7 @@ homepage: https://github.com/samvera-labs/bulkrax
372
378
  licenses:
373
379
  - Apache-2.0
374
380
  metadata: {}
375
- post_install_message:
381
+ post_install_message:
376
382
  rdoc_options: []
377
383
  require_paths:
378
384
  - lib
@@ -387,8 +393,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
387
393
  - !ruby/object:Gem::Version
388
394
  version: '0'
389
395
  requirements: []
390
- rubygems_version: 3.1.2
391
- signing_key:
396
+ rubygems_version: 3.1.4
397
+ signing_key:
392
398
  specification_version: 4
393
399
  summary: Import and export tool for Hyrax and Hyku
394
400
  test_files: []