From 074be23ed203dc2d6aa67d3a1a572781814dd5c1 Mon Sep 17 00:00:00 2001 From: Dmitriy Baklikov Date: Tue, 23 Feb 2016 17:58:27 +0300 Subject: [PATCH 1/9] Switch to aggregations instead of facets for Elasticsearch 2.x. --- app/helpers/spree/base_helper_decorator.rb | 29 +- app/models/spree/product_decorator.rb | 47 +-- app/views/spree/shared/_filter_price.html.erb | 44 +-- .../spree/shared/_filter_properties.html.erb | 26 +- app/views/spree/shared/_filters.html.erb | 12 +- spec/models/products_spec.rb | 319 ++++++++++-------- 6 files changed, 259 insertions(+), 218 deletions(-) diff --git a/app/helpers/spree/base_helper_decorator.rb b/app/helpers/spree/base_helper_decorator.rb index 91b7612..2f29bd1 100644 --- a/app/helpers/spree/base_helper_decorator.rb +++ b/app/helpers/spree/base_helper_decorator.rb @@ -3,16 +3,16 @@ module Spree # parses the properties facet result # input: Facet(name: "properties", type: "terms", body: {"terms" => [{"term" => "key1||value1", "count" => 1},{"term" => "key1||value2", "count" => 1}]}]) # output: Facet(name: key1, type: terms, body: {"terms" => [{"term" => "value1", "count" => 1},{"term" => "value2", "count" => 1}]}) - def expand_properties_facet_to_facet_array(facet) + def expand_properties_aggregation_to_aggregation_array(aggregation) # first step is to build a hash # {"property_name" => [{"term" => "value1", "count" => 1},{"term" => "value2", "count" => 1}]}} property_names = {} - facet["terms"].each do |term| - t = term["term"].split("||") + aggregation[:buckets].each do |term| + t = term[:key].split('||') property_name = t[0] property_value = t[1] # add a search_term to each term hash to allow searching on the element later on - property = {"term" => property_value, "count" => term["count"], "search_term" => term["term"]} + property = { term: property_value, count: term[:doc_count], search_term: term[:key] } if property_names.has_key?(property_name) property_names[property_name] << property else @@ -24,10 +24,9 @@ def expand_properties_facet_to_facet_array(facet) # format: Facet(name: "property_name", type: type, body: {"terms" => [{"term" => "value1", "count" => 1},{"term" => "value2", "count" => 1}]}]) result = {} property_names.each do |key,value| - value.sort_by!{|h| [-h["count"],h["term"].downcase]} # first sort on desc, then on term asc + value.sort_by!{ |value| [-value[:count], value[:term].downcase] } # first sort on desc, then on term asc # result << Spree::Search::Elasticsearch::Facet.new(name: key, search_name: facet.name, type: facet.type, body: {"terms" => value}) result[key] = { - '_type' => facet['_type'], 'terms' => value } end @@ -37,19 +36,19 @@ def expand_properties_facet_to_facet_array(facet) # Helper method for interpreting facets from Elasticsearch. Something like a before filter. # Sorting, changings things, the world is your oyster # Input is a hash - def process_facets(facets) - new_facets = {} + def process_aggregations(aggregations) + new_aggregations = {} delete_keys = [] - facets.map do |key, facet| - if key == "properties" - new_facets.merge! expand_properties_facet_to_facet_array(facet) + aggregations.map do |key, aggregation| + if key == 'properties' + new_aggregations.merge! expand_properties_aggregation_to_aggregation_array(aggregation) delete_keys << :properties else - facet + aggregation end end - delete_keys.each {|key| facets.delete(key) } - facets.merge! new_facets + delete_keys.each { |key| aggregations.delete(key) } + aggregations.merge! new_aggregations end end -end \ No newline at end of file +end diff --git a/app/models/spree/product_decorator.rb b/app/models/spree/product_decorator.rb index c432777..b05239f 100644 --- a/app/models/spree/product_decorator.rb +++ b/app/models/spree/product_decorator.rb @@ -5,11 +5,12 @@ module Spree index_name Spree::ElasticsearchSettings.index document_type 'spree_product' - mapping _all: {"index_analyzer" => "nGram_analyzer", "search_analyzer" => "whitespace_analyzer"} do + mapping _all: { analyzer: 'nGram_analyzer', search_analyzer: 'whitespace_analyzer' } do indexes :name, type: 'multi_field' do indexes :name, type: 'string', analyzer: 'nGram_analyzer', boost: 100 indexes :untouched, type: 'string', include_in_all: false, index: 'not_analyzed' end + indexes :description, analyzer: 'snowball' indexes :available_on, type: 'date', format: 'dateOptionalTime', include_in_all: false indexes :price, type: 'double' @@ -77,7 +78,7 @@ class Product::ElasticsearchQuery # filter: { range: { price: { lte: , gte: } } }, # sort: [], # from: , - # facets: + # aggregations: # } def to_hash q = { match_all: {} } @@ -92,31 +93,31 @@ def to_hash # to { terms: { properties: ["key1||value_a","key1||value_b"] } # { terms: { properties: ["key2||value_a"] } # This enforces "and" relation between different property values and "or" relation between same property values - properties = @properties.map {|k,v| [k].product(v)}.map do |pair| - and_filter << { terms: { properties: pair.map {|prop| prop.join("||")} } } + properties = @properties.map{ |key, value| [key].product(value) }.map do |pair| + and_filter << { terms: { properties: pair.map { |property| property.join('||') } } } end end sorting = case @sorting - when "name_asc" - [ {"name.untouched" => { order: "asc" }}, {"price" => { order: "asc" }}, "_score" ] - when "name_desc" - [ {"name.untouched" => { order: "desc" }}, {"price" => { order: "asc" }}, "_score" ] - when "price_asc" - [ {"price" => { order: "asc" }}, {"name.untouched" => { order: "asc" }}, "_score" ] - when "price_desc" - [ {"price" => { order: "desc" }}, {"name.untouched" => { order: "asc" }}, "_score" ] - when "score" - [ "_score", {"name.untouched" => { order: "asc" }}, {"price" => { order: "asc" }} ] + when 'name_asc' + [ { 'name.untouched' => { order: 'asc' } }, { price: { order: 'asc' } }, '_score' ] + when 'name_desc' + [ { 'name.untouched' => { order: 'desc' } }, { price: { order: 'asc' } }, '_score' ] + when 'price_asc' + [ { 'price' => { order: 'asc' } }, { 'name.untouched' => { order: 'asc' } }, '_score' ] + when 'price_desc' + [ { 'price' => { order: 'desc' } }, { 'name.untouched' => { order: 'asc' } }, '_score' ] + when 'score' + [ '_score', { 'name.untouched' => { order: 'asc' } }, { price: { order: 'asc' } } ] else - [ {"name.untouched" => { order: "asc" }}, {"price" => { order: "asc" }}, "_score" ] + [ { 'name.untouched' => { order: 'asc' } }, { price: { order: 'asc' } }, '_score' ] end - # facets - facets = { - price: { statistical: { field: "price" } }, - properties: { terms: { field: "properties", order: "count", size: 1000000 } }, - taxon_ids: { terms: { field: "taxon_ids", size: 1000000 } } + # aggregations + aggregations = { + price: { stats: { field: 'price' } }, + properties: { terms: { field: 'properties', order: { _count: 'asc' }, size: 1000000 } }, + taxon_ids: { terms: { field: 'taxon_ids', size: 1000000 } } } # basic skeleton @@ -125,7 +126,7 @@ def to_hash query: { filtered: {} }, sort: sorting, from: from, - facets: facets + aggregations: aggregations } # add query and filters to filtered @@ -133,8 +134,8 @@ def to_hash # taxon and property filters have an effect on the facets and_filter << { terms: { taxon_ids: taxons } } unless taxons.empty? # only return products that are available - and_filter << { range: { available_on: { lte: "now" } } } - result[:query][:filtered][:filter] = { "and" => and_filter } unless and_filter.empty? + and_filter << { range: { available_on: { lte: 'now' } } } + result[:query][:filtered][:filter] = { and: and_filter } unless and_filter.empty? # add price filter outside the query because it should have no effect on facets if price_min && price_max && (price_min < price_max) diff --git a/app/views/spree/shared/_filter_price.html.erb b/app/views/spree/shared/_filter_price.html.erb index b8fcd90..5ede448 100644 --- a/app/views/spree/shared/_filter_price.html.erb +++ b/app/views/spree/shared/_filter_price.html.erb @@ -1,14 +1,14 @@ -
-

<%= facet.name %>

-
-
-
- +
+

<%= aggregation.name %>

+
+
+
+
-
- "> +
+ to - "> +
@@ -16,22 +16,22 @@ <% content_for :head do %>