blacklight-spotlight 3.0.0.rc3 → 3.0.0.rc4

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 (81) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/javascripts/spotlight/admin/reindex_monitor.js +1 -0
  3. data/app/assets/stylesheets/spotlight/browse_group_categories_block.scss +23 -0
  4. data/app/controllers/spotlight/catalog_controller.rb +4 -1
  5. data/app/controllers/spotlight/dashboards_controller.rb +1 -1
  6. data/app/controllers/spotlight/exhibits_controller.rb +1 -1
  7. data/app/helpers/spotlight/application_helper.rb +19 -0
  8. data/app/helpers/spotlight/pages_helper.rb +1 -1
  9. data/app/jobs/concerns/spotlight/job_tracking.rb +47 -0
  10. data/app/jobs/concerns/spotlight/limit_concurrency.rb +33 -0
  11. data/app/jobs/spotlight/add_uploads_from_csv.rb +6 -3
  12. data/app/jobs/spotlight/application_job.rb +8 -0
  13. data/app/jobs/spotlight/cleanup_job_trackers_job.rb +13 -0
  14. data/app/jobs/spotlight/default_thumbnail_job.rb +1 -3
  15. data/app/jobs/spotlight/reindex_exhibit_job.rb +36 -0
  16. data/app/jobs/spotlight/reindex_job.rb +49 -41
  17. data/app/jobs/spotlight/rename_sidecar_field_job.rb +2 -2
  18. data/app/jobs/spotlight/update_job_trackers_job.rb +20 -0
  19. data/app/models/concerns/spotlight/user.rb +2 -1
  20. data/app/models/spotlight/event.rb +13 -0
  21. data/app/models/spotlight/exhibit.rb +4 -14
  22. data/app/models/spotlight/job_tracker.rb +105 -0
  23. data/app/models/spotlight/reindex_progress.rb +44 -27
  24. data/app/models/spotlight/resource.rb +24 -58
  25. data/app/models/spotlight/resources/iiif_harvester.rb +10 -1
  26. data/app/models/spotlight/resources/iiif_manifest.rb +3 -1
  27. data/app/models/spotlight/resources/iiif_service.rb +1 -1
  28. data/app/models/spotlight/resources/json_upload.rb +12 -0
  29. data/app/models/spotlight/resources/upload.rb +25 -2
  30. data/app/models/spotlight/solr_document_sidecar.rb +2 -1
  31. data/app/services/spotlight/etl.rb +7 -0
  32. data/app/services/spotlight/etl/context.rb +52 -0
  33. data/app/services/spotlight/etl/executor.rb +194 -0
  34. data/app/services/spotlight/etl/loaders.rb +12 -0
  35. data/app/services/spotlight/etl/pipeline.rb +81 -0
  36. data/app/services/spotlight/etl/solr_loader.rb +96 -0
  37. data/app/services/spotlight/etl/sources.rb +25 -0
  38. data/app/services/spotlight/etl/step.rb +82 -0
  39. data/app/services/spotlight/etl/transforms.rb +64 -0
  40. data/app/services/spotlight/validity_checker.rb +5 -5
  41. data/app/views/spotlight/dashboards/_reindexing_activity.html.erb +6 -6
  42. data/app/views/spotlight/shared/_locale_picker.html.erb +1 -1
  43. data/app/views/spotlight/sir_trevor/blocks/_browse_group_categories_block.html.erb +4 -3
  44. data/config/locales/spotlight.ar.yml +11 -1
  45. data/config/locales/spotlight.en.yml +3 -2
  46. data/db/migrate/20210122082032_create_job_trackers.rb +22 -0
  47. data/db/migrate/20210126123041_create_events.rb +15 -0
  48. data/lib/generators/spotlight/scaffold_resource_generator.rb +5 -13
  49. data/lib/spotlight/engine.rb +8 -1
  50. data/lib/spotlight/version.rb +1 -1
  51. data/spec/controllers/spotlight/catalog_controller_spec.rb +3 -1
  52. data/spec/examples.txt +1448 -1437
  53. data/spec/factories/job_trackers.rb +9 -0
  54. data/spec/features/add_items_spec.rb +9 -4
  55. data/spec/features/javascript/reindex_monitor_spec.rb +1 -1
  56. data/spec/features/site_users_management_spec.rb +4 -4
  57. data/spec/helpers/spotlight/pages_helper_spec.rb +8 -0
  58. data/spec/jobs/spotlight/reindex_exhibit_job_spec.rb +43 -0
  59. data/spec/jobs/spotlight/reindex_job_spec.rb +30 -59
  60. data/spec/models/spotlight/exhibit_spec.rb +3 -57
  61. data/spec/models/spotlight/reindex_progress_spec.rb +89 -87
  62. data/spec/models/spotlight/resource_spec.rb +69 -90
  63. data/spec/models/spotlight/resources/iiif_harvester_spec.rb +9 -10
  64. data/spec/models/spotlight/solr_document_sidecar_spec.rb +1 -0
  65. data/spec/services/spotlight/etl/context_spec.rb +66 -0
  66. data/spec/services/spotlight/etl/executor_spec.rb +149 -0
  67. data/spec/services/spotlight/etl/pipeline_spec.rb +22 -0
  68. data/spec/services/spotlight/etl/solr_loader_spec.rb +76 -0
  69. data/spec/services/spotlight/etl/step_spec.rb +70 -0
  70. data/spec/spec_helper.rb +2 -5
  71. data/spec/views/spotlight/dashboards/_reindexing_activity.html.erb_spec.rb +22 -19
  72. metadata +55 -15
  73. data/app/models/concerns/spotlight/resources/open_graph.rb +0 -36
  74. data/app/models/spotlight/reindexing_log_entry.rb +0 -42
  75. data/app/services/spotlight/resources/iiif_builder.rb +0 -19
  76. data/app/services/spotlight/solr_document_builder.rb +0 -77
  77. data/app/services/spotlight/upload_solr_document_builder.rb +0 -57
  78. data/spec/factories/reindexing_log_entries.rb +0 -54
  79. data/spec/models/spotlight/reindexing_log_entry_spec.rb +0 -129
  80. data/spec/models/spotlight/resources/open_graph_spec.rb +0 -65
  81. data/spec/services/spotlight/solr_document_builder_spec.rb +0 -66
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a8fb1d422302a15b42343a0f7afb8c545869d0c872e355762a7cea998469b12b
4
- data.tar.gz: df63403ced5aa975097224a0e298562d216c427fcab43aba7640eeee18dc8ea9
3
+ metadata.gz: 55231336b3b4a0eae1abdd68bbeb8ede968d5ffbe4359f29f83a3a27124a4b05
4
+ data.tar.gz: b2f2bfc6230b5081493484393a26670961486217b64b7a96b9659979eab64381
5
5
  SHA512:
