blacklight 5.14.0 → 5.15.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 (37) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +10 -10
  3. data/Gemfile +2 -1
  4. data/VERSION +1 -1
  5. data/app/helpers/blacklight/blacklight_helper_behavior.rb +2 -14
  6. data/app/helpers/blacklight/catalog_helper_behavior.rb +31 -0
  7. data/app/views/bookmarks/index.html.erb +2 -0
  8. data/app/views/catalog/_document_default.atom.builder +2 -3
  9. data/app/views/catalog/_search_results.html.erb +1 -1
  10. data/app/views/catalog/index.html.erb +1 -0
  11. data/app/views/saved_searches/index.html.erb +2 -0
  12. data/app/views/search_history/index.html.erb +2 -0
  13. data/app/views/shared/_sitelinks_search_box.html.erb +12 -0
  14. data/config/locales/blacklight.de.yml +8 -0
  15. data/config/locales/blacklight.en.yml +8 -0
  16. data/config/locales/blacklight.es.yml +8 -0
  17. data/config/locales/blacklight.fr.yml +8 -0
  18. data/config/locales/blacklight.it.yml +8 -0
  19. data/config/locales/blacklight.pt-BR.yml +8 -0
  20. data/lib/blacklight.rb +1 -0
  21. data/lib/blacklight/configuration.rb +6 -1
  22. data/lib/blacklight/document_presenter.rb +23 -0
  23. data/lib/blacklight/facet_paginator.rb +121 -0
  24. data/lib/blacklight/search_builder.rb +21 -0
  25. data/lib/blacklight/search_helper.rb +5 -1
  26. data/lib/blacklight/solr/facet_paginator.rb +4 -101
  27. data/lib/blacklight/solr_response/spelling.rb +2 -0
  28. data/spec/features/sitelinks_search_box.rb +12 -0
  29. data/spec/helpers/blacklight_helper_spec.rb +10 -55
  30. data/spec/helpers/catalog_helper_spec.rb +39 -0
  31. data/spec/lib/blacklight/facet_paginator_spec.rb +12 -12
  32. data/spec/lib/blacklight/search_builder_spec.rb +11 -0
  33. data/spec/lib/blacklight/search_helper_spec.rb +21 -0
  34. data/spec/lib/blacklight/solr/facet_paginator_spec.rb +12 -0
  35. data/spec/lib/blacklight/solr_response_spec.rb +30 -9
  36. data/spec/lib/document_presenter_spec.rb +76 -2
  37. metadata +8 -2
