blacklight-spotlight 0.31.0 → 0.32.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 (35) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/spotlight/dashboards_controller.rb +1 -0
  3. data/app/controllers/spotlight/resources_controller.rb +1 -1
  4. data/app/helpers/spotlight/browse_helper.rb +2 -0
  5. data/app/helpers/spotlight/pages_helper.rb +3 -1
  6. data/app/helpers/spotlight/rendering_helper.rb +7 -0
  7. data/app/jobs/spotlight/reindex_job.rb +30 -4
  8. data/app/models/concerns/spotlight/solr_document.rb +1 -1
  9. data/app/models/concerns/spotlight/user.rb +1 -0
  10. data/app/models/spotlight/exhibit.rb +8 -3
  11. data/app/models/spotlight/reindex_progress.rb +19 -29
  12. data/app/models/spotlight/reindexing_log_entry.rb +39 -0
  13. data/app/models/spotlight/resource.rb +10 -59
  14. data/app/serializers/spotlight/exhibit_export_serializer.rb +7 -7
  15. data/app/serializers/spotlight/page_representer.rb +1 -1
  16. data/app/views/spotlight/browse/_search.html.erb +1 -1
  17. data/app/views/spotlight/browse/show.html.erb +1 -1
  18. data/app/views/spotlight/catalog/_edit_default.html.erb +2 -1
  19. data/app/views/spotlight/dashboards/_reindexing_activity.html.erb +28 -0
  20. data/app/views/spotlight/dashboards/show.html.erb +4 -0
  21. data/config/locales/spotlight.en.yml +15 -0
  22. data/db/migrate/20170105222939_create_spotlight_reindexing_log_entries.rb +15 -0
  23. data/lib/spotlight/version.rb +1 -1
  24. data/spec/examples.txt +1066 -8
  25. data/spec/factories/reindexing_log_entries.rb +52 -0
  26. data/spec/features/javascript/reindex_monitor_spec.rb +3 -3
  27. data/spec/jobs/spotlight/reindex_job_spec.rb +37 -0
  28. data/spec/models/spotlight/exhibit_spec.rb +43 -4
  29. data/spec/models/spotlight/reindex_progress_spec.rb +128 -96
  30. data/spec/models/spotlight/reindexing_log_entry_spec.rb +135 -0
  31. data/spec/models/spotlight/resource_spec.rb +16 -28
  32. data/spec/support/views/test_view_helpers.rb +1 -0
  33. data/spec/views/spotlight/browse/show.html.erb_spec.rb +6 -0
  34. data/spec/views/spotlight/dashboards/_reindexing_activity.html.erb_spec.rb +87 -0
  35. metadata +26 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 9129ce334891bf7bc19eda0bd1a59b3431e7914c
4
- data.tar.gz: fdf77ce53166788e4b32db54dc9e0b0a2c577010
3
+ metadata.gz: d1fd2b950f32f603a3ee0f2db98c3b91e5cad5f9
4
+ data.tar.gz: 6cb02f70515f59a51422ef7cf1309216351b091f
5
5
  SHA512:
6
- metadata.gz: 71aff72f8a8e7b63b7cd4d21871b116d9afa35f76113218e296364fe6119a840134932062c315ee0aa969289662639b33c26ce6615224058aa551c9eca4609e9
7
- data.tar.gz: 8c72942251aadf9def1efa373cbbf3eb9b97a4454cbf84ac9e5745c78a3522f57b64a93c732b26bc8a5b44ac0ce195178088df6cbc4fe28272e1d77c0561064f
6
+ metadata.gz: 113fc7cd7d398ecde9d12c8428aeee286b0604267ac11e4222656766760620a11f800a33484d6c1878590f94f367443794d9baa49facaeca9adfb318a26c6a04
7
+ data.tar.gz: c29d9d9a84bedd527d3c63fdc0fa6d2c9ca648d69f54eda3b0b1be77843649329efc526832e6bdc8a98570b9ca8ac02627433c9300f675640eba1469d165b092
@@ -18,6 +18,7 @@ module Spotlight
18
18
 
