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
@@ -0,0 +1,52 @@
1
+ FactoryGirl.define do
2
+ factory :unstarted_reindexing_log_entry, class: Spotlight::ReindexingLogEntry do
3
+ items_reindexed_count 15
4
+ job_status 'unstarted'
5
+ exhibit
6
+ user
7
+ end
8
+
9
+ factory :reindexing_log_entry, class: Spotlight::ReindexingLogEntry do
10
+ items_reindexed_count 10
11
+ start_time { Time.zone.parse('2017-01-05 23:00:00') }
12
+ end_time { Time.zone.parse('2017-01-05 23:05:00') }
13
+ job_status 'succeeded'
14
+ exhibit
15
+ user
16
+ end
17
+
18
+ factory :reindexing_log_entry_no_user, class: Spotlight::ReindexingLogEntry do
19
+ items_reindexed_count 10
20
+ start_time { Time.zone.parse('2017-01-05 23:00:00') }
21
+ end_time { Time.zone.parse('2017-01-05 23:05:00') }
22
+ job_status 'succeeded'
23
+ exhibit
24
+ end
25
+
26
+ factory :in_progress_reindexing_log_entry, class: Spotlight::ReindexingLogEntry do
27
+ items_reindexed_count 100
28
+ start_time { Time.zone.now - 300 }
29
+ end_time nil
30
+ job_status 'in_progress'
31
+ exhibit
32
+ user
33
+ end
34
+
35
+ factory :recent_reindexing_log_entry, class: Spotlight::ReindexingLogEntry do
36
+ sequence(:items_reindexed_count)
37
+ start_time { Time.zone.now - 86_400 }
38
+ end_time { Time.zone.now - 86_100 }
39
+ job_status 'succeeded'
40
+ exhibit
41
+ user
42
+ end
43
+
44
+ factory :failed_reindexing_log_entry, class: Spotlight::ReindexingLogEntry do
45
+ items_reindexed_count 10
46
+ start_time { Time.zone.parse('2017-01-10 23:00:00') }
47
+ end_time { Time.zone.parse('2017-01-10 23:05:00') }
48
+ job_status 'failed'
49
+ exhibit
50
+ user
51
+ end
52
+ end
@@ -6,7 +6,7 @@ feature 'Reindex Monitor', js: true do
6
6
  let(:exhibit_curator) { FactoryGirl.create(:exhibit_curator, exhibit: exhibit) }
7
7
 
8
8
  before do
9
- resources.each(&:waiting!)
9
+ FactoryGirl.create(:in_progress_reindexing_log_entry, exhibit: exhibit, items_reindexed_estimate: 5)
10
10
  login_as exhibit_curator
11
11
  visit spotlight.admin_exhibit_catalog_path(exhibit)
12
12
  end
@@ -14,8 +14,8 @@ feature 'Reindex Monitor', js: true do
14
14
  it 'is rendered on the item admin page' do
15
15
  expect(page).to have_css('.panel.index-status', visible: true)
16
16
  within('.panel.index-status') do
17
- expect(page).to have_css('p', text: /Began reindexing a total of \d items/)
18
- expect(page).to have_css('p', text: /Reindexed \d of \d items/)
17
+ expect(page).to have_css('p', text: /Began reindexing a total of \d+ items/)
18
+ expect(page).to have_css('p', text: /Reindexed \d+ of \d+ items/)
19
19
  end
20
20
  end
21
21
  end
@@ -2,6 +2,8 @@
2
2
  describe Spotlight::ReindexJob do
3
3
  let(:exhibit) { FactoryGirl.create(:exhibit) }
4
4
  let(:resource) { FactoryGirl.create(:resource) }
5
+ let(:user) { FactoryGirl.create(:user) }
6
+ let(:log_entry) { Spotlight::ReindexingLogEntry.create(exhibit: exhibit, user: user) }
5
7
 
6
8
  before do
7
9
  allow_any_instance_of(Spotlight::Resource).to receive(:reindex)
@@ -14,6 +16,7 @@ describe Spotlight::ReindexJob do
14
16
  exhibit.resources << resource
15
17
  exhibit.save
16
18
  end
