blacklight 7.15.1 → 7.17.2

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 (79) 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.html.erb +1 -1
  10. data/app/components/blacklight/document/action_component.rb +6 -1
  11. data/app/components/blacklight/document/actions_component.html.erb +3 -5
  12. data/app/components/blacklight/document/actions_component.rb +16 -2
  13. data/app/components/blacklight/document_component.html.erb +4 -7
  14. data/app/components/blacklight/document_component.rb +73 -73
  15. data/app/components/blacklight/document_metadata_component.html.erb +2 -2
  16. data/app/components/blacklight/document_metadata_component.rb +13 -2
  17. data/app/components/blacklight/document_title_component.html.erb +17 -0
  18. data/app/components/blacklight/document_title_component.rb +59 -0
  19. data/app/components/blacklight/facet_field_checkboxes_component.html.erb +2 -2
  20. data/app/components/blacklight/facet_field_component.rb +4 -1
  21. data/app/components/blacklight/facet_field_list_component.html.erb +2 -2
  22. data/app/components/blacklight/facet_field_no_layout_component.rb +4 -1
  23. data/app/components/blacklight/metadata_field_component.html.erb +2 -2
  24. data/app/components/blacklight/metadata_field_layout_component.html.erb +3 -1
  25. data/app/components/blacklight/metadata_field_layout_component.rb +26 -1
  26. data/app/components/blacklight/response/view_type_button_component.html.erb +4 -0
  27. data/app/components/blacklight/response/view_type_button_component.rb +38 -0
  28. data/app/components/blacklight/response/view_type_component.html.erb +2 -5
  29. data/app/components/blacklight/response/view_type_component.rb +9 -13
  30. data/app/components/blacklight/search_bar_component.rb +9 -2
  31. data/app/components/blacklight/system/dropdown_component.html.erb +4 -7
  32. data/app/components/blacklight/system/dropdown_component.rb +24 -0
  33. data/app/components/blacklight/system/flash_message_component.html.erb +1 -1
  34. data/app/components/blacklight/system/flash_message_component.rb +7 -1
  35. data/app/components/blacklight/system/modal_component.rb +7 -1
  36. data/app/controllers/concerns/blacklight/catalog.rb +1 -1
  37. data/app/helpers/blacklight/blacklight_helper_behavior.rb +3 -4
  38. data/app/helpers/blacklight/catalog_helper_behavior.rb +2 -0
  39. data/app/helpers/blacklight/component_helper_behavior.rb +2 -2
  40. data/app/helpers/blacklight/configuration_helper_behavior.rb +2 -2
  41. data/app/presenters/blacklight/document_presenter.rb +8 -4
  42. data/app/services/blacklight/search_service.rb +1 -1
  43. data/app/views/bookmarks/_tools.html.erb +1 -1
  44. data/app/views/catalog/_citation.html.erb +1 -1
  45. data/app/views/catalog/_document.html.erb +2 -2
  46. data/app/views/catalog/_facet_layout.html.erb +2 -2
  47. data/app/views/catalog/_show_main_content.html.erb +3 -3
  48. data/app/views/catalog/email.html.erb +2 -2
  49. data/app/views/catalog/email_success.html.erb +1 -1
  50. data/app/views/catalog/facet.html.erb +3 -3
  51. data/app/views/catalog/sms.html.erb +2 -2
  52. data/app/views/catalog/sms_success.html.erb +1 -1
  53. data/blacklight.gemspec +1 -1
  54. data/config/locales/blacklight.de.yml +2 -2
  55. data/lib/blacklight/configuration.rb +50 -5
  56. data/lib/blacklight/configuration/view_config.rb +18 -5
  57. data/lib/blacklight/engine.rb +3 -1
  58. data/lib/blacklight/open_struct_with_hash_access.rb +22 -1
  59. data/lib/blacklight/search_state.rb +2 -2
  60. data/lib/blacklight/solr/facet_paginator.rb +2 -0
  61. data/lib/blacklight/solr/request.rb +31 -0
  62. data/lib/blacklight/solr/response.rb +2 -16
  63. data/lib/blacklight/solr/response/facets.rb +76 -22
  64. data/lib/blacklight/solr/response/params.rb +104 -0
  65. data/lib/blacklight/solr/search_builder_behavior.rb +57 -27
  66. data/lib/generators/blacklight/assets_generator.rb +6 -2
  67. data/lib/generators/blacklight/user_generator.rb +1 -1
  68. data/spec/components/blacklight/document_component_spec.rb +3 -3
  69. data/spec/lib/blacklight/configuration/view_config_spec.rb +1 -1
  70. data/spec/lib/blacklight/open_struct_with_hash_access_spec.rb +20 -0
  71. data/spec/models/blacklight/configuration_spec.rb +64 -0
  72. data/spec/models/blacklight/solr/facet_paginator_spec.rb +4 -0
  73. data/spec/models/blacklight/solr/request_spec.rb +62 -29
  74. data/spec/models/blacklight/solr/response/facets_spec.rb +109 -0
  75. data/spec/models/blacklight/solr/response_spec.rb +10 -0
  76. data/spec/models/blacklight/solr/search_builder_spec.rb +49 -0
  77. data/spec/views/catalog/_view_type_group.html.erb_spec.rb +8 -9
  78. data/spec/views/catalog/index.atom.builder_spec.rb +1 -1
  79. metadata +10 -4