19
19
  @pages = @exhibit.pages.recent.limit(5)
20
20
  @solr_documents = load_recent_solr_documents 5
21
+ @recent_reindexing = @exhibit.reindexing_log_entries.recent
21
22
 
22
23
  attach_dashboard_breadcrumbs
23
24
  end
@@ -35,7 +35,7 @@ module Spotlight
35
35
  end
36
36
 
37
37
  def reindex_all
38
- @exhibit.reindex_later
38
+ @exhibit.reindex_later current_user
39
39
 
40
40
  redirect_to admin_exhibit_catalog_path(@exhibit), notice: t(:'spotlight.resources.reindexing_in_progress')
41
41
  end
@@ -3,6 +3,8 @@ module Spotlight
3
3
  # Helper for browse views
4
4
  module BrowseHelper
5
5
  include ::BlacklightConfigurationHelper
6
+ include Spotlight::RenderingHelper
7
+
6
8
  def document_index_view_type
7
9
  if @search && @search.default_index_view_type.present? && params[:view].blank?
8
10
  blacklight_config.view[@search.default_index_view_type].key
@@ -2,6 +2,8 @@ module Spotlight
2
2
  ##
3
3
  # Sir-trevor helpers methods
4
4
  module PagesHelper
5
+ include Spotlight::RenderingHelper
6
+
5
7
  ##
6
8
  # Override the default #sir_trevor_markdown so we can use
7
9
  # a more complete markdown rendered
@@ -12,7 +14,7 @@ module Spotlight
12
14
  ''
13
15
  end
14
16
 
15
- GitHub::Markup.render('.md', clean_text).html_safe
17
+ render_markdown(clean_text)
16
18
  end
17
19
 
18
20
  def available_index_fields
@@ -0,0 +1,7 @@
1
+ module Spotlight
2
+ module RenderingHelper # :nodoc:
3
+ def render_markdown(text)
4
+ GitHub::Markup.render('.md', text).html_safe
5
+ end
6
+ end
7
+ end
@@ -4,12 +4,34 @@ module Spotlight
4
4
  class ReindexJob < ActiveJob::Base
5
5
  queue_as :default
6
6
 
7
- before_enqueue do |job|
8
- resource_list(job.arguments.first).each(&:waiting!)
7
+ before_perform do |job|
8
+ job_log_entry = log_entry(job)
9
+ next unless job_log_entry
10
+
11
+ items_reindexed_estimate = resource_list(job.arguments.first).sum do |resource|
12
+ resource.document_builder.documents_to_index.size
13
+ end
14
+ job_log_entry.update(items_reindexed_estimate: items_reindexed_estimate)
15
+ end
16
+
17
+ around_perform do |job, block|
18
+ job_log_entry = log_entry(job)
19
+ job_log_entry.in_progress! if job_log_entry
20
+
21
+ begin
22
+ block.call
23
+ rescue
24
+ job_log_entry.failed! if job_log_entry
25
+ raise
26
+ end
27
+
28
+ job_log_entry.succeeded! if job_log_entry
9
29
  end
10
30
 
11
- def perform(exhibit_or_resources)
12
- resource_list(exhibit_or_resources).each(&:reindex)
31
+ def perform(exhibit_or_resources, log_entry = nil)
32
+ resource_list(exhibit_or_resources).each do |resource|
33
+ resource.reindex(log_entry)
34
+ end
13
35
  end
14
36
 
15
37
  private
@@ -23,5 +45,9 @@ module Spotlight
23
45
  Array(exhibit_or_resources)
24
46
  end
25
47
  end
48
+
49
+ def log_entry(job)
50
+ job.arguments.second if job.arguments.second.is_a?(Spotlight::ReindexingLogEntry)
51
+ end
26
52
  end