6
- metadata.gz: 3c6261c2edc4b04d331d0e3961f0a88ce15599314add02061475349e9c86cb5dcb7c1cb5bd9d89100266bfcbab4449160b7b662d66aece1d73e5a721d6d35914
7
- data.tar.gz: 96fad663f5370e18de8a345ed3bbd9f7b6e7b774a76ca2d16342d66bdfa2bde41fe3eaef70b290bde32e35828f50bc3c71f0f7f75df20e8029c61fff29193ab1
6
+ metadata.gz: c3d2a9d1203e0950c79844e4fad0b2083e3cc8d1b63a357ce47a2760ac48b13ec9c2f4e2f0890add8a533d71f61bf7a96a684b74710c564b3e17d919635b592a
7
+ data.tar.gz: 842054ed3d1c5d46c5c6f9bb3180efdf83feee8f1bf44ae607865d6bea2f9d4589e1b19990cbb53a65ba4249fd5794dca213830be71b055a28b69e188b29dde7
@@ -84,6 +84,7 @@ Spotlight.onLoad(function() {
84
84
  }
85
85
 
86
86
  function calculatePercentage(data) {
87
+ if (data.total == 0) return 0;
87
88
  return Math.floor((data.completed / data.total) * 100);
88
89
  }
89
90
 
@@ -4,6 +4,24 @@
4
4
  padding-bottom: $spacer * .75;
5
5
  padding-top: $spacer * .75;
6
6
 
7
+ .browse-categories .browse-category {
8
+ .category-caption {
9
+ z-index: 100;
10
+ }
11
+
12
+ .hover-overlay {
13
+ height: 100%;
14
+ position: absolute;
15
+ width: 100%;
16
+ }
17
+
18
+ &:hover {
19
+ .hover-overlay {
20
+ background: linear-gradient(to bottom, rgba(0, 0, 0, 0.2), rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0.7));
21
+ }
22
+ }
23
+ }
24
+
7
25
  .spotlight-flexbox.browse-categories .box {
8
26
  display: flex;
9
27
  }