@@ -0,0 +1,121 @@
1
+ module Blacklight
2
+ # Pagination for facet values -- works by setting the limit to max
3
+ # displayable. You have to ask Solr for limit+1, to get enough
4
+ # results to see if 'more' are available'. That is, the all_facet_values
5
+ # arg in constructor should be the result of asking solr for limit+1
6
+ # values.
7
+ # This is a workaround for the fact that Solr itself can't compute
8
+ # the total values for a given facet field,
9
+ # so we cannot know how many "pages" there are.
10
+ #
11
+ class FacetPaginator
12
+ extend Deprecation
13
+
14
+ self.deprecation_horizon = 'blacklight version 6.0.0'
15
+
16
+ # What request keys will we use for the parameters need. Need to
17
+ # make sure they do NOT conflict with catalog/index request params,
18
+ # and need to make them accessible in a list so we can easily
19
+ # strip em out before redirecting to catalog/index.
20
+ mattr_accessor :request_keys do
21
+ { sort: :'facet.sort', page: :'facet.page' }
22
+ end
23
+
24
+ if Rails.version < "4.1"
25
+ self.request_keys = { sort: :'facet.sort', page: :'facet.page' }
26
+ end
27
+
28
+ attr_reader :offset, :limit, :sort
29
+
30
+ # all_facet_values is a list of facet value objects returned by solr,
31
+ # asking solr for n+1 facet values.
32
+ # options:
33
+ # :limit => number to display per page, or (default) nil. Nil means
34
+ # display all with no previous or next.
35
+ # :offset => current item offset, default 0
36
+ # :sort => 'count' or 'index', solr tokens for facet value sorting, default 'count'.
37
+ def initialize(all_facet_values, arguments)
38
+ # to_s.to_i will conveniently default to 0 if nil
39
+ @offset = arguments[:offset].to_s.to_i
40
+ @limit = arguments[:limit]
41
+ @sort = arguments[:sort]
42
+
43
+ @all = all_facet_values
44
+ end
45
+
46
+ # The number of records solr gave us when we asked for limit + 1 records at the current offset
47
+ def total_count
48
+ @all.size
49
+ end
50
+
51
+ def items
52
+ items_for_limit(@all)
53
+ end
54
+
55
+ def prev_page
56
+ current_page - 1 unless first_page?
57
+ end
58
+
59
+ def current_page
60
+ if limit == 0 #check for divide by zero
61
+ 1
62
+ else
63
+ @offset / limit + 1
64
+ end
65
+ end
66
+
67
+ def next_page
68
+ current_page + 1 unless last_page?
69
+ end
70
+
71
+ #@deprecated
72
+ def has_next?
73
+ !last_page?
74
+ end
75
+ deprecation_deprecate :has_next? => "use #!last_page?"
76
+
77
+ #@deprecated
78
+ def has_previous?
79
+ !first_page?
80
+ end
81
+ deprecation_deprecate :has_next? => "use #!first_page?"
82
+
83
+ def last_page?
84
+ limit.nil? || total_count <= limit
85
+ end
86
+
87
+ def first_page?
88
+ current_page == 1
89
+ end
90
+
91
+ # We're implementing total_pages so that this matches the API from kaminari, even though we can't
92
+ # know the total number of pages.
93
+ # https://github.com/amatsuda/kaminari/blob/v0.16.1/lib/kaminari/models/page_scope_methods.rb#L21
94
+ def total_pages
95
+ -1
96
+ end
97
+
98
+ # Pass in a desired solr facet solr key ('count' or 'index', see
99
+ # http://wiki.apache.org/solr/SimpleFacetParameters#facet.limit
100
+ # under facet.sort ), and your current request params.
101
+ # Get back params suitable to passing to an ActionHelper method for
102
+ # creating a url, to resort by that method.
103
+ def params_for_resort_url(sort_method, params)
104
+ # When resorting, we've got to reset the offset to start at beginning,
105
+ # no way to make it make sense otherwise.
106
+ params.merge(request_keys[:sort] => sort_method, request_keys[:page] => nil)
107
+ end
108
+
109
+ def as_json(_ = nil)
110
+ { 'items' => items.as_json, 'limit' => limit, 'offset' => offset, 'sort' => sort }
111
+ end
112
+
113
+ private
114
+
115
+ # setting limit to nil implies no limit
116
+ # @return an array of facets on the page
117
+ def items_for_limit(values)
118
+ limit.nil? ? values : values.take(limit)
119
+ end
120
+ end
121
+ end
@@ -56,6 +56,27 @@ module Blacklight
56
56
  builder
57
57
  end
58
58
 
59
+ ##
60
+ # Converse to append, remove processor chain directives,
61
+ # returning a new builder that's a copy of receiver with
62
+ # specified change.
63
+ #
64
+ # Methods in argument that aren't currently in processor
65
+ # chain are ignored as no-ops, rather than raising.
66
+ def except *except_processor_chain
67
+ builder = self.class.new(processor_chain - except_processor_chain, scope)
68
+ .with(blacklight_params)
69
+ .merge(@merged_params)
70
+ .reverse_merge(@reverse_merged_params)
71
+
72
+ builder.start(@start) if @start
73
+ builder.rows(@rows) if @rows
74
+ builder.page(@page) if @page
75
+ builder.facet(@facet) if @facet
76
+
77
+ builder
78
+ end
79
+
59
80
  ##
60
81
  # Merge additional, repository-specific parameters
61
82
  def merge extra_params, &block
@@ -94,14 +94,18 @@ module Blacklight::SearchHelper
94
94
 
95
95
  # a solr query method
96
96
  # @param [Hash,HashWithIndifferentAccess] user_params ({}) the user provided parameters (e.g. query, facets, sort, etc)