27
53
  end
@@ -86,7 +86,7 @@ module Spotlight
86
86
  end
87
87
 
88
88
  def to_solr
89
- { self.class.unique_key.to_sym => id }.reverse_merge(sidecars.inject({}) { |a, e| a.merge(e.to_solr) }).merge(tags_to_solr)
89
+ { self.class.unique_key.to_sym => id }.reverse_merge(sidecars.inject({}) { |acc, elem| acc.merge(elem.to_solr) }).merge(tags_to_solr)
90
90
  end
91
91
 
92
92
  def make_public!(exhibit)
@@ -6,6 +6,7 @@ module Spotlight
6
6
  included do
7
7
  has_many :roles, class_name: 'Spotlight::Role', dependent: :destroy
8
8
  has_many :exhibits, class_name: 'Spotlight::Exhibit', through: :roles, source: 'resource', source_type: 'Spotlight::Exhibit'
9
+ has_many :reindexing_log_entries, class_name: 'Spotlight::ReindexingLogEntry'
9
10
 
10
11
  scope :with_roles, -> { where(id: Spotlight::Role.distinct.pluck(:user_id)) }
11
12
 
@@ -32,6 +32,7 @@ module Spotlight
32
32
  has_many :feature_pages, extend: FriendlyId::FinderMethods
33
33
  has_many :main_navigations, dependent: :delete_all
34
34
  has_many :owned_taggings, class_name: 'ActsAsTaggableOn::Tagging', as: :tagger
35
+ has_many :reindexing_log_entries, dependent: :destroy
35
36
  has_many :resources
36
37
  has_many :roles, as: :resource, dependent: :delete_all
37
38
  has_many :searches, dependent: :destroy, extend: FriendlyId::FinderMethods
@@ -79,8 +80,8 @@ module Spotlight
79
80
  end
80
81
  end
81
82
 
82
- def reindex_later
83
- Spotlight::ReindexJob.perform_later(self)
83
+ def reindex_later(user = nil)
84
+ Spotlight::ReindexJob.perform_later(self, new_reindexing_log_entry(user))
84
85
  end
85
86
 
86
87
  def uploaded_resource_fields
@@ -100,7 +101,7 @@ module Spotlight
100
101
  end
101
102
 
102
103
  def reindex_progress
103
- @reindex_progress ||= ReindexProgress.new(resources) if resources
104
+ @reindex_progress ||= ReindexProgress.new(self)
104
105
  end
105
106
 
106
107
  protected
@@ -108,5 +109,9 @@ module Spotlight
108
109
  def sanitize_description
109
110
  self.description = ::Rails::Html::FullSanitizer.new.sanitize(description)
110
111
  end
112
+
113
+ def new_reindexing_log_entry(user = nil)
114
+ Spotlight::ReindexingLogEntry.create(exhibit: self, user: user, items_reindexed_count: 0, job_status: 'unstarted')
115
+ end
111
116
  end
112
117
  end
@@ -2,51 +2,47 @@ module Spotlight
2
2
  ##
3
3
  # ReindexProgress is a class that models the progress of reindexing a list of resources
4
4
  class ReindexProgress
5
- def initialize(resource_list)
6
- @resources = if resource_list.present?
7
- resource_list.order('updated_at')
8
- else
9
- Spotlight::Resource.none
10
- end
5
+ attr_reader :exhibit
6
+
7
+ def initialize(exhibit)
8
+ @exhibit = exhibit
11
9
  end
12
10
 
13
11
  def recently_in_progress?
14
- any_waiting? || (!!finished_at && finished_at > Spotlight::Engine.config.reindex_progress_window.minutes.ago)
12
+ return false if current_log_entry.blank?
13
+ return true if current_log_entry.in_progress?
14
+
15
+ current_log_entry.end_time.present? && (current_log_entry.end_time > Spotlight::Engine.config.reindex_progress_window.minutes.ago)
15
16
  end