@@ -115,9 +115,9 @@ module Blacklight
115
115
  # documents
116
116
  def url_for_document(doc, options = {})
117
117
  if respond_to?(:blacklight_config) &&
118
- blacklight_config.show.route &&
118
+ blacklight_config.view_config(:show).route &&
119
119
  (!doc.respond_to?(:to_model) || doc.to_model.is_a?(SolrDocument))
120
- route = blacklight_config.show.route.merge(action: :show, id: doc).merge(options)
120
+ route = blacklight_config.view_config(:show).route.merge(action: :show, id: doc).merge(options)
121
121
  route[:controller] = params[:controller] if route[:controller] == :current
122
122
  route
123
123
  else
@@ -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
@@ -59,12 +59,14 @@ module Blacklight::Solr
59
59
  ##
60
60
  if search_field&.query_builder.present?
61
61
  add_search_field_query_builder_params(solr_parameters)
62
+ elsif search_field&.clause_params.present?
63
+ add_search_field_with_json_query_parameters(solr_parameters)
62
64
  elsif search_field&.solr_local_parameters.present?
63
65
  add_search_field_with_local_parameters(solr_parameters)
64
66
  elsif search_state.query_param.is_a? Hash
65
67
  add_additional_filters(solr_parameters, search_state.query_param)
66
68
  elsif search_state.query_param
67
- solr_parameters[:q] = search_state.query_param
69
+ solr_parameters.append_query search_state.query_param
68
70
  end
69
71
  end
70
72
 
@@ -73,39 +75,41 @@ module Blacklight::Solr
73
75
 
74
76
  return if q.blank?
75
77
 
76
- solr_parameters[:q] = if q.values.any?(&:blank?)
77
- # if any field parameters are empty, exclude _all_ results
78
- "{!lucene}NOT *:*"
79
- else
80
- "{!lucene}" + q.map do |field, values|
81
- "#{field}:(#{Array(values).map { |x| solr_param_quote(x) }.join(' OR ')})"
82
- end.join(" AND ")
83
- end
78
+ if q.values.any?(&:blank?)
79
+ # if any field parameters are empty, exclude _all_ results
80
+ solr_parameters.append_query "{!lucene}NOT *:*"
81
+ else
82
+ composed_query = q.map do |field, values|
83
+ "#{field}:(#{Array(values).map { |x| solr_param_quote(x) }.join(' OR ')})"
84
+ end.join(" AND ")
85
+
86
+ solr_parameters.append_query "{!lucene}#{composed_query}"
87
+ end
84
88
 
85
89
  solr_parameters[:defType] = 'lucene'
86
90
  solr_parameters[:spellcheck] = 'false'
87
91
  end
88
92
 
93
+ def add_search_field_with_json_query_parameters(solr_parameters)
94
+ bool_query = search_field.clause_params.transform_values { |v| v.merge(query: search_state.query_param) }
95
+
96
+ solr_parameters.append_boolean_query(:must, bool_query)
97
+ end
98
+
89
99
  # Transform "clause" parameters into the Solr JSON Query DSL
90
100
  def add_adv_search_clauses(solr_parameters)
91
101
  return if search_state.clause_params.blank?
92
102
 
93
103
  defaults = { must: [], must_not: [], should: [] }
94
- bool_query = (solr_parameters.dig(:json, :query, :bool) || {}).reverse_merge(defaults)
95
-
96
104
  default_op = blacklight_params[:op]&.to_sym || :must
105
+ solr_parameters[:mm] = 1 if default_op == :should && search_state.clause_params.values.any? { |clause| }
97
106
 
98
107
  search_state.clause_params.each_value do |clause|
99
108
  op, query = adv_search_clause(clause, default_op)
100
- bool_query[op] << query if defaults.key?(op) && query
101
- end
102
-
103
- return if bool_query.values.all?(&:blank?)
109
+ next unless defaults.key?(op)
104
110
 
105
- solr_parameters[:mm] = 1 if default_op == :should
106
- solr_parameters[:json] ||= { query: { bool: {} } }
107
- solr_parameters[:json][:query] ||= { bool: {} }
108
- solr_parameters[:json][:query][:bool] = bool_query.reject { |_k, v| v.blank? }
111
+ solr_parameters.append_boolean_query(op, query)
112
+ end
109
113
  end
110
114
 
111
115
  # @return [Array] the first element is the query operator and the second is the value to add
@@ -131,7 +135,7 @@ module Blacklight::Solr
131
135
  if filter.config.filter_query_builder
132
136
  filter_query, subqueries = filter.config.filter_query_builder.call(self, filter, solr_parameters)
