bulkrax 1.0.2 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/app/controllers/bulkrax/exporters_controller.rb +12 -4
  4. data/app/controllers/bulkrax/importers_controller.rb +23 -17
  5. data/app/factories/bulkrax/object_factory.rb +84 -63
  6. data/app/jobs/bulkrax/create_relationships_job.rb +156 -0
  7. data/app/jobs/bulkrax/delete_work_job.rb +6 -2
  8. data/app/jobs/bulkrax/export_work_job.rb +3 -1
  9. data/app/jobs/bulkrax/exporter_job.rb +1 -0
  10. data/app/jobs/bulkrax/{import_work_collection_job.rb → import_collection_job.rb} +4 -2
  11. data/app/jobs/bulkrax/import_file_set_job.rb +69 -0
  12. data/app/jobs/bulkrax/import_work_job.rb +2 -0
  13. data/app/jobs/bulkrax/importer_job.rb +18 -1
  14. data/app/matchers/bulkrax/application_matcher.rb +5 -5
  15. data/app/models/bulkrax/csv_collection_entry.rb +8 -6
  16. data/app/models/bulkrax/csv_entry.rb +132 -65
  17. data/app/models/bulkrax/csv_file_set_entry.rb +26 -0
  18. data/app/models/bulkrax/entry.rb +19 -8
  19. data/app/models/bulkrax/exporter.rb +12 -5
  20. data/app/models/bulkrax/importer.rb +24 -5
  21. data/app/models/bulkrax/oai_entry.rb +5 -1
  22. data/app/models/bulkrax/rdf_entry.rb +16 -7
  23. data/app/models/bulkrax/xml_entry.rb +4 -0
  24. data/app/models/concerns/bulkrax/dynamic_record_lookup.rb +39 -0
  25. data/app/models/concerns/bulkrax/export_behavior.rb +2 -2
  26. data/app/models/concerns/bulkrax/has_matchers.rb +44 -13
  27. data/app/models/concerns/bulkrax/import_behavior.rb +40 -5
  28. data/app/models/concerns/bulkrax/importer_exporter_behavior.rb +23 -2
  29. data/app/models/concerns/bulkrax/status_info.rb +4 -4
  30. data/app/parsers/bulkrax/application_parser.rb +67 -84
  31. data/app/parsers/bulkrax/bagit_parser.rb +13 -4
  32. data/app/parsers/bulkrax/csv_parser.rb +170 -64
  33. data/app/parsers/bulkrax/oai_dc_parser.rb +6 -3
  34. data/app/parsers/bulkrax/xml_parser.rb +5 -0
  35. data/app/views/bulkrax/exporters/_form.html.erb +1 -1
  36. data/app/views/bulkrax/exporters/show.html.erb +2 -1
  37. data/app/views/bulkrax/importers/index.html.erb +17 -17
  38. data/app/views/bulkrax/importers/show.html.erb +52 -6
  39. data/config/locales/bulkrax.en.yml +1 -0
  40. data/db/migrate/20190731114016_change_importer_and_exporter_to_polymorphic.rb +5 -1
  41. data/db/migrate/20211004170708_change_bulkrax_statuses_error_message_column_type_to_text.rb +5 -0
  42. data/db/migrate/20211203195233_rename_children_counters_to_relationships.rb +6 -0
  43. data/db/migrate/20211220195027_add_file_set_counters_to_importer_runs.rb +7 -0
  44. data/db/migrate/20220118001339_add_import_attempts_to_entries.rb +5 -0
  45. data/db/migrate/20220119213325_add_work_counters_to_importer_runs.rb +6 -0
  46. data/lib/bulkrax/engine.rb +1 -1
  47. data/lib/bulkrax/version.rb +1 -1
  48. data/lib/bulkrax.rb +9 -17
  49. data/lib/generators/bulkrax/templates/bin/importer +17 -11
  50. data/lib/generators/bulkrax/templates/config/bulkrax_api.yml +3 -1
  51. data/lib/generators/bulkrax/templates/config/initializers/bulkrax.rb +7 -12
  52. metadata +22 -10
  53. data/app/jobs/bulkrax/child_relationships_job.rb +0 -128
@@ -2,31 +2,61 @@
2
2
 
3
3
  require 'csv'
4
4
  module Bulkrax