16
17
 
17
18
  def started_at
18
- return unless resources.present?
19
-
20
- enqueued_resources = resources.select(&:enqueued_at?)
21
-
22
- return unless enqueued_resources.any?
23
-
24
- @started ||= enqueued_resources.min_by(&:enqueued_at).enqueued_at
19
+ current_log_entry.try(:start_time)
25
20
  end
26
21
 
27
22
  def updated_at
28
- @updated ||= resources.maximum(:updated_at) || started_at
23
+ current_log_entry.try(:updated_at)
29
24
  end
30
25
 
31
26
  def finished?
32
- completed_resources.present? && !any_waiting?
27
+ return false if current_log_entry.blank?
28
+ current_log_entry.succeeded? || current_log_entry.failed?
33
29
  end
34
30
 
35
31
  def finished_at
36
- return unless finished?
37
- @finished ||= completed_resources.max_by(&:last_indexed_finished).last_indexed_finished
32
+ current_log_entry.try(:end_time)
38
33
  end
39
34
 
40
35
  def total
41
- @total ||= resources.map(&:last_indexed_estimate).compact.sum
36
+ current_log_entry.try(:items_reindexed_estimate)
42
37
  end
43
38
 
44
39
  def completed
45
- @completed ||= resources.map(&:last_indexed_count).compact.sum
40
+ current_log_entry.try(:items_reindexed_count)
46
41
  end
47
42
 
48
43
  def errored?
49
- resources.any?(&:errored?)
44
+ return false if current_log_entry.blank?
45
+ current_log_entry.failed?
50
46
  end
51
47
 
52
48
  def as_json(*)
@@ -63,10 +59,8 @@ module Spotlight
63
59
 
64
60
  private
65
61
 
66
- attr_reader :resources
67
-
68
- def any_waiting?
69
- resources.any?(&:waiting?)
62
+ def current_log_entry
63
+ exhibit.reindexing_log_entries.where.not(job_status: 'unstarted').first
70
64
  end
71
65
 
72
66
  def localized_start_time
@@ -83,9 +77,5 @@ module Spotlight
83
77
  return unless updated_at
84
78
  I18n.l(updated_at, format: :short)
85
79
  end
86
-
87
- def completed_resources
88
- resources.completed
89
- end
90
80
  end
91
81
  end
@@ -0,0 +1,39 @@
1
+ module Spotlight
2
+ ##
3
+ # a log entry representing an attempt to reindex some number of records in an exhibit
4
+ class ReindexingLogEntry < ActiveRecord::Base
5
+ enum job_status: { unstarted: 0, in_progress: 1, succeeded: 2, failed: 3 }
6
+
7
+ belongs_to :exhibit, class_name: 'Spotlight::Exhibit'
8
+ belongs_to :user, class_name: '::User'
9
+
10
+ # null start times sort to the top, to more easily surface pending reindexing
11
+ default_scope { order('start_time IS NOT NULL, start_time DESC') }
12
+ scope :recent, -> { limit(5) }
13
+
14
+ def duration
15
+ end_time - start_time if end_time
16
+ end
17
+
18
+ def in_progress!
19
+ self.start_time = Time.zone.now
20
+ super
21
+ rescue
22
+ Rails.logger.error "unexpected error updating log entry to :in_progress from #{caller}"
23
+ end
24
+
25
+ def succeeded!
26
+ self.end_time = Time.zone.now
27
+ super
28
+ rescue
29
+ Rails.logger.error "unexpected error updating log entry to :succeeded from #{caller}"
30
+ end
31
+
32
+ def failed!
33
+ self.end_time = Time.zone.now
34
+ super
35
+ rescue
36
+ Rails.logger.error "unexpected error updating log entry to :failed from #{caller}"
37
+ end
38
+ end
39
+ end
@@ -16,19 +16,9 @@ module Spotlight
16
16
  has_many :solr_document_sidecars