133
137
 
134
- solr_parameters.append_filter_query(filter_query)
138
+ solr_parameters.append_filter_query(filter_query) if filter_query
135
139
  solr_parameters.merge!(subqueries) if subqueries
136
140
  else
137
141
  filter.values.reject(&:blank?).each do |value|
@@ -148,6 +152,21 @@ module Blacklight::Solr
148
152
  end
149
153
  end
150
154
 
155
+ def add_solr_facet_json_params(solr_parameters, field_name, facet, **additional_parameters)
156
+ solr_parameters[:json] ||= { facet: {} }
157
+ solr_parameters[:json][:facet] ||= {}
158
+
159
+ field_config = facet.json.respond_to?(:reverse_merge) ? facet.json : {}
160
+
161
+ field_config = field_config.reverse_merge(
162
+ type: 'terms',
163
+ field: facet.field,
164
+ limit: facet_limit_with_pagination(field_name)
165
+ ).merge(additional_parameters)
166
+
167
+ solr_parameters[:json][:facet][field_name] = field_config.select { |_k, v| v.present? }
168
+ end
169
+
151
170
  ##
152
171
  # Add appropriate Solr facetting directives in, including
153
172
  # taking account of our facet paging/'more'. This is not
@@ -156,6 +175,11 @@ module Blacklight::Solr
156
175
  facet_fields_to_include_in_request.each do |field_name, facet|
157
176
  solr_parameters[:facet] ||= true
158
177
 
178
+ if facet.json
179
+ add_solr_facet_json_params(solr_parameters, field_name, facet)
180
+ next
181
+ end
182
+
159
183
  if facet.pivot
160
184
  solr_parameters.append_facet_pivot with_ex_local_param(facet.ex, facet.pivot.join(","))
161
185
  elsif facet.query
@@ -225,9 +249,7 @@ module Blacklight::Solr
225
249
 
226
250
  facet_config = blacklight_config.facet_fields[facet]
227
251
 
228
- # Now override with our specific things for fetching facet values
229
- facet_ex = facet_config.respond_to?(:ex) ? facet_config.ex : nil
230
- solr_params[:"facet.field"] = with_ex_local_param(facet_ex, facet_config.field)
252
+ solr_params[:rows] = 0
231
253
 
232
254
  limit = if solr_params["facet.limit"]
233
255
  solr_params["facet.limit"].to_i
@@ -240,13 +262,21 @@ module Blacklight::Solr
240
262
  prefix = search_state.facet_prefix
241
263
  offset = (page - 1) * limit
242
264
 
265
+ if facet_config.json
266
+ add_solr_facet_json_params(solr_parameters, facet, facet_config, limit: limit + 1, offset: offset, sort: sort, prefix: prefix)
267
+ return
268
+ end
269
+
270
+ # Now override with our specific things for fetching facet values
271
+ facet_ex = facet_config.respond_to?(:ex) ? facet_config.ex : nil
272
+ solr_params[:"facet.field"] = with_ex_local_param(facet_ex, facet_config.field)
273
+
243
274
  # Need to set as f.facet_field.facet.* to make sure we
244
275
  # override any field-specific default in the solr request handler.
245
276
  solr_params[:"f.#{facet_config.field}.facet.limit"] = limit + 1
246
277
  solr_params[:"f.#{facet_config.field}.facet.offset"] = offset
247
278
  solr_params[:"f.#{facet_config.field}.facet.sort"] = sort if sort
248
279
  solr_params[:"f.#{facet_config.field}.facet.prefix"] = prefix if prefix
249
- solr_params[:rows] = 0
250
280
  end
251
281
 
252
282
  def with_ex_local_param(ex, value)
@@ -367,7 +397,7 @@ module Blacklight::Solr
367
397
  ##
368
398
  # The key to use to retrieve the grouped field to display
369
399
  def grouped_key_for_results
370
- blacklight_config.index.group
400
+ blacklight_config.view_config(action_name: :index).group
371
401
  end
372
402
 
373
403
  def facet_fields_to_include_in_request
@@ -385,7 +415,7 @@ module Blacklight::Solr
385
415
  def add_search_field_query_builder_params(solr_parameters)
386
416
  q, additional_parameters = search_field.query_builder.call(self, search_field, solr_parameters)
387
417
 
388
- solr_parameters[:q] = q
418
+ solr_parameters.append_query q
389
419
  solr_parameters.merge!(additional_parameters) if additional_parameters
390
420
  end
391
421
 
@@ -393,7 +423,7 @@ module Blacklight::Solr
393
423
  local_params = search_field.solr_local_parameters.map do |key, val|
394
424
  key.to_s + "=" + solr_param_quote(val, quote: "'")
395
425
  end.join(" ")
396
- solr_parameters[:q] = "{!#{local_params}}#{search_state.query_param}"
426
+ solr_parameters.append_query "{!#{local_params}}#{search_state.query_param}"
397
427
 
398
428
  ##
399
429
  # Set Solr spellcheck.q to be original user-entered query, without