blacklight-spotlight 0.31.0 → 0.32.0

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