blacklight 7.16.0 → 7.17.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 (61) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +4 -0
  3. data/VERSION +1 -1
  4. data/app/components/blacklight/advanced_search_form_component.html.erb +9 -3
  5. data/app/components/blacklight/advanced_search_form_component.rb +48 -35
  6. data/app/components/blacklight/constraints_component.html.erb +19 -3
  7. data/app/components/blacklight/constraints_component.rb +5 -1
  8. data/app/components/blacklight/content_areas_shim.rb +12 -0
  9. data/app/components/blacklight/document/action_component.rb +4 -0
  10. data/app/components/blacklight/document/actions_component.html.erb +3 -5
  11. data/app/components/blacklight/document/actions_component.rb +14 -1
  12. data/app/components/blacklight/document_component.html.erb +4 -7
  13. data/app/components/blacklight/document_component.rb +73 -73
  14. data/app/components/blacklight/document_metadata_component.html.erb +2 -2
  15. data/app/components/blacklight/document_metadata_component.rb +13 -2
  16. data/app/components/blacklight/document_title_component.html.erb +17 -0
  17. data/app/components/blacklight/document_title_component.rb +59 -0
  18. data/app/components/blacklight/facet_field_checkboxes_component.html.erb +2 -2
  19. data/app/components/blacklight/facet_field_component.rb +4 -1
  20. data/app/components/blacklight/facet_field_list_component.html.erb +2 -2
  21. data/app/components/blacklight/facet_field_no_layout_component.rb +4 -1
  22. data/app/components/blacklight/metadata_field_component.html.erb +2 -2
  23. data/app/components/blacklight/metadata_field_layout_component.html.erb +3 -1
  24. data/app/components/blacklight/metadata_field_layout_component.rb +26 -1
  25. data/app/components/blacklight/response/view_type_button_component.html.erb +4 -0
  26. data/app/components/blacklight/response/view_type_button_component.rb +36 -0
  27. data/app/components/blacklight/response/view_type_component.html.erb +2 -5
  28. data/app/components/blacklight/response/view_type_component.rb +9 -13
  29. data/app/components/blacklight/search_bar_component.rb +4 -1
  30. data/app/components/blacklight/system/dropdown_component.html.erb +4 -7
  31. data/app/components/blacklight/system/dropdown_component.rb +24 -0
  32. data/app/components/blacklight/system/flash_message_component.html.erb +1 -1
  33. data/app/components/blacklight/system/flash_message_component.rb +7 -1
  34. data/app/components/blacklight/system/modal_component.rb +7 -1
  35. data/app/views/catalog/_citation.html.erb +1 -1
  36. data/app/views/catalog/_document.html.erb +2 -2
  37. data/app/views/catalog/_facet_layout.html.erb +2 -2
  38. data/app/views/catalog/_show_main_content.html.erb +3 -3
  39. data/app/views/catalog/email.html.erb +2 -2
  40. data/app/views/catalog/email_success.html.erb +1 -1
  41. data/app/views/catalog/facet.html.erb +3 -3
  42. data/app/views/catalog/sms.html.erb +2 -2
  43. data/app/views/catalog/sms_success.html.erb +1 -1
  44. data/blacklight.gemspec +1 -1
  45. data/config/locales/blacklight.de.yml +2 -2
  46. data/lib/blacklight/engine.rb +3 -1
  47. data/lib/blacklight/solr/facet_paginator.rb +2 -0
  48. data/lib/blacklight/solr/request.rb +31 -0
  49. data/lib/blacklight/solr/response.rb +2 -16
  50. data/lib/blacklight/solr/response/facets.rb +76 -22
  51. data/lib/blacklight/solr/response/params.rb +104 -0
  52. data/lib/blacklight/solr/search_builder_behavior.rb +49 -29
  53. data/lib/generators/blacklight/assets_generator.rb +6 -2
  54. data/lib/generators/blacklight/user_generator.rb +1 -1
  55. data/spec/components/blacklight/document_component_spec.rb +3 -3
  56. data/spec/models/blacklight/solr/facet_paginator_spec.rb +4 -0
  57. data/spec/models/blacklight/solr/request_spec.rb +62 -29
  58. data/spec/models/blacklight/solr/response/facets_spec.rb +109 -0
  59. data/spec/models/blacklight/solr/response_spec.rb +10 -0
  60. data/spec/models/blacklight/solr/search_builder_spec.rb +17 -0
  61. metadata +14 -8