19
+
17
20
  it 'attempts to reindex every resource in the exhibit' do
18
21
  # ActiveJob will reload the collection, so we go through a little trouble:
19
22
  expect_any_instance_of(Spotlight::Resource).to receive(:reindex) do |thingy|
@@ -22,6 +25,40 @@ describe Spotlight::ReindexJob do
22
25
 
23
26
  subject.perform_now
24
27
  end
28
+
29
+ context 'with a log_entry' do
30
+ subject { described_class.new(exhibit, log_entry) }
31
+
32
+ it 'marks the log entry as started' do
33
+ expect(log_entry).to receive(:in_progress!)
34
+ subject.perform_now
35
+ end
36
+
37
+ it 'marks the log entry as successful if there is no error' do
38
+ expect(log_entry).to receive(:succeeded!)
39
+ subject.perform_now
40
+ end
41
+
42
+ it 'marks the log entry as failed if there is an error' do
43
+ unexpected_error = StandardError.new
44
+ # it'd be more realistic to raise on resource#reindex, but that's already stubbed above, so this'll have to do
45
+ expect(subject).to receive(:perform).with(exhibit, log_entry).and_raise unexpected_error
46
+ expect(log_entry).to receive(:failed!)
47
+ expect { subject.perform_now }.to raise_error unexpected_error
48
+ end
49
+
50
+ it 'updates the items_reindexed_estimate field on the log entry' do
51
+ expect(log_entry).to receive(:update).with(items_reindexed_estimate: 1)
52
+ subject.perform_now
53
+ end
54
+
55
+ it 'passes log_entry to the resource.reindex call' do
56
+ # ActiveJob will reload the collection, so we go through a little trouble:
57
+ expect_any_instance_of(Spotlight::Resource).to receive(:reindex).with(log_entry).exactly(:once)
58
+ # expect(resource).to receive(:reindex).with(log_entry)
59
+ subject.perform_now
60
+ end
61
+ end
25
62
  end
26
63
 
27
64
  context 'with a resource' do
@@ -190,10 +190,47 @@ describe Spotlight::Exhibit, type: :model do
190
190
 
191
191
  describe '#reindex_later' do
192
192
  subject { FactoryGirl.create(:exhibit) }
193
+ let(:log_entry) { Spotlight::ReindexingLogEntry.new(exhibit: subject, user: user, items_reindexed_count: 0) }
193
194
 
194
- it 'queues a reindex job for the exhibit' do
195
- expect(Spotlight::ReindexJob).to receive(:perform_later).with(subject)
196
- subject.reindex_later
195
+ context 'user is omitted' do
196
+ let(:user) { nil }
197
+
198
+ it 'queues a reindex job for the exhibit, with nil user for the log entry' do
199
+ expect(subject).to receive(:new_reindexing_log_entry).with(nil).and_return(log_entry)
200
+ expect(Spotlight::ReindexJob).to receive(:perform_later).with(subject, log_entry)
201
+ subject.reindex_later
202
+ expect(log_entry.user).to be nil
203
+ end
204
+ end
205
+
206
+ context 'non-nil user is provided' do
207
+ let(:user) { FactoryGirl.build(:user) }
208
+
209
+ it 'queues a reindex job for the exhibit, with actual user for the log entry' do
210
+ expect(subject).to receive(:new_reindexing_log_entry).with(user).and_return(log_entry)
211
+ expect(Spotlight::ReindexJob).to receive(:perform_later).with(subject, log_entry)
212
+ subject.reindex_later user
213
+ expect(log_entry.user).to eq user
214
+ end
215
+ end
216
+ end
217
+
218
+ describe '#new_reindexing_log_entry' do
219
+ let(:user) { FactoryGirl.build(:user) }
220
+ it 'returns a properly configured Spotlight::ReindexingLogEntry instance' do
221
+ reindexing_log_entry = subject.send(:new_reindexing_log_entry, user)
222
+ expect(reindexing_log_entry.exhibit).to eq subject
223
+ expect(reindexing_log_entry.user).to eq user
224
+ expect(reindexing_log_entry.items_reindexed_count).to eq 0
225
+ expect(reindexing_log_entry.unstarted?).to be true
226
+ end
227
+
228
+ it 'does not require user the user parameter' do
229
+ reindexing_log_entry = subject.send(:new_reindexing_log_entry)
230
+ expect(reindexing_log_entry.exhibit).to eq subject
231
+ expect(reindexing_log_entry.user).to be nil
232
+ expect(reindexing_log_entry.items_reindexed_count).to eq 0
233
+ expect(reindexing_log_entry.unstarted?).to be true
197
234
  end