@@ -66,4 +84,9 @@
66
84
  .browse-group-categories-block .blacklight-icon-arrow-alt-circle-left, .blacklight-icon-arrow-alt-circle-right{
67
85
  transform: rotate(180deg);
68
86
  }
87
+ // Hack to override overflow issue not fixed in RTL upstream in tiny-slider
88
+ .tns-visually-hidden {
89
+ left: 0;
90
+ right: -10000em;
91
+ }
69
92
  }
@@ -31,7 +31,10 @@ module Spotlight
31
31
  blacklight_config.view.admin_table.document_actions = []
32
32
  blacklight_config.track_search_session = false
33
33
 
34
- blacklight_config.add_sort_field :timestamp, sort: "#{blacklight_config.index.timestamp_field} desc" unless blacklight_config.sort_fields.key? :timestamp
34
+ unless blacklight_config.sort_fields.key? :timestamp
35
+ blacklight_config.add_sort_field :timestamp, default: true,
36
+ sort: "#{blacklight_config.index.timestamp_field} desc"
37
+ end
35
38
  end
36
39
 
37
40
  before_action only: :edit do
@@ -22,7 +22,7 @@ module Spotlight
22
22
 
23
23
  @pages = @exhibit.pages.recent.limit(5)
24
24
  @solr_documents = load_recent_solr_documents 5
25
- @recent_reindexing = @exhibit.reindexing_log_entries.recent
25
+ @recent_reindexing = @exhibit.job_trackers.recent
26
26
 
27
27
  attach_dashboard_breadcrumbs
28
28
  end
@@ -27,7 +27,7 @@ module Spotlight
27
27
  end
28
28
 
29
29
  def process_import
30
- if @exhibit.import(JSON.parse(import_exhibit_params.read)) && @exhibit.reindex_later
30
+ if @exhibit.import(JSON.parse(import_exhibit_params.read)) && @exhibit.reindex_later(current_user)
31
31
  redirect_to spotlight.exhibit_dashboard_path(@exhibit), notice: t(:'helpers.submit.exhibit.updated', model: @exhibit.class.model_name.human.downcase)
32
32
  else
33
33
  render action: :import
@@ -30,6 +30,25 @@ module Spotlight
30
30
  current_site.title if current_site.title.present?
31
31
  end
32
32
 
33
+ # Returns the url for the current page in the new locale. This may be
34
+ # overridden in downstream applications where our naive use of `url_for`
35
+ # is insufficient to generate the expected routes
36
+ def current_page_for_locale(locale)
37
+ initial_exception = nil
38
+
39
+ ([self] + additional_locale_routing_scopes).each do |scope|
40
+ return scope.public_send(:url_for, params.to_unsafe_h.merge(locale: locale))
41
+ rescue ActionController::UrlGenerationError => e
42
+ initial_exception ||= e
43
+ end
44
+
45
+ raise initial_exception
46
+ end
47
+
48
+ def additional_locale_routing_scopes
49
+ [spotlight, main_app]
50
+ end
51
+
33
52
  # Can search for named routes directly in the main app, omitting
34
53
  # the "main_app." prefix
35
54
  def method_missing(method, *args, &block)
@@ -21,7 +21,7 @@ module Spotlight
21
21
  # a more complete markdown rendered
22
22
  def sir_trevor_markdown(text)
23
23
  clean_text = if text
24
- text.gsub('<br>', "\n").gsub('<p>', '').gsub('</p>', "\n\n")
24
+ text.gsub('<br>', "\n\n").gsub('<p>', '').gsub('</p>', "\n\n")
25
25
  else