97
- # @param [Hash,HashWithIndifferentAccess] extra_controller_params ({}) extra parameters to add to the search
98
97
  # @param [List<Symbol] processor_chain a list of filter methods to run
98
+ # @yield [search_builder] optional block yields configured SearchBuilder, caller can modify or create new SearchBuilder to be used. Block should return SearchBuilder to be used.
99
99
  # @return [Blacklight::SolrResponse] the solr response object
100
100
  def search_results(user_params, search_params_logic)
101
101
  builder = search_builder(search_params_logic).with(user_params)
102
102
  builder.page(user_params[:page]) if user_params[:page]
103
103
  builder.rows(user_params[:per_page] || user_params[:rows]) if user_params[:per_page] or user_params[:rows]
104
104
 
105
+ if block_given?
106
+ builder = yield(builder)
107
+ end
108
+
105
109
  response = repository.search(builder)
106
110
 
107
111
  case
@@ -1,7 +1,5 @@
1
1
  # -*- encoding : utf-8 -*-
2
2
  module Blacklight::Solr
3
-
4
-
5
3
  # Pagination for facet values -- works by setting the limit to max
6
4
  # displayable. You have to ask Solr for limit+1, to get enough
7
5
  # results to see if 'more' are available'. That is, the all_facet_values
@@ -11,25 +9,7 @@ module Blacklight::Solr
11
9
  # the total values for a given facet field,
12
10
  # so we cannot know how many "pages" there are.
13
11
  #
14
- class FacetPaginator
15
- extend Deprecation
16
-
17
- self.deprecation_horizon = 'blacklight version 6.0.0'
18
-
19
- # What request keys will we use for the parameters need. Need to
20
- # make sure they do NOT conflict with catalog/index request params,
21
- # and need to make them accessible in a list so we can easily
22
- # strip em out before redirecting to catalog/index.
23
- mattr_accessor :request_keys do
24
- { sort: :'facet.sort', page: :'facet.page' }
25
- end
26
-
27
- if Rails.version < "4.1"
28
- self.request_keys = { sort: :'facet.sort', page: :'facet.page' }
29
- end
30
-
31
- attr_reader :offset, :limit, :sort
32
-
12
+ class FacetPaginator < Blacklight::FacetPaginator
33
13
  # all_facet_values is a list of facet value objects returned by solr,
34
14
  # asking solr for n+1 facet values.
35
15
  # options:
@@ -38,87 +18,10 @@ module Blacklight::Solr
38
18
  # :offset => current item offset, default 0
39
19
  # :sort => 'count' or 'index', solr tokens for facet value sorting, default 'count'.
40
20
  def initialize(all_facet_values, arguments)
41
- # to_s.to_i will conveniently default to 0 if nil
42
- @offset = arguments[:offset].to_s.to_i
43
- @limit = arguments[:limit]
44
- # count is solr's default
45
- @sort = arguments[:sort] || "count"
46
-
47
- @all = all_facet_values
48
- end
49
-
50
- # The number of records solr gave us when we asked for limit + 1 records at the current offset
51
- def total_count
52
- @all.size
53
- end
54
-
55
- def items
56
- items_for_limit(@all)
57
- end
58
-
59
- def prev_page
60
- current_page - 1 unless first_page?
61
- end
62
-
63
- def current_page
64
- if limit == 0 #check for divide by zero
65
- 1
66
- else
67
- @offset / limit + 1
68
- end
69
- end
70
-
71
- def next_page
72
- current_page + 1 unless last_page?
73
- end
74
-
75
- #@deprecated
76
- def has_next?
77
- !last_page?
78
- end
79
- deprecation_deprecate :has_next? => "use #!last_page?"
80
-
81
- #@deprecated
82
- def has_previous?
83
- !first_page?
84
- end
85
- deprecation_deprecate :has_next? => "use #!first_page?"
86
-
87
- def last_page?
88
- limit.nil? || total_count <= limit
89
- end
21
+ super
90
22
 