198
235
  end
199
236
 
@@ -266,7 +303,9 @@ describe Spotlight::Exhibit, type: :model do
266
303
 
267
304
  describe '#reindex_progress' do
268
305
  it 'returns a Spotlight::ReindexProgress' do
269
- expect(subject.reindex_progress).to be_a Spotlight::ReindexProgress
306
+ reindex_progress = subject.reindex_progress
307
+ expect(reindex_progress).to be_a Spotlight::ReindexProgress
308
+ expect(reindex_progress.exhibit).to eq subject
270
309
  end
271
310
  end
272
311
  end
@@ -1,139 +1,171 @@
1
1
  describe Spotlight::ReindexProgress, type: :model do
2
- let(:start_time) { 20.minutes.ago.at_beginning_of_minute }
3
- let(:finish_time) { 5.minutes.ago.at_beginning_of_minute }
4
- let(:updated_time) { 1.minute.ago.at_beginning_of_minute }
5
- let!(:first_resource) do
6
- FactoryGirl.create(
7
- :resource,
8
- updated_at: updated_time,
9
- indexed_at: start_time,
10
- enqueued_at: start_time,
11
- last_indexed_finished: start_time,
12
- last_indexed_estimate: 7,
13
- last_indexed_count: 5,
14
- index_status: 1
15
- )
16
- end
17
- let!(:last_resource) do
18
- FactoryGirl.create(
19
- :resource,
20
- updated_at: finish_time,
21
- indexed_at: finish_time,
22
- enqueued_at: start_time,
23
- last_indexed_finished: finish_time,
24
- last_indexed_estimate: 3,
25
- last_indexed_count: 2,
26
- index_status: 1
27
- )
2
+ let(:reindexing_log_entries) do
3
+ [
4
+ # failed is the later of the two, and thus the return value for current_log_entry
5
+ FactoryGirl.create(:reindexing_log_entry, items_reindexed_estimate: 11),
6
+ FactoryGirl.create(:failed_reindexing_log_entry, items_reindexed_estimate: 12)
7
+ ]
28
8
  end
9
+ let(:exhibit) { FactoryGirl.create(:exhibit, reindexing_log_entries: reindexing_log_entries) }
29
10
 
30
- let(:new_resource) do
31
- FactoryGirl.create(
32
- :resource,
33
- last_indexed_count: 10,
34
- last_indexed_estimate: 15,
35
- index_status: 0
36
- )
37
- end
11
+ let(:subject) { described_class.new(exhibit) }
38
12
 
39
- let(:resources) { [first_resource, last_resource, new_resource] }
40
- subject { described_class.new(Spotlight::Resource.all) }
41
- let(:json) { JSON.parse(subject.to_json) }
13
+ describe '#started_at' do
14
+ it 'returns start_time for current_log_entry' do
15
+ expect(subject.started_at).to eq(Time.zone.parse('2017-01-10 23:00:00'))
16
+ end
17
+ end
42
18
 
43
- before do
44
- allow(subject).to receive_messages(completed_resources: resources)
19
+ describe '#updated_at' do
20
+ # disable SkipsModelValidations cop for this test, because it complains about #touch, which is convenient here
21
+ # rubocop:disable Rails/SkipsModelValidations
22
+ it 'returns the time of last update for current_log_entry' do
23
+ lower_bound = Time.zone.now
24
+ subject.send(:current_log_entry).touch
25
+ upper_bound = Time.zone.now
26
+
27
+ expect(subject.updated_at).to be_between(lower_bound, upper_bound)
28
+ end
29
+ # rubocop:enable Rails/SkipsModelValidations
45
30
  end
46
31
 