@@ -1,5 +1,5 @@
1
1
  <%= render Blacklight::System::ModalComponent.new do |component| %>
2
- <% component.with(:title, t('blacklight.tools.citation')) %>
2
+ <% component.title { t('blacklight.tools.citation') } %>
3
3
 
4
4
  <%= render Blacklight::Document::CitationComponent.with_collection(@documents) if @documents.present? %>
5
5
  <% end %>
@@ -1,6 +1,6 @@
1
1
  <% # container for a single doc -%>
2
- <%= render (blacklight_config.view_config(document_index_view_type).document_component || Blacklight::DocumentComponent).new(document: document, counter: document_counter_with_offset(document_counter)) do |component| %>
3
- <% component.with(blacklight_config.view_config(document_index_view_type).document_component.blank? && blacklight_config.view_config(document_index_view_type).partials.any? ? :body : :partials) do %>
2
+ <%= render (blacklight_config.view_config(document_index_view_type).document_component || Blacklight::DocumentComponent).new(presenter: document_presenter(document), counter: document_counter_with_offset(document_counter)) do |component| %>
3
+ <% component.public_send(blacklight_config.view_config(document_index_view_type).document_component.blank? && blacklight_config.view_config(document_index_view_type).partials.any? ? :body : :partial) do %>
4
4
  <%= render_document_partials document, blacklight_config.view_config(document_index_view_type).partials, component: component, document_counter: document_counter %>
5
5
  <% end %>
6
6
  <% end %>
@@ -1,8 +1,8 @@
1
1
  <%= render(Blacklight::FacetFieldComponent.new(facet_field: facet_field_presenter(facet_field, nil))) do |component| %>
2
- <% component.with(:label) do %>
2
+ <% component.label do %>
3
3
  <%= facet_field_label(facet_field.key) %>
4
4
  <% end %>
5
- <% component.with(:body) do %>
5
+ <% component.body do %>
6
6
  <%= yield %>
7
7
  <% end %>
8
8
  <% end %>
@@ -3,8 +3,8 @@
3
3
  <% @page_title = t('blacklight.search.show.title', document_title: Deprecation.silence(Blacklight::BlacklightHelperBehavior) { document_show_html_title }, application_name: application_name).html_safe %>
4
4
  <% content_for(:head) { render_link_rel_alternates } %>
5
5
 
6
- <%= render (blacklight_config.view_config(:show).document_component || Blacklight::DocumentComponent).new(document: @document, component: :div, title_component: :h1, show: true) do |component| %>
7
- <% component.with(:footer) do %>
6
+ <%= render (blacklight_config.view_config(:show).document_component || Blacklight::DocumentComponent).new(presenter: document_presenter(@document), component: :div, title_component: :h1, show: true) do |component| %>
7
+ <% component.footer do %>
8
8
  <% if @document.respond_to?(:export_as_openurl_ctx_kev) %>
9
9
  <!--
10
10
  // COinS, for Zotero among others.
@@ -17,7 +17,7 @@
17
17
 
18
18
  <%# Use :body for complete backwards compatibility (overriding the component body markup),
19
19
  but if the app explicitly opted-in to components, make the partials data available as :partials to ease migrations pain %>
20
- <% component.with(blacklight_config.view_config(:show).document_component.blank? && blacklight_config.view_config(:show).partials.any? ? :body : :partials) do %>
20
+ <% component.public_send(blacklight_config.view_config(:show).document_component.blank? && blacklight_config.view_config(:show).partials.any? ? :body : :partial) do %>
21
21
  <div id="doc_<%= @document.id.to_s.parameterize %>">
22
22
  <%= render_document_partials @document, blacklight_config.view_config(:show).partials, component: component %>