91
- def first_page?
92
- current_page == 1
93
- end
94
-
95
- # We're implementing total_pages so that this matches the API from kaminari, even though we can't
96
- # know the total number of pages.
97
- # https://github.com/amatsuda/kaminari/blob/v0.16.1/lib/kaminari/models/page_scope_methods.rb#L21
98
- def total_pages
99
- -1
100
- end
101
-
102
- # Pass in a desired solr facet solr key ('count' or 'index', see
103
- # http://wiki.apache.org/solr/SimpleFacetParameters#facet.limit
104
- # under facet.sort ), and your current request params.
105
- # Get back params suitable to passing to an ActionHelper method for
106
- # creating a url, to resort by that method.
107
- def params_for_resort_url(sort_method, params)
108
- # When resorting, we've got to reset the offset to start at beginning,
109
- # no way to make it make sense otherwise.
110
- params.merge(request_keys[:sort] => sort_method, request_keys[:page] => nil)
111
- end
112
-
113
- def as_json(_ = nil)
114
- { 'items' => items.as_json, 'limit' => limit, 'offset' => offset, 'sort' => sort }
23
+ # count is solr's default
24
+ @sort ||= 'count'
115
25
  end
116
-
117
- private
118
- # setting limit to nil implies no limit
119
- # @return an array of facets on the page
120
- def items_for_limit(values)
121
- limit.nil? ? values : values.take(limit)
122
- end
123
26
  end
124
27
  end
@@ -80,6 +80,8 @@ module Blacklight::SolrResponse::Spelling
80
80
  unless suggestions.nil?
81
81
  if suggestions.index("collation")
82
82
  suggestions[suggestions.index("collation") + 1]
83
+ elsif spellcheck.key?("collations")
84
+ spellcheck['collations'].last
83
85
  end
84
86
  end
85
87
  end
@@ -0,0 +1,12 @@
1
+ require 'spec_helper'
2
+
3
+ feature 'Sitelinks search box' do
4
+ scenario 'is home page' do
5
+ visit root_path
6
+ expect(page).to have_css 'script[type="application/ld+json"]'
7
+ end
8
+ scenario 'on search page' do
9
+ visit catalog_index_path q: 'book'
10
+ expect(page).to_not have_css 'script[type="application/ld+json"]'
11
+ end
12
+ end
@@ -48,65 +48,20 @@ describe BlacklightHelper do
48
48
  end
49
49
 
50
50
  describe "render_link_rel_alternates" do
51
- class MockDocumentAppHelper
52
- include Blacklight::Solr::Document
53
- end
54
- module MockExtension
55
- def self.extended(document)
56
- document.will_export_as(:weird, "application/weird")
57
- document.will_export_as(:weirder, "application/weirder")
58
- document.will_export_as(:weird_dup, "application/weird")
59
- end
60
- def export_as_weird ; "weird" ; end
61
- def export_as_weirder ; "weirder" ; end
62
- def export_as_weird_dup ; "weird_dup" ; end
63
- end
64
- MockDocumentAppHelper.use_extension(MockExtension)
65
- def mock_document_app_helper_url *args
66
- solr_document_url(*args)
67
- end
68
- before(:each) do
69
- @doc_id = "MOCK_ID1"
70
- @document = MockDocumentAppHelper.new(:id => @doc_id)
71
- render_params = {:controller => "controller", :action => "action"}
72
- allow(helper).to receive(:params).and_return(render_params)
73
- end
74
- it "generates <link rel=alternate> tags" do
51
+ let(:document) { double }
52
+ let(:result) { double }
53
+ let(:presenter) { Blacklight::DocumentPresenter.new(document, self) }
75
54
 