5
- class CsvParser < ApplicationParser
5
+ class CsvParser < ApplicationParser # rubocop:disable Metrics/ClassLength
6
6
  include ErroredEntries
7
7
  def self.export_supported?
8
8
  true
9
9
  end
10
10
 
11
- def initialize(importerexporter)
12
- @importerexporter = importerexporter
11
+ def records(_opts = {})
12
+ file_for_import = only_updates ? parser_fields['partial_import_file_path'] : import_file_path
13
+ # data for entry does not need source_identifier for csv, because csvs are read sequentially and mapped after raw data is read.
14
+ csv_data = entry_class.read_data(file_for_import)
15
+ importer.parser_fields['total'] = csv_data.count
16
+ importer.save
17
+ @records ||= csv_data.map { |record_data| entry_class.data_for_entry(record_data, nil) }
13
18
  end
14
19
 
15
20
  def collections
16
- # does the CSV contain a collection column?
17
- return [] unless import_fields.include?(:collection)
21
+ ActiveSupport::Deprecation.warn(
22
+ 'Creating Collections using the collection_field_mapping will no longer be supported as of Bulkrax version 3.0.' \
23
+ ' Please configure Bulkrax to use related_parents_field_mapping and related_children_field_mapping instead.'
24
+ )
18
25
  # retrieve a list of unique collections
19
- records.map { |r| r[:collection].split(/\s*[;|]\s*/) if r[:collection].present? }.flatten.compact.uniq
26
+ records.map do |r|
27
+ collections = []
28
+ r[collection_field_mapping].split(/\s*[;|]\s*/).each { |title| collections << { title: title, from_collection_field_mapping: true } } if r[collection_field_mapping].present?
29
+ model_field_mappings.each do |model_mapping|
30
+ collections << r if r[model_mapping.to_sym]&.downcase == 'collection'
31
+ end
32
+ collections
33
+ end.flatten.compact.uniq
20
34
  end
21
35
 
22
36
  def collections_total
23
37
  collections.size
24
38
  end
25
39
 
26
- def records(_opts = {})
27
- file_for_import = only_updates ? parser_fields['partial_import_file_path'] : import_file_path
28
- # data for entry does not need source_identifier for csv, because csvs are read sequentially and mapped after raw data is read.
29
- @records ||= entry_class.read_data(file_for_import).map { |record_data| entry_class.data_for_entry(record_data, nil) }
40
+ def works
41
+ records - collections - file_sets
42
+ end
43
+
44
+ def works_total
45
+ works.size
46
+ end
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
30
60
  end
31
61
 
32
62
  # We could use CsvEntry#fields_from_data(data) but that would mean re-reading the data
@@ -44,8 +74,9 @@ module Bulkrax
44
74
  end
45
75
 
46
76
  def valid_import?
47
- error_alert = "Missing at least one required element, missing element(s) are: #{missing_elements(import_fields).join(', ')}"
48
- raise StandardError, error_alert unless required_elements?(import_fields)
77
+ import_strings = keys_without_numbers(import_fields.map(&:to_s))
78
+ error_alert = "Missing at least one required element, missing element(s) are: #{missing_elements(import_strings).join(', ')}"
79
+ raise StandardError, error_alert unless required_elements?(import_strings)
49
80
 
50
81
  file_paths.is_a?(Array)
51
82
  rescue StandardError => e
@@ -56,26 +87,46 @@ module Bulkrax
56
87
  def create_collections
57
88
  collections.each_with_index do |collection, index|
58
89
  next if collection.blank?