26
26
  ''
27
27
  end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Spotlight
4
+ # Job status tracking
5
+ module JobTracking
6
+ extend ActiveSupport::Concern
7
+ include ActiveJob::Status
8
+
9
+ def self.with_job_tracking
10
+ before_perform :find_or_initialize_job_tracker
11
+ after_perform :finalize_job_tracker
12
+ end
13
+
14
+ def job_tracker
15
+ @job_tracker ||= find_or_initialize_job_tracker
16
+ end
17
+
18
+ private
19
+
20
+ def find_or_initialize_job_tracker
21
+ JobTracker.find_or_create_by(job_id: job_id) do |tracker|
22
+ tracker.job_class = self.class.name
23
+ tracker.status = 'enqueued'
24
+ update_job_tracker_properties(tracker)
25
+ end
26
+ end
27
+
28
+ def finalize_job_tracker
29
+ job_tracker.update(status: 'completed') if job_tracker.status == 'enqueued'
30
+ end
31
+
32
+ def update_job_tracker_properties(tracker)
33
+ tracker.resource = job_tracking_resource
34
+ tracker.on = reports_on_resource || tracker.resource
35
+
36
+ tracker.user = arguments.last[:user] if arguments.last.is_a?(Hash)
37
+ end
38
+
39
+ def job_tracking_resource
40
+ arguments.first
41
+ end
42
+
43
+ def reports_on_resource
44
+ arguments.last[:reports_on] if arguments.last.is_a?(Hash)
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Spotlight
4
+ # Job status tracking
5
+ module LimitConcurrency
6
+ extend ActiveSupport::Concern
7
+
8
+ VALIDITY_TOKEN_PARAMETER = 'validity_token'
9
+
10
+ included do
11
+ # The validity checker is a seam for implementations to expire unnecessary
12
+ # indexing tasks if it becomes redundant while waiting in the job queue.
13
+ class_attribute :validity_checker, default: Spotlight::ValidityChecker.new
14
+
15
+ before_enqueue do |job|
16
+ token = job.arguments.last[VALIDITY_TOKEN_PARAMETER] if job.arguments.last.is_a?(Hash)
17
+ token ||= validity_checker.mint(job)
18
+
19
+ job.arguments << {} unless job.arguments.last.is_a? Hash
20
+ job.arguments.last[VALIDITY_TOKEN_PARAMETER] = token
21
+ end
22
+
23
+ before_perform do |job|
24
+ next unless job.arguments.last.is_a?(Hash)
25
+
26
+ token = job.arguments.last.delete(VALIDITY_TOKEN_PARAMETER)
27
+ throw(:abort) unless token.nil? || validity_checker.check(job, validity_token: token)
28
+
29
+ job.arguments.pop if job.arguments.last.empty?
30
+ end
31
+ end
32
+ end
33
+ end
@@ -3,12 +3,11 @@
3
3
  module Spotlight
4
4
  ##
5
5
  # Process a CSV upload into new Spotlight::Resource::Upload objects
6
- class AddUploadsFromCsv < ActiveJob::Base
6
+ class AddUploadsFromCsv < Spotlight::ApplicationJob
7
+ include Spotlight::JobTracking
7
8
  attr_reader :count
8
9
  attr_reader :errors
9
10
 
10
- queue_as :default
11
-
12
11
  after_perform do |job|
13
12
  csv_data, exhibit, user = job.arguments