47
- describe '#recently_in_progress?' do
48
- let(:resources) { [first_resource, last_resource] }
49
- context 'when the last resource has been updated within the allotted time' do
50
- it 'is true' do
51
- expect(subject).to be_recently_in_progress
52
- end
32
+ describe '#finished?' do
33
+ it 'returns true if current_log_entry is succeeded or failed' do
34
+ expect(subject.finished?).to be true
53
35
  end
36
+ end
54
37
 
55
- context 'when any of the resources is marked as waiting' do
56
- before do
57
- first_resource.waiting!
58
- end
59
- it 'is true' do
60
- expect(subject).to be_recently_in_progress
61
- end
38
+ describe '#finished_at' do
39
+ it 'returns end_time for current_log_entry' do
40
+ expect(subject.finished_at).to eq(Time.zone.parse('2017-01-10 23:05:00'))
62
41
  end
42
+ end
63
43
 
64
- context 'when the last resources has been updated outside of the allotted time ' do
65
- before do
66
- expect(last_resource).to receive_messages(last_indexed_finished: 12.minutes.ago)
67
- end
68
- it 'is false' do
69
- expect(subject).not_to be_recently_in_progress
70
- end
44
+ describe '#total' do
45
+ it 'returns items_reindexed_estimate for current_log_entry' do
46
+ expect(subject.total).to be 12
71
47
  end
48
+ end
72
49
 
73
- it 'is included in the json' do
74
- expect(json['recently_in_progress']).to be true
50
+ describe '#completed' do
51
+ it 'returns items_reindexed_count for current_log_entry' do
52
+ expect(subject.completed).to be 10
75
53
  end
76
54
  end
77
55
 
78
- describe '#started_at' do
79
- it 'returns the indexed_at attribute of the first resource' do
80
- expect(subject.started_at).to eq start_time
56
+ describe '#errored?' do
57
+ it 'returns true for log entries marked as failed' do
58
+ expect(subject.errored?).to be true
81
59
  end
60
+ end
82
61
 
83
- it 'is included in the json as a localized string' do
84
- expect(json['started_at']).to eq I18n.l(start_time, format: :short)
62
+ describe '#as_json' do
63
+ it 'returns a hash with values for current_log_entry via the various helper methods' do
64
+ expect(subject.as_json).to eq(
65
+ recently_in_progress: subject.recently_in_progress?,
66
+ started_at: subject.send(:localized_start_time),
67
+ finished_at: subject.send(:localized_finish_time),
68
+ updated_at: subject.send(:localized_updated_time),
69
+ total: subject.total,
70
+ completed: subject.completed,
71
+ errored: subject.errored?
72
+ )
85
73
  end
74
+ end
86
75
 
87
- context 'with unqueued resources' do
88
- subject { described_class.new(Spotlight::Resource.where(id: new_resource.id)) }
76
+ describe '#recently_in_progress?' do
77
+ context 'there is no end_time for current_log_entry' do
78
+ let(:current_log_entry) { FactoryGirl.create(:in_progress_reindexing_log_entry) }
79
+ let(:exhibit) { FactoryGirl.create(:exhibit, reindexing_log_entries: [current_log_entry]) }
89
80
 
90
- it 'returns the indexed_at attribute of the first resource' do
91
- expect(subject.started_at).to be_nil
81
+ it 'returns true' do
82
+ expect(subject.recently_in_progress?).to be true
92
83
  end
93
84
  end
94
- end
95
85
 
96
- describe '#updated_at' do
97
- let(:resources) { [first_resource, last_resource] }
86
+ context 'current_log_entry has an end_time less than Spotlight::Engine.config.reindex_progress_window.minutes.ago' do
87
+ let(:current_log_entry) { FactoryGirl.create(:recent_reindexing_log_entry, end_time: Time.zone.now) }
88
+ let(:exhibit) { FactoryGirl.create(:exhibit, reindexing_log_entries: [current_log_entry]) }
98
89
 
99
- it 'returns the updated_at attribute of the last resource' do
100
- expect(subject.updated_at).to eq updated_time
90
+ it 'returns true' do
91
+ expect(subject.recently_in_progress?).to be true
92
+ end
101
93
  end
102
94
 