23
23
  </div>
@@ -1,7 +1,7 @@
1
1
  <%= render Blacklight::System::ModalComponent.new do |component| %>
2
- <% component.with(:title, t('blacklight.email.form.title')) %>
2
+ <% component.title { t('blacklight.email.form.title') } %>
3
3
 
4
- <% component.with(:body) do %>
4
+ <% component.body do %>
5
5
  <%= render 'email_form' %>
6
6
  <% end %>
7
7
  <% end %>
@@ -1,5 +1,5 @@
1
1
  <%= render Blacklight::System::ModalComponent.new do |component| %>
2
- <% component.with(:title, t('blacklight.email.form.title')) %>
2
+ <% component.title { t('blacklight.email.form.title') } %>
3
3
 
4
4
  <%= render partial: '/shared/flash_msg' %>
5
5
  <span data-blacklight-modal="close"></span>
@@ -1,11 +1,11 @@
1
1
  <%= render Blacklight::System::ModalComponent.new do |component| %>
2
- <% component.with(:prefix) do %>
2
+ <% component.prefix do %>
3
3
  <div class="facet-pagination top row justify-content-between">
4
4
  <%= render :partial=>'facet_pagination' %>
5
5
  </div>
6
6
  <% end %>
7
7
 
8
- <% component.with(:title, facet_field_label(@facet.key)) %>
8
+ <% component.title { facet_field_label(@facet.key) } %>
9
9
 
10
10
  <%= render partial: 'facet_index_navigation' if @facet.index_range && @display_facet.index? %>
11
11
 
@@ -13,7 +13,7 @@
13
13
  <%= render_facet_limit(@display_facet, layout: false) %>
14
14
  </div>
15
15
 
16
- <% component.with(:footer) do %>
16
+ <% component.footer do %>
17
17
  <div class="facet-pagination bottom row justify-content-between">
18
18
  <%= render :partial=>'facet_pagination' %>
19
19
  </div>
@@ -1,7 +1,7 @@
1
1
  <%= render Blacklight::System::ModalComponent.new do |component| %>
2
- <% component.with(:title, t('blacklight.sms.form.title')) %>
2
+ <% component.title { t('blacklight.sms.form.title') } %>
3
3
 
4
- <% component.with(:body) do %>
4
+ <% component.body do %>
5
5
  <%= render 'sms_form' %>
6
6
  <% end %>
7
7
  <% end %>
@@ -1,5 +1,5 @@
1
1
  <%= render Blacklight::System::ModalComponent.new do |component| %>
2
- <% component.with(:title, t('blacklight.sms.form.title')) %>
2
+ <% component.title { t('blacklight.sms.form.title') } %>
3
3
 
4
4
  <%= render partial: '/shared/flash_msg' %>
5
5
  <span data-blacklight-modal="close"></span>
data/blacklight.gemspec CHANGED
@@ -32,7 +32,7 @@ Gem::Specification.new do |s|
32
32
  s.add_dependency "deprecation"
33
33
  s.add_dependency "i18n", '>= 1.7.0' # added named parameters
34
34
  s.add_dependency "ostruct", '>= 0.3.2'
35
- s.add_dependency "view_component", '>= 2.23.0'
35
+ s.add_dependency "view_component", '>= 2.28.0'
36
36
 
37
37
  s.add_development_dependency "rsolr", ">= 1.0.6", "< 3" # Library for interacting with rSolr.
38
38
  s.add_development_dependency "rspec-rails", "~> 4.0.0.beta2"
@@ -3,7 +3,7 @@ de:
3
3
  pagination:
4
4
  first: '&laquo; Erste'
5
5
  last: 'Letzte &raquo;'
6
- previous: '&laquo; Voherige'
6
+ previous: '&laquo; Vorherige'
7
7
  next: 'Nächste &raquo;'
8
8
  truncate: '…'
9
9
  aria:
@@ -16,7 +16,7 @@ de:
16
16
  go_to_previous_page: Zurück zur letzten Seite
17
17
 