59
- metadata = {
60
- title: [collection],
61
- work_identifier => [collection],
62
- visibility: 'open',
63
- collection_type_gid: Hyrax::CollectionType.find_or_create_default_collection_type.gid
64
- }
65
- new_entry = find_or_create_entry(collection_entry_class, collection, 'Bulkrax::Importer', metadata)
66
- ImportWorkCollectionJob.perform_now(new_entry.id, current_run.id)
67
- increment_counters(index, true)
90
+ break if records.find_index(collection).present? && limit_reached?(limit, records.find_index(collection))
91
+ ActiveSupport::Deprecation.warn(
92
+ 'Creating Collections using the collection_field_mapping will no longer be supported as of Bulkrax version 3.0.' \
93
+ ' Please configure Bulkrax to use related_parents_field_mapping and related_children_field_mapping instead.'
94
+ )
95
+
96
+ ## BEGIN
97
+ # Add required metadata to collections being imported using the collection_field_mapping, which only have a :title
98
+ # TODO: Remove once collection_field_mapping is removed
99
+ metadata = if collection.delete(:from_collection_field_mapping)
100
+ uci = unique_collection_identifier(collection)
101
+ {
102
+ title: [collection[:title]],
103
+ work_identifier => uci,
104
+ source_identifier => uci,
105
+ visibility: 'open',
106
+ collection_type_gid: ::Hyrax::CollectionType.find_or_create_default_collection_type.gid
107
+ }
108
+ end
109
+ collection_hash = metadata.presence || collection
110
+ ## END
111
+
112
+ new_entry = find_or_create_entry(collection_entry_class, collection_hash[source_identifier], 'Bulkrax::Importer', collection_hash)
113
+ # TODO: add support for :delete option
114
+ ImportCollectionJob.perform_now(new_entry.id, current_run.id)
115
+ increment_counters(index, collection: true)
68
116
  end
117
+ importer.record_status
118
+ rescue StandardError => e
119
+ status_info(e)
69
120
  end
70
121
 
71
122
  def create_works
72
- records.each_with_index do |record, index|
73
- next unless record_has_source_identifier(record, index)
74
- break if limit_reached?(limit, index)
123
+ works.each_with_index do |work, index|
124
+ next unless record_has_source_identifier(work, records.find_index(work))
125
+ break if limit_reached?(limit, records.find_index(work))
75
126
 
76
- seen[record[source_identifier]] = true
77
- new_entry = find_or_create_entry(entry_class, record[source_identifier], 'Bulkrax::Importer', record.to_h.compact)
78
- if record[:delete].present?
127
+ seen[work[source_identifier]] = true
128
+ new_entry = find_or_create_entry(entry_class, work[source_identifier], 'Bulkrax::Importer', work.to_h)
129
+ if work[:delete].present?
79
130
  DeleteWorkJob.send(perform_method, new_entry, current_run)
80
131
  else
81
132
  ImportWorkJob.send(perform_method, new_entry.id, current_run.id)
@@ -87,6 +138,20 @@ module Bulkrax
87
138
  status_info(e)
88
139
  end
89
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
+
90
155
  def write_partial_import_file(file)
91
156
  import_filename = import_file_path.split('/').last
92
157
  partial_import_filename = "#{File.basename(import_filename, '.csv')}_corrected_entries.csv"
@@ -99,10 +164,6 @@ module Bulkrax
99
164
  path
100
165
  end
101
166
 
102
- def create_parent_child_relationships
103
- super
104
- end
105
-
106
167
  def extra_filters
107
168
  output = ""
108
169
  if importerexporter.start_date.present?
@@ -117,6 +178,8 @@ module Bulkrax
117
178
 
118
179
  def current_work_ids
119
180
  case importerexporter.export_from
181
+ when 'all'
182
+ ActiveFedora::SolrService.query("has_model_ssim:(#{Hyrax.config.curation_concerns.join(' OR ')}) #{extra_filters}", rows: 2_147_483_647).map(&:id)
120
183
  when 'collection'
121
184
  ActiveFedora::SolrService.query("member_of_collection_ids_ssim:#{importerexporter.export_source + extra_filters}", rows: 2_000_000_000).map(&:id)
122
185
  when 'worktype'
@@ -126,9 +189,16 @@ module Bulkrax
126
189
  complete_statuses = Bulkrax::Status.latest_by_statusable
127
190
  .includes(:statusable)
128
191
  .where('bulkrax_statuses.statusable_id IN (?) AND bulkrax_statuses.statusable_type = ? AND status_message = ?', entry_ids, 'Bulkrax::Entry', 'Complete')
129
- complete_entry_identifiers = complete_statuses.map { |s| s.statusable&.identifier }
130
192
 