17
17
 
18
18
  serialize :data, Hash
19
- store :metadata, accessors: [
20
- :enqueued_at,
21
- :last_indexed_estimate,
22
- :last_indexed_count,
23
- :last_index_elapsed_time,
24
- :last_indexed_finished
25
- ], coder: JSON
26
19
 
27
- enum index_status: [:waiting, :completed, :errored]
28
-
29
- around_index :reindex_with_logging
30
20
  after_index :commit
31
- after_index :completed!
21
+ after_index :touch_exhibit!
32
22
 
33
23
  ##
34
24
  # Persist the record to the database, and trigger a reindex to solr
@@ -41,27 +31,9 @@ module Spotlight
41
31
  ##
42
32
  # Enqueue an asynchronous reindexing job for this resource
43
33
  def reindex_later
44
- waiting!
45
34
  Spotlight::ReindexJob.perform_later(self)
46
35
  end
47
36
 
48
- def waiting!
49
- update(enqueued_at: Time.zone.now)
50
- super
51
- end
52
-
53
- def enqueued_at
54
- cast_to_date_time(super)
55
- end
56
-
57
- def enqueued_at?
58
- enqueued_at.present?
59
- end
60
-
61
- def last_indexed_finished
62
- cast_to_date_time(super)
63
- end
64
-
65
37
  def document_model
66
38
  exhibit.blacklight_config.document_model if exhibit
67
39
  end
@@ -71,43 +43,28 @@ module Spotlight
71
43
  # Index the result of {#to_solr} into the index in batches of {#batch_size}
72
44
  #
73
45
  # @return [Integer] number of records indexed
74
- def reindex
46
+ # rubocop:disable Metrics/MethodLength
47
+ def reindex(reindexing_log_entry = nil)
75
48
  benchmark "Reindexing #{self} (batch size: #{batch_size})" do
76
49
  count = 0
77
50
 
78
51
  run_callbacks :index do
79
52
  document_builder.documents_to_index.each_slice(batch_size) do |batch|
80
53
  write_to_index(batch)
81
- update(last_indexed_count: (count += batch.length))
54
+ count += batch.length
55
+ reindexing_log_entry.update(items_reindexed_count: count) if reindexing_log_entry
82
56
  end
83
57
 
84
58
  count
85
59
  end
86
60
  end
87
61
  end
62
+ # rubocop:enable Metrics/MethodLength
88
63
 
89
64
  def document_builder
90
65
  @document_builder ||= document_builder_class.new(self)
91
66
  end
92
67
 
93
- protected
94
-
95
- def reindex_with_logging
96
- time_start = Time.zone.now
97
-
98
- update(indexed_at: time_start,
99
- last_indexed_estimate: document_builder.documents_to_index.size,
100
- last_indexed_finished: nil,
101
- last_index_elapsed_time: nil)
102
-
103
- count = yield
104
-
105
- time_end = Time.zone.now
106
- update(last_indexed_count: count,
107
- last_indexed_finished: time_end,
108
- last_index_elapsed_time: time_end - time_start)
109
- end
110
-
111
68
  private
112
69
 
113
70
  def blacklight_solr
@@ -136,18 +93,12 @@ module Spotlight
136
93
  Rails.logger.warn "Unable to commit to solr: #{e}"
137
94
  end
138
95
 
139
- def write?
140
- Spotlight::Engine.config.writable_index
96
+ def touch_exhibit!
97
+ exhibit.touch
141
98
  end
142
99
 
143
- def cast_to_date_time(value)
144
- return unless value
145
-
146
- if defined? ActiveModel::Type::DateTime
147
- ActiveModel::Type::DateTime.new.cast(value)
148
- else
149
- ActiveRecord::Type::DateTime.new.type_cast_from_database(value)
150
- end
100
+ def write?
101
+ Spotlight::Engine.config.writable_index
151
102
  end