18
18
  pagination_compact:
19
- previous: '&laquo; Voherige'
19
+ previous: '&laquo; Vorherige'
20
20
  next: 'Nächste &raquo;'
21
21
 
22
22
  blacklight:
@@ -8,7 +8,9 @@ module Blacklight
8
8
  # BlacklightHelper is needed by all helpers, so we inject it
9
9
  # into action view base here.
10
10
  initializer 'blacklight.helpers' do
11
- ActionView::Base.include BlacklightHelper
11
+ config.after_initialize do
12
+ ActionView::Base.include BlacklightHelper
13
+ end
12
14
  end
13
15
 
14
16
  # This makes our rake tasks visible.
@@ -20,6 +20,8 @@ module Blacklight::Solr
20
20
  def initialize(all_facet_values, arguments = {})
21
21
  super
22
22
 
23
+ @sort = arguments[:sort].keys.first.to_s if arguments[:sort].is_a? Hash
24
+
23
25
  # count is solr's default
24
26
  @sort ||= if @limit.to_i > 0
25
27
  'count'
@@ -17,6 +17,37 @@ class Blacklight::Solr::Request < ActiveSupport::HashWithIndifferentAccess
17
17
  end
18
18
  end
19
19
 
20
+ def append_query(query)
21
+ if self['q'] || dig(:json, :query, :bool)
22
+ self[:json] ||= { query: { bool: { must: [] } } }
23
+ self[:json][:query] ||= { bool: { must: [] } }
24
+ self[:json][:query][:bool][:must] << query
25
+
26
+ if self['q']
27
+ self[:json][:query][:bool][:must] << self['q']
28
+ delete 'q'
29
+ end
30
+ else
31
+ self['q'] = query
32
+ end
33
+ end
34
+
35
+ def append_boolean_query(bool_operator, query)
36
+ return if query.blank?
37
+
38
+ self[:json] ||= { query: { bool: { bool_operator => [] } } }
39
+ self[:json][:query] ||= { bool: { bool_operator => [] } }
40
+ self[:json][:query][:bool][bool_operator] ||= []
41
+
42
+ if self['q']
43
+ self[:json][:query][:bool][:must] ||= []
44
+ self[:json][:query][:bool][:must] << self['q']
45
+ delete 'q'
46
+ end
47
+
48
+ self[:json][:query][:bool][bool_operator] << query
49
+ end
50
+
20
51
  def append_filter_query(query)
21
52
  self['fq'] << query
22
53
  end
@@ -9,6 +9,7 @@ class Blacklight::Solr::Response < ActiveSupport::HashWithIndifferentAccess
9
9
  autoload :MoreLikeThis
10
10
  autoload :GroupResponse
11
11
  autoload :Group
12
+ autoload :Params
12
13
  end
13
14
 
14
15
  include PaginationMethods
@@ -16,6 +17,7 @@ class Blacklight::Solr::Response < ActiveSupport::HashWithIndifferentAccess
16
17
  include Facets
17
18
  include Response
18
19
  include MoreLikeThis
20
+ include Params
19
21
 
20
22
  attr_reader :request_params
21
23
  attr_accessor :blacklight_config, :options
@@ -33,22 +35,6 @@ class Blacklight::Solr::Response < ActiveSupport::HashWithIndifferentAccess
33
35
  self['responseHeader'] || {}
34
36
  end
35
37
 
36
- def params
37
- header['params'] || request_params
38
- end
39
-
40
- def start
41
- params[:start].to_i
42
- end
43
-
44
- def rows
45
- params[:rows].to_i
46
- end
47
-
48
- def sort
49
- params[:sort]
50
- end
51
-
52
38
  def documents
53
39
  @documents ||= (response['docs'] || []).collect { |doc| document_factory.build(doc, self, options) }
54
40
  end
@@ -29,6 +29,7 @@ module Blacklight::Solr::Response::Facets
29
29
  # represents a facet; which is a field and its values
30
30
  class FacetField
31
31
  attr_reader :name, :items
32
+ attr_accessor :missing
32
33
 