131
- ActiveFedora::SolrService.query("#{work_identifier}_tesim:(#{complete_entry_identifiers.join(' OR ')})#{extra_filters}", rows: 2_000_000_000).map(&:id)
193
+ complete_entry_identifiers = complete_statuses.map { |s| s.statusable&.identifier&.gsub(':', '\:') }
194
+ extra_filters = extra_filters.presence || '*:*'
195
+
196
+ ActiveFedora::SolrService.get(
197
+ extra_filters.to_s,
198
+ fq: "#{work_identifier}_sim:(#{complete_entry_identifiers.join(' OR ')})",
199
+ fl: 'id',
200
+ rows: 2_000_000_000
201
+ )['response']['docs'].map { |obj| obj['id'] }
132
202
  end
133
203
  end
134
204
 
@@ -136,12 +206,18 @@ module Bulkrax
136
206
  current_work_ids.each_with_index do |wid, index|
137
207
  break if limit_reached?(limit, index)
138
208
  new_entry = find_or_create_entry(entry_class, wid, 'Bulkrax::Exporter')
139
- Bulkrax::ExportWorkJob.perform_now(new_entry.id, current_run.id)
209
+ begin
210
+ entry = Bulkrax::ExportWorkJob.perform_now(new_entry.id, current_run.id)
211
+ rescue => e
212
+ Rails.logger.info("#{e.message} was detected during export")
213
+ end
214
+ self.headers |= entry.parsed_metadata.keys if entry
140
215
  end
141
216
  end
142
217
  alias create_from_collection create_new_entries
143
218
  alias create_from_importer create_new_entries
144
219
  alias create_from_worktype create_new_entries
220
+ alias create_from_all create_new_entries
145
221
 
146
222
  def entry_class
147
223
  CsvEntry
@@ -151,22 +227,18 @@ module Bulkrax
151
227
  CsvCollectionEntry
152
228
  end
153
229
 
230
+ def file_set_entry_class
231
+ CsvFileSetEntry
232
+ end
233
+
154
234
  # See https://stackoverflow.com/questions/2650517/count-the-number-of-lines-in-a-file-without-reading-entire-file-into-memory
155
235
  # Changed to grep as wc -l counts blank lines, and ignores the final unescaped line (which may or may not contain data)
156
236
  def total
157
- if importer?
158
- return @total if @total&.positive?
159
- # windows enocded
160
- @total = `grep -c ^M #{real_import_file_path}`.to_i - 1
161
- # unix encoded
162
- @total = `grep -vc ^$ #{real_import_file_path}`.to_i - 1 if @total < 1
163
- elsif exporter?
164
- @total = importerexporter.entries.count
165
- else
166
- @total = 0
167
- end
168
- return @total
169
- rescue StandardErrorr
237
+ @total = importer.parser_fields['total'] || 0 if importer?
238
+ @total = importerexporter.entries.count if exporter?
239
+
240
+ return @total || 0
241
+ rescue StandardError
170
242
  @total = 0
171
243
  end
172
244
 
@@ -201,32 +273,58 @@ module Bulkrax
201
273
  end
202
274
  end
203
275
 
204
- def key_allowed(key)
205
- !Bulkrax.reserved_properties.include?(key) &&
206
- new_entry(entry_class, 'Bulkrax::Exporter').field_supported?(key) &&
276
+ def export_key_allowed(key)
277
+ new_entry(entry_class, 'Bulkrax::Exporter').field_supported?(key) &&
207
278
  key != source_identifier.to_s
208
279
  end
209
280
 
210
281
  # All possible column names
211
282
  def export_headers
212
- headers = ['id']
213
- headers << source_identifier.to_s
214
- headers << 'model'
215
- headers << 'collections'
216
- importerexporter.mapping.each_key { |key| headers << key if key_allowed(key) }
217
- headers << 'file'
283
+ headers = sort_headers(self.headers)
284
+
285
+ # we don't want access_control_id exported and we want file at the end
286
+ headers.delete('access_control_id') if headers.include?('access_control_id')
287
+
288
+ # add the headers below at the beginning or end to maintain the preexisting export behavior
289
+ headers.prepend('model')
290
+ headers.prepend(source_identifier.to_s)
291
+ headers.prepend('id')
292
+
218
293
  headers.uniq
219
294
  end
220
295
 