103
- it 'is included in the json as a localized string under the updated_at attribute' do
104
- expect(json['updated_at']).to eq I18n.l(updated_time, format: :short)
95
+ context 'current_log_entry is unstarted ' do
96
+ let(:current_log_entry) { FactoryGirl.create(:unstarted_reindexing_log_entry) }
97
+ let(:exhibit) { FactoryGirl.create(:exhibit, reindexing_log_entries: [current_log_entry]) }
98
+
99
+ it 'returns false' do
100
+ expect(subject.recently_in_progress?).to be false
101
+ end
105
102
  end
106
103
  end
107
104
 
108
- describe '#finished_at' do
109
- let(:resources) { [first_resource, last_resource] }
105
+ describe 'private methods' do
106
+ describe '#current_log_entry' do
107
+ let(:reindexing_log_entries) do
108
+ [
109
+ FactoryGirl.create(:unstarted_reindexing_log_entry),
110
+ FactoryGirl.create(:reindexing_log_entry),
111
+ FactoryGirl.create(:in_progress_reindexing_log_entry),
112
+ FactoryGirl.create(:failed_reindexing_log_entry),
113
+ FactoryGirl.create(:unstarted_reindexing_log_entry)
114
+ ]
115
+ end
110
116
 
111
- it 'returns the updated_at attribute of the last resource' do
112
- expect(subject.finished_at).to eq finish_time
117
+ it 'returns the latest log entry that is not unstarted' do
118
+ expect(subject.send(:current_log_entry)).to eq(reindexing_log_entries[2])
119
+ end
113
120
  end
114
121
 
115
- it 'is included in the json as a localized string under the updated_at attribute' do
116
- expect(json['finished_at']).to eq I18n.l(finish_time, format: :short)
122
+ describe '#localized_start_time' do
123
+ it 'returns the short formatted start time' do
124
+ expect(subject.send(:localized_start_time)).to eq I18n.l(subject.started_at, format: :short)
125
+ end
117
126
  end
118
- end
119
127
 
120
- describe '#total' do
121
- it 'sums the resources last_indexed_estimate' do
122
- expect(subject.total).to eq 25
128
+ describe '#localized_finish_time' do
129
+ it 'returns the short formatted end time' do
130
+ expect(subject.send(:localized_finish_time)).to eq I18n.l(subject.finished_at, format: :short)
131
+ end
123
132
  end
124
133
 
125
- it 'is included in the json' do
126
- expect(json['total']).to eq 25
134
+ describe '#localized_updated_time' do
135
+ it 'returns the short formatted last updated time' do
136
+ expect(subject.send(:localized_updated_time)).to eq I18n.l(subject.updated_at, format: :short)
137
+ end
127
138
  end
128
139
  end
129
140
 
130
- describe '#completed' do
131
- it 'sums the resources last_indexed_count' do
132
- expect(subject.completed).to eq 17
133
- end
134
-
135
- it 'is included in the json' do
136
- expect(json['completed']).to eq 17
141
+ context 'current_log_entry is nil' do
142
+ let(:reindexing_log_entries) { [] }
143
+
144
+ # rubocop:disable RSpec/MultipleExpectations
145
+ it 'methods return gracefully' do
146
+ expect(subject.send(:current_log_entry)).to be nil
147
+
148
+ expect(subject.recently_in_progress?).to be false
149
+ expect(subject.started_at).to be nil
150
+ expect(subject.updated_at).to be nil
151
+ expect(subject.finished?).to be false
152
+ expect(subject.finished_at).to be nil
153
+ expect(subject.total).to be nil
154
+ expect(subject.completed).to be nil
155
+ expect(subject.errored?).to be false
156
+ expect(subject.send(:localized_start_time)).to be nil
157
+ expect(subject.send(:localized_finish_time)).to be nil
158
+ expect(subject.send(:localized_updated_time)).to be nil
159
+ expect(subject.as_json).to eq(
160
+ recently_in_progress: false,
161
+ started_at: nil,
162
+ finished_at: nil,
163
+ updated_at: nil,
164
+ total: nil,
165
+ completed: nil,
166
+ errored: false
167
+ )
137
168
  end
169
+ # rubocop:enable RSpec/MultipleExpectations
138
170
  end
139
171
  end