152
103
  end
153
104
  end
@@ -33,7 +33,7 @@ module Spotlight
33
33
  property prop
34
34
  end
35
35
 
36
- collection :searches, parse_strategy: ->(fragment, _i, options) { options.represented.searches.find_or_initialize_by(slug: fragment['slug']) },
36
+ collection :searches, populator: ->(fragment, options) { options[:represented].searches.find_or_initialize_by(slug: fragment['slug']) },
37
37
  class: Spotlight::Search do
38
38
  (Spotlight::Search.attribute_names - %w(id scope exhibit_id masthead_id thumbnail_id)).each do |prop|
39
39
  property prop
@@ -44,16 +44,16 @@ module Spotlight
44
44
  property :thumbnail, class: Spotlight::FeaturedImage, decorator: FeaturedImageRepresenter
45
45
  end
46
46
 
47
- collection :about_pages, parse_strategy: ->(fragment, _i, options) { options.represented.about_pages.find_or_initialize_by(slug: fragment['slug']) },
47
+ collection :about_pages, populator: ->(fragment, options) { options[:represented].about_pages.find_or_initialize_by(slug: fragment['slug']) },
48
48
  class: Spotlight::AboutPage,
49
49
  decorator: PageRepresenter
50
50
 
51
- collection :feature_pages, parse_strategy: ->(fragment, _i, options) { options.represented.feature_pages.find_or_initialize_by(slug: fragment['slug']) },
51
+ collection :feature_pages, populator: ->(fragment, options) { options[:represented].feature_pages.find_or_initialize_by(slug: fragment['slug']) },
52
52
  getter: ->(_opts) { feature_pages.at_top_level },
53
53
  class: Spotlight::FeaturePage,
54
54
  decorator: NestedPageRepresenter
55
55
 
56
- property :home_page, parse_strategy: ->(_fragment, options) { options.represented.home_page },
56
+ property :home_page, populator: ->(_fragment, options) { options[:represented].home_page },
57
57
  class: Spotlight::HomePage,
58
58
  decorator: PageRepresenter
59
59
 
@@ -65,14 +65,14 @@ module Spotlight
65
65
 
66
66
  property :blacklight_configuration, class: Spotlight::BlacklightConfiguration, decorator: ConfigurationRepresenter
67
67
 
68
- collection :custom_fields, parse_strategy: ->(fragment, _i, options) { options.represented.custom_fields.find_or_initialize_by(slug: fragment['slug']) },
68
+ collection :custom_fields, populator: ->(fragment, options) { options[:represented].custom_fields.find_or_initialize_by(slug: fragment['slug']) },
69
69
  class: Spotlight::CustomField do
70
70
  (Spotlight::CustomField.attribute_names - %w(id exhibit_id)).each do |prop|
71
71
  property prop
72
72
  end
73
73
  end
74
74
 
75
- collection :contacts, parse_strategy: ->(fragment, _i, options) { options.represented.contacts.find_or_initialize_by(slug: fragment['slug']) },
75
+ collection :contacts, populator: ->(fragment, options) { options[:represented].contacts.find_or_initialize_by(slug: fragment['slug']) },
76
76
  class: Spotlight::Contact do
77
77
  (Spotlight::Contact.attribute_names - %w(id exhibit_id avatar)).each do |prop|
78
78
  property prop
@@ -150,7 +150,7 @@ module Spotlight
150
150
  end
151
151
  end
152
152
 
153
- collection :resources, class: ->(fragment, *) { fragment.key?('type') ? fragment['type'].constantize : Spotlight::Resource } do
153
+ collection :resources, class: ->(options) { options[:fragment].key?('type') ? options[:fragment]['type'].constantize : Spotlight::Resource } do
154
154
  (Spotlight::Resource.attribute_names - %w(id url exhibit_id)).each do |prop|
155
155
  property prop
156
156
  end