296
+ def object_names
297
+ return @object_names if @object_names
298
+
299
+ @object_names = mapping.values.map { |value| value['object'] }
300
+ @object_names.uniq!.delete(nil)
301
+
302
+ @object_names
303
+ end
304
+
305
+ def sort_headers(headers)
306
+ # converting headers like creator_name_1 to creator_1_name so they get sorted by numerical order
307
+ # while keeping objects grouped together
308
+ headers.sort_by do |item|
309
+ number = item.match(/\d+/)&.[](0) || 0.to_s
310
+ sort_number = number.rjust(4, "0")
311
+ object_prefix = object_names.detect { |o| item.match(/^#{o}/) } || item
312
+ remainder = item.gsub(/^#{object_prefix}_/, '').gsub(/_#{number}/, '')
313
+ "#{object_prefix}_#{sort_number}_#{remainder}"
314
+ end
315
+ end
316
+
221
317
  # in the parser as it is specific to the format
222
318
  def setup_export_file
223
- File.join(importerexporter.exporter_export_path, 'export.csv')
319
+ File.join(importerexporter.exporter_export_path, "export_#{importerexporter.export_source}_from_#{importerexporter.export_from}.csv")
224
320
  end
225
321
 
226
322
  # Retrieve file paths for [:file] mapping in records
227
323
  # and check all listed files exist.
228
324
  def file_paths
229
325
  raise StandardError, 'No records were found' if records.blank?
326
+ return [] if importerexporter.metadata_only?
327
+
230
328
  @file_paths ||= records.map do |r|
231
329
  file_mapping = Bulkrax.field_mappings.dig(self.class.to_s, 'file', :from)&.first&.to_sym || :file
232
330
  next if r[file_mapping].blank?
@@ -245,23 +343,31 @@ module Bulkrax
245
343
  # Retrieve the path where we expect to find the files
246
344
  def path_to_files
247
345
  @path_to_files ||= File.join(
248
- File.file?(import_file_path) ? File.dirname(import_file_path) : import_file_path,
346
+ zip? ? importer_unzip_path : File.dirname(import_file_path),
249
347
  'files'
250
348
  )
251
349
  end
252
350
 
253
351
  private
254
352
 
353
+ def unique_collection_identifier(collection_hash)
354
+ entry_uid = collection_hash[source_identifier]
355
+ entry_uid ||= if Bulkrax.fill_in_blank_source_identifiers.present?
356
+ Bulkrax.fill_in_blank_source_identifiers.call(self, records.find_index(collection_hash))
357
+ else
358
+ collection_hash[:title].split(/\s*[;|]\s*/).first
359
+ end
360
+
361
+ entry_uid
362
+ end
363
+
255
364
  # Override to return the first CSV in the path, if a zip file is supplied
256
365
  # We expect a single CSV at the top level of the zip in the CSVParser
257
366
  # but we are willing to go look for it if need be
258
367
  def real_import_file_path
259
- if file? && zip?
260
- unzip(parser_fields['import_file_path'])
261
- return Dir["#{importer_unzip_path}/**/*.csv"].first
262
- else
263
- parser_fields['import_file_path']
264
- end
368
+ return Dir["#{importer_unzip_path}/**/*.csv"].first if file? && zip?
369
+
370
+ parser_fields['import_file_path']
265
371
  end
266
372
  end
267
373
  end
@@ -75,8 +75,8 @@ module Bulkrax
75
75
 
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
- ImportWorkCollectionJob.perform_now(new_entry.id, importerexporter.current_run.id)
79
- increment_counters(index, true)
78
+ ImportCollectionJob.perform_now(new_entry.id, importerexporter.current_run.id)
79
+ increment_counters(index, collection: true)
80
80
  end
81
81
  end
82
82
 
@@ -119,7 +119,10 @@ module Bulkrax
119
119
  end
120
120
  end
121
121
 
122
- def create_parent_child_relationships; end
122
+ # TODO: change to differentiate between collection and work records when adding ability to import collection metadata
123
+ def works_total
124
+ total
125
+ end
123
126
 
124
127
  def total
125
128
  @total ||= records(quick: true).doc.find(".//resumptionToken").to_a.first.attributes["completeListSize"].to_i
@@ -12,6 +12,11 @@ module Bulkrax
12
12
  # @todo not yet supported
13
13
  def create_collections; end
14
14
 
15
+ # TODO: change to differentiate between collection and work records when adding ability to import collection metadata
16
+ def works_total
17
+ total
18
+ end
19
+
15
20
  # @todo not yet supported
16
21
  def import_fields; end
17
22
 
@@ -32,7 +32,7 @@
32
32
  prompt: 'Select from the list',
33
33
  label_html: { class: 'importer export-source-option hidden' },
34
34
  input_html: { class: 'importer export-source-option hidden' },
35
- collection: form.object.importers_list %>
35
+ collection: form.object.importers_list.sort %>
36
36
 
37
37
  <%= form.input :export_source_collection,
38
38
  prompt: 'Start typing ...',
@@ -57,8 +57,9 @@
57
57
  <strong><%= t('bulkrax.exporter.labels.limit') %>:</strong>
58
58
  <%= @exporter.limit %>
59
59
  </p>
60
+ <%= render partial: 'bulkrax/shared/bulkrax_errors', locals: {item: @exporter} %>
60
61
 
61
- <%= render partial: 'bulkrax/shared/bulkrax_field_mapping', locals: {item: @exporter} %>
62
+ <%= render partial: 'bulkrax/shared/bulkrax_field_mapping', locals: {item: @exporter} %>
62
63
 
63
64
  <%# Currently, no parser-specific fields exist on Exporter,
64
65
  thus there's no real reason to always show this field %>
@@ -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>
@@ -31,23 +32,22 @@
31
32
  </thead>
32
33
  <tbody>
33
34
  <% @importers.each do |importer| %>
34
- <tr>
35
- <th scope="row"><%= link_to importer.name, importer_path(importer) %></th>
36
- <td>
37
- <%= importer.status %>
38
- </td>
39
- <td><%= importer.last_imported_at.strftime("%b %d, %Y") if importer.last_imported_at %></td>
40
- <td><%= importer.next_import_at.strftime("%b %d, %Y") if importer.next_import_at %></td>
41
- <td><%= importer.importer_runs.last&.enqueued_records %></td>
42
- <td><%= (importer.importer_runs.last&.processed_collections || 0) + (importer.importer_runs.last&.processed_records || 0) %></td>
43
- <td><%= (importer.importer_runs.last&.failed_collections || 0) + (importer.importer_runs.last&.failed_records || 0) %></td>
44
- <td><%= importer.importer_runs.last&.deleted_records %></td>
45
- <td><%= importer.importer_runs.last&.total_collection_entries %></td>
46
- <td><%= importer.importer_runs.last&.total_work_entries %></td>
47
- <td><%= link_to raw('<span class="glyphicon glyphicon-info-sign"></span>'), importer_path(importer) %></td>
48
- <td><%= link_to raw('<span class="glyphicon glyphicon-pencil"></span>'), edit_importer_path(importer) %></td>
49
- <td><%= link_to raw('<span class="glyphicon glyphicon-remove"></span>'), importer, method: :delete, data: { confirm: 'Are you sure?' } %></td>
50
- </tr>
35
+ <tr>
36
+ <th scope="row"><%= link_to importer.name, importer_path(importer) %></th>
37
+ <td><%= importer.status %></td>
38
+ <td><%= importer.last_imported_at.strftime("%b %d, %Y") if importer.last_imported_at %></td>
39
+ <td><%= importer.next_import_at.strftime("%b %d, %Y") if importer.next_import_at %></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>
47
+ <td><%= link_to raw('<span class="glyphicon glyphicon-info-sign"></span>'), importer_path(importer) %></td>
48
+ <td><%= link_to raw('<span class="glyphicon glyphicon-pencil"></span>'), edit_importer_path(importer) %></td>
49
+ <td><%= link_to raw('<span class="glyphicon glyphicon-remove"></span>'), importer, method: :delete, data: { confirm: 'Are you sure?' } %></td>
50
+ </tr>
51
51
  <% end %>
52
52
  </tbody>
53
53
  </table>
@@ -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">
@@ -101,8 +107,10 @@
101
107
  <td><%= e.id %></td>
102
108
  <% if e.status == "Complete" %>
103
109
  <td><span class="glyphicon glyphicon-ok" style="color: green;"></span> <%= e.status %></td>
110
+ <% elsif e.status == "Pending" %>
111
+ <td><span class="glyphicon glyphicon-option-horizontal" style="color: blue;"></span> <%= e.status %></td>
104
112
  <% else %>
105
- <td><span class="glyphicon glyphicon-remove" style="color: red;"></span> <%= e.status %></td>
113
+ <td><span class="glyphicon glyphicon-remove" style="color: <%= e.status == 'Deleted' ? 'green' : 'red' %>;"></span> <%= e.status %></td>
106
114
  <% end %>
107
115
  <% if e.last_error.present? %>
108
116
  <td><%= link_to e.last_error.dig("error_class"), bulkrax.importer_entry_path(@importer.id, e.id) %></td>
@@ -137,8 +145,10 @@
137
145
  <td><%= e.id %></td>
138
146
  <% if e.status == "Complete" %>
139
147
  <td><span class="glyphicon glyphicon-ok" style="color: green;"></span> <%= e.status %></td>
148
+ <% elsif e.status == "Pending" %>
149
+ <td><span class="glyphicon glyphicon-option-horizontal" style="color: blue;"></span> <%= e.status %></td>
140
150
  <% else %>
141
- <td><span class="glyphicon glyphicon-remove" style="color: red;"></span> <%= e.status %></td>
151
+ <td><span class="glyphicon glyphicon-remove" style="color: <%= e.status == 'Deleted' ? 'green' : 'red' %>;"></span> <%= e.status %></td>
142
152
  <% end %>
143
153
  <% if e.last_error.present? %>
144
154
  <td><%= link_to e.last_error.dig("error_class"), bulkrax.importer_entry_path(@importer.id, e.id) %></td>
@@ -154,6 +164,42 @@
154
164
  <%= page_entries_info(@collection_entries) %><br />
155
165
  <%= paginate(@collection_entries, theme: 'blacklight', param_name: :collections_entries_page, params: {anchor: 'collection-entries'}) %>
156
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>
157
203
  </div>
158
204
  </div>
159
205
 
@@ -6,6 +6,7 @@ en:
6
6
  importers: Importers
7
7
  exporter:
8
8
  labels:
9
+ all: All
9
10
  collection: Collection
10
11
  export_format: Export Format
11
12
  export_from: Export From
@@ -6,7 +6,11 @@ end
6
6
 
7
7
  class ChangeImporterAndExporterToPolymorphic < ActiveRecord::Migration[5.1]
8
8
  def change
9
- rename_column :bulkrax_entries, :importer_id, :importerexporter_id if column_exists?(:bulkrax_entries, :importer_id)
9
+ if column_exists?(:bulkrax_entries, :importer_id)
10
+ remove_foreign_key :bulkrax_entries, column: :importer_id
11
+ remove_index :bulkrax_entries, :importer_id
12
+ rename_column :bulkrax_entries, :importer_id, :importerexporter_id
13
+ end
10
14
  add_column :bulkrax_entries, :importerexporter_type, :string, after: :id, default: 'Bulkrax::Importer' unless column_exists?(:bulkrax_entries, :importerexporter_type)
11
15
  end
12
16
  end
@@ -0,0 +1,5 @@
1
+ class ChangeBulkraxStatusesErrorMessageColumnTypeToText < ActiveRecord::Migration[5.1]
2
+ def change
3
+ change_column :bulkrax_statuses, :error_message, :text
4
+ end
5
+ end
@@ -0,0 +1,6 @@
1
+ class RenameChildrenCountersToRelationships < ActiveRecord::Migration[5.2]
2
+ def change
3
+ rename_column :bulkrax_importer_runs, :processed_children, :processed_relationships
4
+ rename_column :bulkrax_importer_runs, :failed_children, :failed_relationships
5
+ end
6
+ end
@@ -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
@@ -25,7 +25,7 @@ module Bulkrax
25
25
  config.after_initialize do
26
26
  my_engine_root = Bulkrax::Engine.root.to_s
27
27
  paths = ActionController::Base.view_paths.collect(&:to_s)
28
- hyrax_path = paths.detect { |path| path.match('/hyrax-') }
28
+ hyrax_path = paths.detect { |path| path.match(/\/hyrax-[\d\.]+.*/) }
29
29
  paths = if hyrax_path
30
30
  paths.insert(paths.index(hyrax_path), my_engine_root + '/app/views')
31
31
  else
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Bulkrax
4
- VERSION = '1.0.2'
4
+ VERSION = '2.1.0'
5
5
  end