33
34
  def initialize name, items, options = {}
34
35
  @name = name
@@ -52,6 +53,14 @@ module Blacklight::Solr::Response::Facets
52
53
  @options[:prefix] || solr_default_prefix
53
54
  end
54
55
 
56
+ def type
57
+ @options[:type] || 'terms'
58
+ end
59
+
60
+ def data
61
+ @options[:data] || {}
62
+ end
63
+
55
64
  def index?
56
65
  sort == 'index'
57
66
  end
@@ -90,7 +99,7 @@ module Blacklight::Solr::Response::Facets
90
99
  # Get all the Solr facet data (fields, queries, pivots) as a hash keyed by
91
100
  # both the Solr field name and/or by the blacklight field name
92
101
  def aggregations
93
- @aggregations ||= {}.merge(facet_field_aggregations).merge(facet_query_aggregations).merge(facet_pivot_aggregations)
102
+ @aggregations ||= {}.merge(facet_field_aggregations).merge(facet_query_aggregations).merge(facet_pivot_aggregations).merge(json_facet_aggregations)
94
103
  end
95
104
 
96
105
  def facet_counts
@@ -159,19 +168,24 @@ module Blacklight::Solr::Response::Facets
159
168
  items = values.map do |value, hits|
160
169
  i = FacetItem.new(value: value, hits: hits)
161
170
 
162
- # solr facet.missing serialization
171
+ # legacy solr facet.missing serialization
163
172
  if value.nil?
164
173
  i.label = I18n.t(:"blacklight.search.fields.facet.missing.#{facet_field_name}", default: [:"blacklight.search.facets.missing"])
165
174
  i.fq = "-#{facet_field_name}:[* TO *]"
175
+ i.missing = true
166
176
  end
167
177
 
168
178
  i
169
179
  end
170
180
 
171
181
  options = facet_field_aggregation_options(facet_field_name)
172
- hash[facet_field_name] = FacetField.new(facet_field_name,
173
- items,
174
- options)
182
+ facet_field = FacetField.new(facet_field_name, items, options)
183
+
184
+ if values[nil]
185
+ facet_field.missing = items.find(&:missing)
186
+ end
187
+
188
+ hash[facet_field_name] = facet_field
175
189
 
176
190
  # alias all the possible blacklight config names..
177
191
  blacklight_config.facet_fields.select { |_k, v| v.field == facet_field_name }.each_key do |key|
@@ -180,23 +194,6 @@ module Blacklight::Solr::Response::Facets
180
194
  end
181
195
  end
182
196
 
183
- def facet_field_aggregation_options(facet_field_name)
184
- options = {}
185
- options[:sort] = (params[:"f.#{facet_field_name}.facet.sort"] || params[:'facet.sort'])
186
- if params[:"f.#{facet_field_name}.facet.limit"] || params[:"facet.limit"]
187
- options[:limit] = (params[:"f.#{facet_field_name}.facet.limit"] || params[:"facet.limit"]).to_i
188
- end
189
-
190
- if params[:"f.#{facet_field_name}.facet.offset"] || params[:'facet.offset']
191
- options[:offset] = (params[:"f.#{facet_field_name}.facet.offset"] || params[:'facet.offset']).to_i
192
- end
193
-
194
- if params[:"f.#{facet_field_name}.facet.prefix"] || params[:'facet.prefix']
195
- options[:prefix] = (params[:"f.#{facet_field_name}.facet.prefix"] || params[:'facet.prefix'])
196
- end
197
- options
198
- end
199
-
200
197
  ##
201
198
  # Aggregate Solr's facet_query response into the virtual facet fields defined
202
199
  # in the blacklight configuration
@@ -211,12 +208,28 @@ module Blacklight::Solr::Response::Facets
211
208
  Blacklight::Solr::Response::Facets::FacetItem.new(value: key, hits: hits, label: facet_field.query[key][:label])
212
209
  end
213
210
 
211
+ items += facet_query_aggregations_from_json(facet_field)
212
+
214
213
  items = items.sort_by(&:hits).reverse if facet_field.sort && facet_field.sort.to_sym == :count
