blacklight 5.14.0 → 5.15.0

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