76
- response = render_link_rel_alternates(@document)
77
-
78
- tmp_value = Capybara.ignore_hidden_elements
79
- Capybara.ignore_hidden_elements = false
80
- @document.export_formats.each_pair do |format, spec|
81
- expect(response).to have_selector("link[href$='.#{ format }']") do |matches|
82
- expect(matches).to have(1).match
83
- tag = matches[0]
84
- expect(tag.attributes["rel"].value).to eq "alternate"
85
- expect(tag.attributes["title"].value).to eq format.to_s
86
- expect(tag.attributes["href"].value).to eq mock_document_app_helper_url(@document, :format =>format)
87
- end
88
- end
89
- Capybara.ignore_hidden_elements = tmp_value
90
- end
91
- it "respects :unique=>true" do
92
- response = render_link_rel_alternates(@document, :unique => true)
93
- tmp_value = Capybara.ignore_hidden_elements
94
- Capybara.ignore_hidden_elements = false
95
- expect(response).to have_selector("link[type='application/weird']", :count => 1)
96
- Capybara.ignore_hidden_elements = tmp_value
97
- end
98
- it "excludes formats from :exclude" do
99
- response = render_link_rel_alternates(@document, :exclude => [:weird_dup])
55
+ before { allow(helper).to receive(:presenter).and_return(presenter) }
100
56
 
101
- tmp_value = Capybara.ignore_hidden_elements
102
- Capybara.ignore_hidden_elements = false
103
- expect(response).to_not have_selector("link[href$='.weird_dup']")
104
- Capybara.ignore_hidden_elements = tmp_value
57
+ it "generates <link rel=alternate> tags" do
58
+ expect(presenter).to receive(:link_rel_alternates).and_return(result)
59
+ expect(helper.render_link_rel_alternates(document)).to eq result
105
60
  end
106
61
 
107
- it "should be html safe" do
108
- response = render_link_rel_alternates(@document)
109
- expect(response).to be_html_safe
62
+ it "sends parameters" do
63
+ expect(presenter).to receive(:link_rel_alternates).with(unique: true).and_return(result)
64
+ expect(helper.render_link_rel_alternates(document, unique: true)).to eq result
110
65
  end
111
66
  end
112
67
 
@@ -323,4 +323,43 @@ describe CatalogHelper do
323
323
  end
324
324
  end
325
325
 
326
+ describe "#render_search_to_page_title_filter" do
327
+ before do
328
+ allow(helper).to receive(:blacklight_config).and_return(blacklight_config)
329
+ end
330
+
331
+ let :blacklight_config do
332
+ Blacklight::Configuration.new
333
+ end
334
+
335
+ it "should render a facet with a single value" do
336
+ expect(helper.render_search_to_page_title_filter('foo', ['bar'])).to eq "Foo: bar"
337
+ end
338
+
339
+ it "should render a facet with two values" do
340
+ expect(helper.render_search_to_page_title_filter('foo', ['bar', 'baz'])).to eq "Foo: bar and baz"
341
+ end
342
+
343
+ it "should render a facet with more than two values" do
344
+ expect(helper.render_search_to_page_title_filter('foo', ['bar', 'baz', 'foobar'])).to eq "Foo: 3 selected"
345
+ end
346
+ end
347
+
348
+ describe "#render_search_to_page_title" do
349
+ before do
350
+ allow(helper).to receive(:blacklight_config).and_return(blacklight_config)
351
+ allow(helper).to receive(:default_search_field).and_return(Blacklight::Configuration::SearchField.new(:key => 'default_search_field', :display_label => 'Default'))
352
+ allow(helper).to receive(:label_for_search_field).with(nil).and_return('')
353
+ end
354
+
355
+ let :blacklight_config do
356
+ Blacklight::Configuration.new
357
+ end
358
+
359
+ let(:params) { {'q' => 'foobar', "f" => {"format" => ["Book"]}} }
360
+
361
+ it "should render a page title" do
362
+ expect(helper.render_search_to_page_title(params)).to eq "foobar / Format: Book"
363
+ end
364
+ end
326
365
  end
@@ -1,6 +1,6 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe 'Blacklight::Solr::FacetPaginator' do
3
+ describe Blacklight::FacetPaginator do
4
4
 
5
5
  let(:f1) { Blacklight::SolrResponse::Facets::FacetItem.new(hits: '792', value: 'Book') }
6
6
  let(:f2) { Blacklight::SolrResponse::Facets::FacetItem.new(hits: '65', value: 'Musical Score') }
@@ -14,7 +14,7 @@ describe 'Blacklight::Solr::FacetPaginator' do
14
14
  let(:limit) { 6 }
15
15
 
16
16
  context 'on the first page of two pages' do