215
214
 
216
215
  hash[field_name] = Blacklight::Solr::Response::Facets::FacetField.new field_name, items
217
216
  end
218
217
  end
219
218
 
219
+ def facet_query_aggregations_from_json(facet_field)
220
+ return [] unless self['facets']
221
+
222
+ salient_facet_queries = facet_field.query.map { |_k, x| x[:fq] }
223
+
224
+ relevant_facet_data = self['facets'].select { |k, _v| salient_facet_queries.include?(k) }.reject { |_key, data| data['count'].zero? }
225
+
226
+ relevant_facet_data.map do |key, data|
227
+ salient_fields = facet_field.query.select { |_key, val| val[:fq] == key }
228
+ facet_key = ((salient_fields.keys if salient_fields.respond_to? :keys) || salient_fields.first).first
229
+ Blacklight::Solr::Response::Facets::FacetItem.new(value: facet_key, hits: data[:count], label: facet_field.query[facet_key][:label])
230
+ end
231
+ end
232
+
220
233
  ##
221
234
  # Convert Solr's facet_pivot response into
222
235
  # a hash of Blacklight::Solr::Response::Facet::FacetField objects
@@ -244,4 +257,45 @@ module Blacklight::Solr::Response::Facets
244
257
 
245
258
  Blacklight::Solr::Response::Facets::FacetItem.new(value: lst[:value], hits: lst[:count], field: lst[:field], items: items, fq: parent_fq)
246
259
  end
260
+
261
+ def construct_json_nested_facet_fields(bucket, parent_fq = {})
262
+ bucket.select { |_, nested| nested.is_a?(Hash) && nested.key?('buckets') }.map do |facet_field_name, nested|
263
+ nested['buckets'].map do |subbucket|
264
+ i = Blacklight::Solr::Response::Facets::FacetItem.new(field: facet_field_name, value: subbucket['val'], hits: subbucket['count'], fq: parent_fq, data: subbucket)
265
+
266
+ i.items = construct_json_nested_facet_fields(subbucket, parent_fq.merge(key => subbucket['val'])) if has_json_nested_facets?(subbucket)
267
+ i
268
+ end
269
+ end.flatten
270
+ end
271
+
272
+ def has_json_nested_facets?(bucket)
273
+ bucket.any? { |_, nested| nested.is_a?(Hash) && nested.key?('buckets') }
274
+ end
275
+
276
+ def json_facet_aggregations
277
+ return {} unless self['facets']
278
+
279
+ self['facets'].each_with_object({}) do |(facet_field_name, data), hash|
280
+ next if facet_field_name == 'count'
281
+
282
+ items = (data['buckets'] || []).map do |bucket|
283
+ i = Blacklight::Solr::Response::Facets::FacetItem.new(value: bucket['val'], hits: bucket['count'], data: bucket)
284
+
285
+ i.items = construct_json_nested_facet_fields(bucket, facet_field_name => bucket['val']) if has_json_nested_facets?(bucket)
286
+
287
+ i
288
+ end
289
+
290
+ options = facet_field_aggregation_options(facet_field_name).merge(data: data)
291
+ facet_field = FacetField.new(facet_field_name, items, options)
292
+
293
+ facet_field.missing = Blacklight::Solr::Response::Facets::FacetItem.new(
294
+ hits: data.dig('missing', 'count'),
295
+ data: data['missing']
296
+ ) if data['missing']
297
+
298
+ hash[facet_field_name] = facet_field
299
+ end
300
+ end
247
301
  end
