Просмотр исходного кода

Fixes #4851 - Object manager attributes with type "datetime" are not sortable in API queries.

Rolf Schmidt 1 год назад
Родитель
Сommit
3efc403157

+ 1 - 1
app/models/ticket/selector/search_index.rb

@@ -26,7 +26,7 @@ class Ticket::Selector::SearchIndex < Ticket::Selector::Base
   end
 
   def query_sort_by_index(query)
-    query[:sort] = SearchIndexBackend.search_by_index_sort(sort_by: options[:sort_by], order_by: options[:order_by])
+    query[:sort] = SearchIndexBackend.search_by_index_sort(index: 'Ticket', sort_by: options[:sort_by], order_by: options[:order_by])
     query
   end
 

+ 19 - 21
lib/search_index_backend.rb

@@ -336,7 +336,7 @@ remove whole data from index
       condition['query_string']['fields'] = fields
     end
 
-    query_data = build_query(condition, options)
+    query_data = build_query(index, condition, options)
 
     if (fields = options.dig(:highlight_fields_by_indexes, index.to_sym))
       fields_for_highlight = fields.index_with { |_elem| {} }
@@ -375,22 +375,23 @@ remove whole data from index
     end
   end
 
-  def self.search_by_index_sort(sort_by: nil, order_by: nil, fulltext: false)
+  def self.search_by_index_sort(index:, sort_by: nil, order_by: nil, fulltext: false)
     result = (sort_by || [])
       .map(&:to_s)
       .each_with_object([])
-      .with_index do |(elem, memo), index|
+      .with_index do |(elem, memo), idx|
         next if elem.blank?
-        next if order_by&.at(index).blank?
+        next if order_by&.at(idx).blank?
 
         # for sorting values use .keyword values (no analyzer is used - plain values)
-        if elem.exclude?('.') && elem !~ %r{_(time|date|till|id|ids|at)$} && elem != 'id'
+        is_keyword = get_mapping_properties_object(Array.wrap(index).first.constantize).dig(:properties, elem, :fields, :keyword, :type) == 'keyword'
+        if is_keyword
           elem += '.keyword'
         end
 
         memo.push(
           elem => {
-            order: order_by[index],
+            order: order_by[idx],
           },
         )
       end
@@ -646,13 +647,13 @@ generate url for index or document access (only for internal use)
     limit: 10
   }.freeze
 