17
- subject { Blacklight::Solr::FacetPaginator.new(seven_facet_values, limit: limit) }
17
+ subject { described_class.new(seven_facet_values, limit: limit) }
18
18
  it { should be_first_page }
19
19
  it { should_not be_last_page }
20
20
  its(:current_page) { should eq 1 }
@@ -26,7 +26,7 @@ describe 'Blacklight::Solr::FacetPaginator' do
26
26
  end
27
27
 
28
28
  context 'on the last page of two pages' do
29
- subject { Blacklight::Solr::FacetPaginator.new([f7], offset: 6, limit: limit) }
29
+ subject { described_class.new([f7], offset: 6, limit: limit) }
30
30
  it { should_not be_first_page }
31
31
  it { should be_last_page }
32
32
  its(:current_page) { should eq 2 }
@@ -38,7 +38,7 @@ describe 'Blacklight::Solr::FacetPaginator' do
38
38
  end
39
39
 
40
40
  context 'on the second page of three pages' do
41
- subject { Blacklight::Solr::FacetPaginator.new(seven_facet_values, offset: 6, limit: limit) }
41
+ subject { described_class.new(seven_facet_values, offset: 6, limit: limit) }
42
42
  it { should_not be_first_page }
43
43
  it { should_not be_last_page }
44
44
  its(:current_page) { should eq 2 }
@@ -50,15 +50,15 @@ describe 'Blacklight::Solr::FacetPaginator' do
50
50
  end
51
51
 
52
52
  context 'on the first page of one page' do
53
- subject { Blacklight::Solr::FacetPaginator.new(six_facet_values, offset: 0, limit: limit) }
53
+ subject { described_class.new(six_facet_values, offset: 0, limit: limit) }
54
54
  it { should be_first_page }
55
55
  it { should be_last_page }
56
56
  end
57
57
 
58
58
  describe "params_for_resort_url" do
59
- let(:sort_key) { Blacklight::Solr::FacetPaginator.request_keys[:sort] }
60
- let(:page_key) { Blacklight::Solr::FacetPaginator.request_keys[:page] }
61
- subject { Blacklight::Solr::FacetPaginator.new([], offset: 100, limit: limit, sort: 'index') }
59
+ let(:sort_key) { described_class.request_keys[:sort] }
60
+ let(:page_key) { described_class.request_keys[:page] }
61
+ subject { described_class.new([], offset: 100, limit: limit, sort: 'index') }
62
62
 
63
63
  it 'should know a manually set sort, and produce proper sort url' do
64
64
  expect(subject.sort).to eq 'index'
@@ -71,7 +71,7 @@ describe 'Blacklight::Solr::FacetPaginator' do
71
71
  end
72
72
 
73
73
  context "for a nil :limit" do
74
- subject { Blacklight::Solr::FacetPaginator.new(seven_facet_values, offset: 0, limit: nil) }
74
+ subject { described_class.new(seven_facet_values, offset: 0, limit: nil) }
75
75
  it "should return all the items" do
76
76
  expect(subject.items).to eq seven_facet_values
77
77
  end
@@ -79,16 +79,16 @@ describe 'Blacklight::Solr::FacetPaginator' do
79
79
  end
80
80
 
81
81
  describe "#as_json" do
82
- subject { Blacklight::Solr::FacetPaginator.new([f1], offset: 0, limit: nil).as_json }
82
+ subject { described_class.new([f1], offset: 0, limit: nil).as_json }
83
83
  it "should be well structured" do
84
84
  expect(subject).to eq("items" => [{"hits"=>"792", "value"=>"Book"}], "limit" => nil,
85
- "offset" => 0, "sort" => "count")
85
+ "offset" => 0, "sort" => nil)
86
86
  end
87
87
  end
88
88
 
89
89
  describe "#total_pages" do
90
90
  # this method is just for API compatability with kaminari 0.16.1
91
- subject { Blacklight::Solr::FacetPaginator.new([f1], offset: 0, limit: nil).total_pages }
91
+ subject { described_class.new([f1], offset: 0, limit: nil).total_pages }
92
92
  it { should eq -1 }
93
93
  end
94
94
  end