14
13
  Spotlight::IndexingCompleteMailer.documents_indexed(
@@ -59,5 +58,9 @@ module Spotlight
59
58
  end.compact.to_h
60
59
  end.compact
61
60
  end
61
+
62
+ def job_tracking_resource
63
+ arguments[1]
64
+ end
62
65
  end
63
66
  end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Spotlight
4
+ # :nodoc:
5
+ class ApplicationJob < ActiveJob::Base
6
+ queue_as :default
7
+ end
8
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Spotlight
4
+ ###
5
+ # Calls the #set_default_thumbnail method
6
+ # on the object passed in and calls save
7
+ ###
8
+ class CleanupJobTrackersJob < Spotlight::ApplicationJob
9
+ def perform
10
+ Spotlight::JobTracker.where(status: 'completed', updated_at: Time.zone.at(0)...Spotlight::Engine.config.reindex_progress_window.minutes.ago).delete_all
11
+ end
12
+ end
13
+ end
@@ -5,9 +5,7 @@ module Spotlight
5
5
  # Calls the #set_default_thumbnail method
6
6
  # on the object passed in and calls save
7
7
  ###
8
- class DefaultThumbnailJob < ActiveJob::Base
9
- queue_as :default
10
-
8
+ class DefaultThumbnailJob < Spotlight::ApplicationJob
11
9
  def perform(thumbnailable)
12
10
  thumbnailable.set_default_thumbnail
13
11
  thumbnailable.save
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Spotlight
4
+ ##
5
+ # Reindex an exhibit by parallelizing resource indexing into multiple batches of reindex jobs
6
+ class ReindexExhibitJob < Spotlight::ApplicationJob
7
+ include Spotlight::JobTracking
8
+ include Spotlight::LimitConcurrency
9
+
10
+ def perform(exhibit, batch_size: Spotlight::Engine.config.reindexing_batch_size, batch_count: Spotlight::Engine.config.reindexing_batch_count, **)
11
+ job_tracker.update(status: 'in_progress')
12
+
13
+ count = exhibit.resources.count
14
+
15
+ # Use the provided batch size, or calculate a reasonable default
16
+ batch_count = (count.to_f / batch_size).ceil if batch_size
17
+ batch_count ||= 1 + Math.log(count).round # e.g. 10 => 3, 100 => 6, 1000 => 8
18
+
19
+ return Spotlight::ReindexJob.perform_now(exhibit, reports_on: job_tracker) if batch_count == 1
20
+
21
+ batch_size ||= (count.to_f / batch_count).ceil
22
+
23
+ perform_later_in_batches(exhibit, of: batch_size)
24
+ end
25
+
26
+ def perform_later_in_batches(exhibit, of:)
27
+ last = 0
28
+ exhibit.resources.select(:id).in_batches(of: of) do |batch|
29
+ last = batch.last.id
30
+ Spotlight::ReindexJob.perform_later(exhibit, reports_on: job_tracker, start: batch.first.id, finish: batch.last.id)
31
+ end
32
+
33
+ Spotlight::ReindexJob.perform_later(exhibit, reports_on: job_tracker, start: last)
34
+ end
35
+ end
36
+ end
@@ -3,72 +3,80 @@
3
3
  module Spotlight
4
4
  ##
5
5
  # Reindex the given resources or exhibits
6
- class ReindexJob < ActiveJob::Base
7
- queue_as :default
8
-
9
- # The validity checker is a seam for implementations to expire unnecessary
10
- # indexing tasks if it becomes redundant while waiting in the job queue.
11
- class_attribute :validity_checker, default: Spotlight::ValidityChecker.new
12
- self.validity_checker ||= Spotlight::ValidityChecker.new if Rails.version < '5.2'
6
+ class ReindexJob < Spotlight::ApplicationJob
7
+ include Spotlight::JobTracking
8
+ include Spotlight::LimitConcurrency
13
9
 
14
10
  before_perform do |job|
15
- job_log_entry = log_entry(job)
16
- next unless job_log_entry
11
+ pagination = job.arguments.last.slice(:start, :finish) if job.arguments.last.is_a? Hash
12
+ pagination ||= {}
17
13
 
18
- items_reindexed_estimate = resource_list(job.arguments.first).sum do |resource|
19
- resource.document_builder.documents_to_index.size
20
- end
21
- job_log_entry.update(items_reindexed_estimate: items_reindexed_estimate)
14
+ progress.total = resource_list(job.arguments.first, **pagination).sum(&:estimated_size)
15
+ end
16
+
17
+ after_perform do
18
+ exhibit&.touch # rubocop:disable Rails/SkipsModelValidations
22
19
  end
23
20
 
24
- around_perform do |job, block|
25
- job_log_entry = log_entry(job)
26
- job_log_entry&.in_progress!
21
+ after_perform :commit
22
+
23
+ # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
24
+ def perform(exhibit_or_resources, start: nil, finish: nil, **)
25
+ job_tracker.update(status: 'in_progress')
26
+
27
+ errors = 0
28
+
29
+ error_handler = lambda do |pipeline, _error_context, exception, _data|
30
+ job_tracker.append_log_entry(type: :error, message: exception.to_s, resource_id: pipeline.source&.id)
31
+ errors += 1
32
+ end
27
33
 
28
- begin
29
- block.call
30
- rescue StandardError
31
- job_log_entry&.failed!
32
- raise
34
+ resource_list(exhibit_or_resources, start: start, finish: finish).each do |resource|
35
+ resource.reindex(touch: false, commit: false, job_tracker: job_tracker, additional_data: job_data, on_error: error_handler) do |*|
36
+ progress&.increment
37
+ end
38
+ rescue StandardError => e
39
+ error_handler.call(Struct.new(:source).new(resource), self, e, nil)
33
40
  end
34
41
 
35
- job_log_entry&.succeeded!
42
+ job_tracker.append_log_entry(type: :info, message: "#{progress.progress} of #{progress.total} (#{errors} errors)")
43
+ job_tracker.update(status: errors.zero? ? 'completed' : 'failed', data: { progress: progress.progress, total: progress.total })
36
44
  end
45
+ # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
37
46
 
38
- def self.perform_later(exhibit_or_resources, log_entry = nil)
39
- validity_token = validity_checker.mint(exhibit_or_resources)
47
+ private
40
48
 
41
- super(exhibit_or_resources, log_entry, validity_token)
49
+ def commit
50
+ Blacklight.default_index.connection.commit
42
51
  end
43
52
 
44
- def perform(exhibit_or_resources, log_entry = nil, validity_token = nil)
45
- return unless still_valid?(exhibit_or_resources, validity_token)
53
+ def job_data
54
+ return unless job_tracker
46
55
 
47
- resource_list(exhibit_or_resources).each do |resource|
48
- resource.reindex(log_entry)
49
- end
56
+ { Spotlight::Engine.config.job_tracker_id_field => job_tracker.top_level_job_tracker.job_id }
50
57
  end
51
58
 
52
- private
53
-
54
- def resource_list(exhibit_or_resources)
59
+ def resource_list(exhibit_or_resources, start: nil, finish: nil)
55
60
  if exhibit_or_resources.is_a?(Spotlight::Exhibit)
56
- exhibit_or_resources.resources.find_each
57
- elsif exhibit_or_resources.is_a?(Enumerable)
58
- exhibit_or_resources
61
+ exhibit_or_resources.resources.find_each(start: start, finish: finish)
59
62
  else
60
63
  Array(exhibit_or_resources)
61
64
  end
62
65
  end
63
66
 
64
- def log_entry(job)
65
- job.arguments.second if job.arguments.second.is_a?(Spotlight::ReindexingLogEntry)
67
+ def job_tracking_resource
68
+ exhibit
66
69
  end
67
70
 
68
- def still_valid?(exhibit_or_resources, validity_token)
69
- return true unless validity_token
71
+ def exhibit
72
+ exhibit_or_resources = arguments.first
70
73
 
71
- validity_checker.check exhibit_or_resources, validity_token
74
+ case exhibit_or_resources
75
+ when Spotlight::Exhibit
76
+ exhibit_or_resources
77
+ when Spotlight::Resource
78
+ exhibit_or_resources.exhibit
79
+ end
72
80
  end
73
81
  end
74
82
  end
@@ -4,8 +4,8 @@ module Spotlight
4
4
  ##
5
5
  # After renaming an exhibit-specific field, we also
6
6
  # need to update the sidecars that may contain that field
7
- class RenameSidecarFieldJob < ActiveJob::Base
8
- queue_as :default
7
+ class RenameSidecarFieldJob < Spotlight::ApplicationJob
8
+ include Spotlight::JobTracking
9
9
 
10
10
  def perform(exhibit, old_field, new_field, old_slug = nil, new_slug = nil)
11
11
  exhibit.solr_document_sidecars.find_each do |s|