bulkrax 1.0.2 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
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