@@ -0,0 +1,104 @@
1
+ # frozen_string_literal: true
2
+ module Blacklight::Solr::Response::Params
3
+ # From https://solr.apache.org/guide/8_8/json-request-api.html#supported-properties-and-syntax
4
+ QUERY_PARAMETER_TO_JSON_PARAMETER_MAPPING = {
5
+ q: :query,
6
+ fq: :filter,
7
+ start: :offset,
8
+ rows: :limit,
9
+ fl: :fields,
10
+ sort: :sort
11
+ }.freeze
12
+
13
+ def params
14
+ header['params'] || request_params
15
+ end
16
+
17
+ def start
18
+ search_builder&.start || single_valued_param(:start).to_i
19
+ end
20
+
21
+ def rows
22
+ search_builder&.rows || single_valued_param(:rows).to_i
23
+ end
24
+
25
+ def sort
26
+ search_builder&.sort || single_valued_param(:sort)
27
+ end
28
+
29
+ def facet_field_aggregation_options(facet_field_name)
30
+ defaults = {
31
+ sort: single_valued_param(:'facet.sort'),
32
+ limit: single_valued_param(:"facet.limit")&.to_i || 100,
33
+ offset: single_valued_param(:"facet.offset")&.to_i || 0,
34
+ prefix: single_valued_param(:"facet.prefix")
35
+ }
36
+
37
+ json_facet = json_params.dig('facet', facet_field_name)&.slice(:limit, :offset, :prefix, :sort)&.symbolize_keys || {}
38
+
39
+ param_facet = {
40
+ sort: single_valued_param(:"f.#{facet_field_name}.facet.sort"),
41
+ limit: single_valued_param(:"f.#{facet_field_name}.facet.limit")&.to_i,
42
+ offset: single_valued_param(:"f.#{facet_field_name}.facet.offset")&.to_i,
43
+ prefix: single_valued_param(:"f.#{facet_field_name}.facet.prefix")
44
+ }.reject { |_k, v| v.nil? }
45
+
46
+ options = defaults.merge(json_facet).merge(param_facet)
47
+ options[:sort] ||= options[:limit].positive? ? 'count' : 'index'
48
+
49
+ options
50
+ end
51
+
52
+ private
53
+
54
+ def search_builder
55
+ request_params if request_params.is_a?(Blacklight::SearchBuilder)
56
+ end
57
+
58
+ # Extract JSON Request API parameters from the response header or the request itself
59
+ def json_params
60
+ encoded_json_params = header&.dig('params', 'json')
61
+
62
+ return request_params['json'] || {} if encoded_json_params.blank?
63
+
64
+ @json_params ||= JSON.parse(encoded_json_params).with_indifferent_access
65
+ end
66
+
67
+ # Handle merging solr parameters from the myriad of ways they may be expressed by applying the single-value
68
+ # precedence logic:
69
+ #
70
+ # From https://solr.apache.org/guide/8_8/json-request-api.html#json-parameter-merging :
71
+ # When multiple parameter values conflict with one another a single value is chosen based on the following precedence rules:
72
+ # - Traditional query parameters (q, rows, etc.) take first precedence and are used over any other specified values.
73
+ # - json-prefixed query parameters are considered next.
74
+ # - Values specified in the JSON request body have the lowest precedence and are only used if specified nowhere else.
75
+ #
76
+ # @param [String] key the solr parameter to use
77
+ def single_valued_param(key)
78
+ json_key = QUERY_PARAMETER_TO_JSON_PARAMETER_MAPPING[key]
79
+
80
+ params[key] ||
81
+ params["json.#{key}"] ||
82
+ json_params[json_key || key] ||
83
+ json_params.dig(:params, key) ||
84
+ json_params.dig(:params, "json.#{key}")
85
+ end
86
+
87
+ # Merge together multi-valued solr parameters from the myriad of ways they may be expressed.
88
+ # Unlike single-valued parameters, this merges all the values across the params.
89
+ #
90
+ # @param [String] key the solr parameter to use
91
+ def multivalued_param(key)
92
+ json_key = QUERY_PARAMETER_TO_JSON_PARAMETER_MAPPING[key]
93
+
94
+ [
95
+ params[key],
96
+ params["json.#{key}"],
97
+ json_params[json_key || key],
98
+ json_params.dig(:params, key),
99
+ json_params.dig(:params, "json.#{key}")
100
+ ].select(&:present?).inject([]) do |memo, arr|
101
+ memo.concat(Array.wrap(arr))
102
+ end
103
+ end
104
+ end