-  def self.build_query(condition, options = {})
+  def self.build_query(index, condition, options = {})
     options = DEFAULT_QUERY_OPTIONS.merge(options.deep_symbolize_keys)
 
     data = {
       from:  options[:from],
       size:  options[:limit],
-      sort:  search_by_index_sort(sort_by: options[:sort_by], order_by: options[:order_by], fulltext: options[:fulltext]),
+      sort:  search_by_index_sort(index: index, sort_by: options[:sort_by], order_by: options[:order_by], fulltext: options[:fulltext]),
       query: {
         bool: {
           must: []
@@ -770,11 +771,8 @@ helper method for making HTTP calls and raising error if response was not succes
 =end
 
   def self.get_mapping_properties_object(object)
-    name = '_doc'
     result = {
-      name => {
-        properties: {}
-      }
+      properties: {}
     }
 
     store_columns = %w[preferences data]
@@ -786,37 +784,37 @@ helper method for making HTTP calls and raising error if response was not succes
 
     object.columns_hash.each do |key, value|
       if value.type == :string && value.limit && value.limit <= 5000 && store_columns.exclude?(key)
-        result[name][:properties][key] = {
+        result[:properties][key] = {
           type:   string_type,
           fields: {
             keyword: string_raw,
           }
         }
       elsif value.type == :integer
-        result[name][:properties][key] = {
+        result[:properties][key] = {
           type: 'integer',
         }
       elsif value.type == :datetime || value.type == :date
-        result[name][:properties][key] = {
+        result[:properties][key] = {
           type: 'date',
         }
       elsif value.type == :boolean
-        result[name][:properties][key] = {
+        result[:properties][key] = {
           type:   'boolean',
           fields: {
             keyword: boolean_raw,
           }
         }
       elsif value.type == :binary
-        result[name][:properties][key] = {
+        result[:properties][key] = {
           type: 'binary',
         }
       elsif value.type == :bigint
-        result[name][:properties][key] = {
+        result[:properties][key] = {
           type: 'long',
         }
       elsif value.type == :decimal
-        result[name][:properties][key] = {
+        result[:properties][key] = {
           type: 'float',
         }
       end
@@ -824,13 +822,13 @@ helper method for making HTTP calls and raising error if response was not succes
 
     case object.name
     when 'Ticket'
-      result[name][:properties][:article] = {
+      result[:properties][:article] = {
         type:              'nested',
         include_in_parent: true,
       }
     end
 
-    result[name]
+    result
   end
 
   # get es version

+ 23 - 5
spec/lib/search_index_backend_spec.rb

@@ -11,7 +11,7 @@ RSpec.describe SearchIndexBackend do
   end
 
   describe '.build_query' do
-    subject(:query) { described_class.build_query('', query_extension: params) }
+    subject(:query) { described_class.build_query('Ticket', '', query_extension: params) }
 
     let(:params) { { 'bool' => { 'filter' => { 'term' => { 'a' => 'b' } } } } }
 
@@ -146,6 +146,24 @@ RSpec.describe SearchIndexBackend do
         expect(result).to eq([{ id: record.id.to_s, type: record_type }])
       end
     end
+
+    context 'can sort by datetime fields', db_strategy: :reset do
+      let(:record_type) { 'Ticket'.freeze }
+      let(:record)     { create(:ticket) }
+      let(:field_name) { SecureRandom.uuid }
+
+      before do
+        create(:object_manager_attribute_datetime, name: field_name)
+        ObjectManager::Attribute.migration_execute
+        record.search_index_update_backend
+        described_class.refresh
+      end
+
+      it 'finds added records' do
+        result = described_class.search(record.number, record_type, sort_by: [field_name], order_by: ['desc'])
+        expect(result).to eq([{ id: record.id.to_s, type: record_type }])
+      end
+    end
   end
 
   describe '.append_wildcard_to_simple_query' do
@@ -878,19 +896,19 @@ RSpec.describe SearchIndexBackend do
 
   describe '.search_by_index_sort' do
     it 'does return a sort value' do
-      expect(described_class.search_by_index_sort(sort_by: ['title'], order_by: 'desc')).to eq([{ 'title.keyword'=>{ order: 'd' } }, '_score'])
+      expect(described_class.search_by_index_sort(index: 'Ticket', sort_by: ['title'], order_by: 'desc')).to eq([{ 'title.keyword'=>{ order: 'd' } }, '_score'])
     end
 
     it 'does return a sort value for fulltext searches' do
-      expect(described_class.search_by_index_sort(sort_by: ['title'], order_by: 'desc', fulltext: true)).to eq([{ 'title.keyword'=>{ order: 'd' } }, '_score'])
+      expect(described_class.search_by_index_sort(index: 'Ticket', sort_by: ['title'], order_by: 'desc', fulltext: true)).to eq([{ 'title.keyword'=>{ order: 'd' } }, '_score'])
     end
 
     it 'does return a default sort value' do
-      expect(described_class.search_by_index_sort).to eq([{ updated_at: { order: 'desc' } }, '_score'])
+      expect(described_class.search_by_index_sort(index: 'Ticket')).to eq([{ updated_at: { order: 'desc' } }, '_score'])
     end
 
     it 'does return a default sort value for fulltext searches' do
-      expect(described_class.search_by_index_sort(fulltext: true)).to eq(['_score'])
+      expect(described_class.search_by_index_sort(index: 'Ticket', fulltext: true)).to eq(['_score'])
     end
   end