Search query extension refactoring to use params and not arguments.
This commit is contained in:
parent
9ef3cb62c0
commit
c2f4755a6c
9 changed files with 127 additions and 62 deletions
|
@ -205,21 +205,21 @@ class FormController < ApplicationController
|
||||||
return false if !SearchIndexBackend.enabled?
|
return false if !SearchIndexBackend.enabled?
|
||||||
|
|
||||||
form_limit_by_ip_per_hour = Setting.get('form_ticket_create_by_ip_per_hour') || 20
|
form_limit_by_ip_per_hour = Setting.get('form_ticket_create_by_ip_per_hour') || 20
|
||||||
result = SearchIndexBackend.search("preferences.form.remote_ip:'#{request.remote_ip}' AND created_at:>now-1h", form_limit_by_ip_per_hour, 'Ticket')
|
result = SearchIndexBackend.search("preferences.form.remote_ip:'#{request.remote_ip}' AND created_at:>now-1h", 'Ticket', limit: form_limit_by_ip_per_hour)
|
||||||
if result.count >= form_limit_by_ip_per_hour.to_i
|
if result.count >= form_limit_by_ip_per_hour.to_i
|
||||||
response_access_deny
|
response_access_deny
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
form_limit_by_ip_per_day = Setting.get('form_ticket_create_by_ip_per_day') || 240
|
form_limit_by_ip_per_day = Setting.get('form_ticket_create_by_ip_per_day') || 240
|
||||||
result = SearchIndexBackend.search("preferences.form.remote_ip:'#{request.remote_ip}' AND created_at:>now-1d", form_limit_by_ip_per_day, 'Ticket')
|
result = SearchIndexBackend.search("preferences.form.remote_ip:'#{request.remote_ip}' AND created_at:>now-1d", 'Ticket', limit: form_limit_by_ip_per_day)
|
||||||
if result.count >= form_limit_by_ip_per_day.to_i
|
if result.count >= form_limit_by_ip_per_day.to_i
|
||||||
response_access_deny
|
response_access_deny
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
form_limit_per_day = Setting.get('form_ticket_create_per_day') || 5000
|
form_limit_per_day = Setting.get('form_ticket_create_per_day') || 5000
|
||||||
result = SearchIndexBackend.search('preferences.form.remote_ip:* AND created_at:>now-1d', form_limit_per_day, 'Ticket')
|
result = SearchIndexBackend.search('preferences.form.remote_ip:* AND created_at:>now-1d', 'Ticket', limit: form_limit_per_day)
|
||||||
if result.count >= form_limit_per_day.to_i
|
if result.count >= form_limit_per_day.to_i
|
||||||
response_access_deny
|
response_access_deny
|
||||||
return true
|
return true
|
||||||
|
|
|
@ -64,7 +64,7 @@ class SearchController < ApplicationController
|
||||||
|
|
||||||
# do only one query to index search backend
|
# do only one query to index search backend
|
||||||
if objects_with_direct_search_index.present?
|
if objects_with_direct_search_index.present?
|
||||||
items = SearchIndexBackend.search(query, limit, objects_with_direct_search_index)
|
items = SearchIndexBackend.search(query, objects_with_direct_search_index, limit: limit)
|
||||||
items.each do |item|
|
items.each do |item|
|
||||||
require_dependency item[:type].to_filename
|
require_dependency item[:type].to_filename
|
||||||
local_class = Kernel.const_get(item[:type])
|
local_class = Kernel.const_get(item[:type])
|
||||||
|
|
|
@ -64,7 +64,7 @@ returns
|
||||||
|
|
||||||
# try search index backend
|
# try search index backend
|
||||||
if SearchIndexBackend.enabled?
|
if SearchIndexBackend.enabled?
|
||||||
items = SearchIndexBackend.search(query, limit, 'Chat::Session', {}, offset)
|
items = SearchIndexBackend.search(query, 'Chat::Session', limit: limit, from: offset)
|
||||||
chat_sessions = []
|
chat_sessions = []
|
||||||
items.each do |item|
|
items.each do |item|
|
||||||
chat_session = Chat::Session.lookup(id: item[:id])
|
chat_session = Chat::Session.lookup(id: item[:id])
|
||||||
|
|
|
@ -83,7 +83,10 @@ returns
|
||||||
|
|
||||||
# try search index backend
|
# try search index backend
|
||||||
if SearchIndexBackend.enabled?
|
if SearchIndexBackend.enabled?
|
||||||
items = SearchIndexBackend.search(query, limit, 'Organization', {}, offset, sort_by, order_by)
|
items = SearchIndexBackend.search(query, 'Organization', limit: limit,
|
||||||
|
from: offset,
|
||||||
|
sort_by: sort_by,
|
||||||
|
order_by: order_by)
|
||||||
organizations = []
|
organizations = []
|
||||||
items.each do |item|
|
items.each do |item|
|
||||||
organization = Organization.lookup(id: item[:id])
|
organization = Organization.lookup(id: item[:id])
|
||||||
|
|
|
@ -127,9 +127,9 @@ returns
|
||||||
|
|
||||||
# try search index backend
|
# try search index backend
|
||||||
if condition.blank? && SearchIndexBackend.enabled?
|
if condition.blank? && SearchIndexBackend.enabled?
|
||||||
query_extention = {}
|
query_extension = {}
|
||||||
query_extention['bool'] = {}
|
query_extension['bool'] = {}
|
||||||
query_extention['bool']['must'] = []
|
query_extension['bool']['must'] = []
|
||||||
|
|
||||||
if current_user.permissions?('ticket.agent')
|
if current_user.permissions?('ticket.agent')
|
||||||
group_ids = current_user.group_ids_access('read')
|
group_ids = current_user.group_ids_access('read')
|
||||||
|
@ -152,9 +152,13 @@ returns
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
query_extention['bool']['must'].push access_condition
|
query_extension['bool']['must'].push access_condition
|
||||||
|
|
||||||
items = SearchIndexBackend.search(query, limit, 'Ticket', query_extention, offset, sort_by, order_by)
|
items = SearchIndexBackend.search(query, 'Ticket', limit: limit,
|
||||||
|
query_extension: query_extension,
|
||||||
|
from: offset,
|
||||||
|
sort_by: sort_by,
|
||||||
|
order_by: order_by)
|
||||||
if !full
|
if !full
|
||||||
ids = []
|
ids = []
|
||||||
items.each do |item|
|
items.each do |item|
|
||||||
|
|
|
@ -101,19 +101,24 @@ returns
|
||||||
|
|
||||||
# try search index backend
|
# try search index backend
|
||||||
if SearchIndexBackend.enabled?
|
if SearchIndexBackend.enabled?
|
||||||
query_extention = {}
|
query_extension = {}
|
||||||
if params[:role_ids].present?
|
if params[:role_ids].present?
|
||||||
query_extention['bool'] = {}
|
query_extension['bool'] = {}
|
||||||
query_extention['bool']['must'] = []
|
query_extension['bool']['must'] = []
|
||||||
if !params[:role_ids].is_a?(Array)
|
if !params[:role_ids].is_a?(Array)
|
||||||
params[:role_ids] = [params[:role_ids]]
|
params[:role_ids] = [params[:role_ids]]
|
||||||
end
|
end
|
||||||
access_condition = {
|
access_condition = {
|
||||||
'query_string' => { 'default_field' => 'role_ids', 'query' => "\"#{params[:role_ids].join('" OR "')}\"" }
|
'query_string' => { 'default_field' => 'role_ids', 'query' => "\"#{params[:role_ids].join('" OR "')}\"" }
|
||||||
}
|
}
|
||||||
query_extention['bool']['must'].push access_condition
|
query_extension['bool']['must'].push access_condition
|
||||||
end
|
end
|
||||||
items = SearchIndexBackend.search(query, limit, 'User', query_extention, offset, sort_by, order_by)
|
|
||||||
|
items = SearchIndexBackend.search(query, 'User', limit: limit,
|
||||||
|
query_extension: query_extension,
|
||||||
|
from: offset,
|
||||||
|
sort_by: sort_by,
|
||||||
|
order_by: order_by)
|
||||||
users = []
|
users = []
|
||||||
items.each do |item|
|
items.each do |item|
|
||||||
user = User.lookup(id: item[:id])
|
user = User.lookup(id: item[:id])
|
||||||
|
|
|
@ -36,7 +36,7 @@ class CleanupUserPreferencesNotificationSound2 < ActiveRecord::Migration[5.1]
|
||||||
user.save!
|
user.save!
|
||||||
end
|
end
|
||||||
|
|
||||||
items = SearchIndexBackend.search('preferences.notification_sound.enabled:*', 3000, 'User') || []
|
items = SearchIndexBackend.search('preferences.notification_sound.enabled:*', 'User', limit: 3000) || []
|
||||||
items.each do |item|
|
items.each do |item|
|
||||||
next if !item[:id]
|
next if !item[:id]
|
||||||
|
|
||||||
|
|
|
@ -274,13 +274,19 @@ remove whole data from index
|
||||||
|
|
||||||
=begin
|
=begin
|
||||||
|
|
||||||
return search result
|
@param query [String] search query
|
||||||
|
@param index [String, Array<String>, nil] indexes to search in (see search_by_index)
|
||||||
|
@param options [Hash] search options (see build_query)
|
||||||
|
|
||||||
result = SearchIndexBackend.search('search query', limit, ['User', 'Organization'])
|
@return search result
|
||||||
|
|
||||||
result = SearchIndexBackend.search('search query', limit, 'User')
|
@example Sample queries
|
||||||
|
|
||||||
result = SearchIndexBackend.search('search query', limit, 'User', ['updated_at'], ['desc'])
|
result = SearchIndexBackend.search('search query', ['User', 'Organization'], limit: limit)
|
||||||
|
|
||||||
|
result = SearchIndexBackend.search('search query', 'User', limit: limit)
|
||||||
|
|
||||||
|
result = SearchIndexBackend.search('search query', 'User', limit: limit, sort_by: ['updated_at'], order_by: ['desc'])
|
||||||
|
|
||||||
result = [
|
result = [
|
||||||
{
|
{
|
||||||
|
@ -299,25 +305,28 @@ return search result
|
||||||
|
|
||||||
=end
|
=end
|
||||||
|
|
||||||
# rubocop:disable Metrics/ParameterLists
|
def self.search(query, index = nil, options = {})
|
||||||
def self.search(query, limit = 10, index = nil, query_extention = {}, from = 0, sort_by = [], order_by = [])
|
if !index.is_a? Array
|
||||||
# rubocop:enable Metrics/ParameterLists
|
return search_by_index(query, index, options)
|
||||||
return [] if query.blank?
|
|
||||||
|
|
||||||
if index.class == Array
|
|
||||||
ids = []
|
|
||||||
index.each do |local_index|
|
|
||||||
local_ids = search_by_index(query, limit, local_index, query_extention, from, sort_by, order_by )
|
|
||||||
ids = ids.concat(local_ids)
|
|
||||||
end
|
|
||||||
return ids
|
|
||||||
end
|
|
||||||
search_by_index(query, limit, index, query_extention, from, sort_by, order_by)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# rubocop:disable Metrics/ParameterLists
|
index
|
||||||
def self.search_by_index(query, limit = 10, index = nil, query_extention = {}, from = 0, sort_by = [], order_by = [])
|
.map { |local_index| search_by_index(query, local_index, options) }
|
||||||
# rubocop:enable Metrics/ParameterLists
|
.compact
|
||||||
|
.flatten(1)
|
||||||
|
end
|
||||||
|
|
||||||
|
=begin
|
||||||
|
|
||||||
|
@param query [String] search query
|
||||||
|
@param index [String, Array<String>, nil] index name or list of index names. If index is nil or not present will, search will be performed globally
|
||||||
|
@param options [Hash] search options (see build_query)
|
||||||
|
|
||||||
|
@return search result
|
||||||
|
|
||||||
|
=end
|
||||||
|
|
||||||
|
def self.search_by_index(query, index = nil, options = {})
|
||||||
return [] if query.blank?
|
return [] if query.blank?
|
||||||
|
|
||||||
url = build_url
|
url = build_url
|
||||||
|
@ -332,15 +341,6 @@ return search result
|
||||||
else
|
else
|
||||||
'/_search'
|
'/_search'
|
||||||
end
|
end
|
||||||
data = {}
|
|
||||||
data['from'] = from
|
|
||||||
data['size'] = limit
|
|
||||||
|
|
||||||
data['sort'] = search_by_index_sort(sort_by, order_by)
|
|
||||||
|
|
||||||
data['query'] = query_extention || {}
|
|
||||||
data['query']['bool'] ||= {}
|
|
||||||
data['query']['bool']['must'] ||= []
|
|
||||||
|
|
||||||
# real search condition
|
# real search condition
|
||||||
condition = {
|
condition = {
|
||||||
|
@ -350,14 +350,15 @@ return search result
|
||||||
'analyze_wildcard' => true,
|
'analyze_wildcard' => true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
data['query']['bool']['must'].push condition
|
|
||||||
|
query_data = build_query(condition, options)
|
||||||
|
|
||||||
Rails.logger.info "# curl -X POST \"#{url}\" \\"
|
Rails.logger.info "# curl -X POST \"#{url}\" \\"
|
||||||
Rails.logger.debug { " -d'#{data.to_json}'" }
|
Rails.logger.debug { " -d'#{query_data.to_json}'" }
|
||||||
|
|
||||||
response = UserAgent.get(
|
response = UserAgent.get(
|
||||||
url,
|
url,
|
||||||
data,
|
query_data,
|
||||||
{
|
{
|
||||||
json: true,
|
json: true,
|
||||||
open_timeout: 5,
|
open_timeout: 5,
|
||||||
|
@ -372,35 +373,31 @@ return search result
|
||||||
Rails.logger.error humanized_error(
|
Rails.logger.error humanized_error(
|
||||||
verb: 'GET',
|
verb: 'GET',
|
||||||
url: url,
|
url: url,
|
||||||
payload: data,
|
payload: query_data,
|
||||||
response: response,
|
response: response,
|
||||||
)
|
)
|
||||||
return []
|
return []
|
||||||
end
|
end
|
||||||
data = response.data
|
data = response.data&.dig('hits', 'hits')
|
||||||
|
|
||||||
ids = []
|
return [] if !data
|
||||||
return ids if !data
|
|
||||||
return ids if !data['hits']
|
|
||||||
return ids if !data['hits']['hits']
|
|
||||||
|
|
||||||
data['hits']['hits'].each do |item|
|
data.map do |item|
|
||||||
Rails.logger.info "... #{item['_type']} #{item['_id']}"
|
Rails.logger.info "... #{item['_type']} #{item['_id']}"
|
||||||
data = {
|
|
||||||
|
{
|
||||||
id: item['_id'],
|
id: item['_id'],
|
||||||
type: item['_type'],
|
type: item['_type'],
|
||||||
}
|
}
|
||||||
ids.push data
|
|
||||||
end
|
end
|
||||||
ids
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.search_by_index_sort(sort_by = [], order_by = [])
|
def self.search_by_index_sort(sort_by = nil, order_by = nil)
|
||||||
result = []
|
result = []
|
||||||
|
|
||||||
sort_by.each_with_index do |value, index|
|
sort_by&.each_with_index do |value, index|
|
||||||
next if value.blank?
|
next if value.blank?
|
||||||
next if order_by[index].blank?
|
next if order_by&.at(index).blank?
|
||||||
|
|
||||||
if value !~ /\./ && value !~ /_(time|date|till|id|ids|at)$/
|
if value !~ /\./ && value !~ /_(time|date|till|id|ids|at)$/
|
||||||
value += '.raw'
|
value += '.raw'
|
||||||
|
@ -735,4 +732,44 @@ return true if backend is configured
|
||||||
query += '*' if !query.match?(/:/)
|
query += '*' if !query.match?(/:/)
|
||||||
query
|
query
|
||||||
end
|
end
|
||||||
|
|
||||||
|
=begin
|
||||||
|
|
||||||
|
@param condition [Hash] search condition
|
||||||
|
@param options [Hash] search options
|
||||||
|
@option options [Integer] :from
|
||||||
|
@option options [Integer] :limit
|
||||||
|
@option options [Hash] :query_extension applied to ElasticSearch query
|
||||||
|
@option options [Array<String>] :order_by ordering directions, desc or asc
|
||||||
|
@option options [Array<String>] :sort_by fields to sort by
|
||||||
|
|
||||||
|
=end
|
||||||
|
|
||||||
|
DEFAULT_QUERY_OPTIONS = {
|
||||||
|
from: 0,
|
||||||
|
limit: 10
|
||||||
|
}.freeze
|
||||||
|
|
||||||
|
def self.build_query(condition, options = {})
|
||||||
|
options = DEFAULT_QUERY_OPTIONS.merge(options.deep_symbolize_keys)
|
||||||
|
|
||||||
|
data = {
|
||||||
|
from: options[:from],
|
||||||
|
size: options[:limit],
|
||||||
|
sort: search_by_index_sort(options[:sort_by], options[:order_by]),
|
||||||
|
query: {
|
||||||
|
bool: {
|
||||||
|
must: []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (extension = options.dig(:query_extension))
|
||||||
|
data[:query].deep_merge! extension.deep_dup
|
||||||
|
end
|
||||||
|
|
||||||
|
data[:query][:bool][:must].push condition
|
||||||
|
|
||||||
|
data
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,6 +1,21 @@
|
||||||
require 'test_helper'
|
require 'test_helper'
|
||||||
|
|
||||||
class SearchIndexBackendTest < ActiveSupport::TestCase
|
class SearchIndexBackendTest < ActiveSupport::TestCase
|
||||||
|
test 'query extension keys are normalized to symbols' do
|
||||||
|
query_strings = SearchIndexBackend.build_query('', query_extension: { 'bool' => { 'filter' => { 'term' => { 'a' => 'b' } } } })
|
||||||
|
query_symbols = SearchIndexBackend.build_query('', query_extension: { bool: { filter: { term: { a: 'b' } } } })
|
||||||
|
|
||||||
|
assert_equal query_strings, query_symbols
|
||||||
|
assert_not_nil query_strings.dig(:query, :bool, :filter, :term, :a)
|
||||||
|
end
|
||||||
|
|
||||||
|
test 'search with ES off never returns nil in array' do
|
||||||
|
index_one = SearchIndexBackend.search('preferences.notification_sound.enabled:*', 'User', limit: 3000)
|
||||||
|
index_multi = SearchIndexBackend.search('preferences.notification_sound.enabled:*', %w[User Organization], limit: 3000)
|
||||||
|
|
||||||
|
assert_nil index_one
|
||||||
|
assert index_multi.empty?
|
||||||
|
end
|
||||||
|
|
||||||
test 'simple_query_append_wildcard correctly modifies simple queries' do
|
test 'simple_query_append_wildcard correctly modifies simple queries' do
|
||||||
def clean_queries(query_string)
|
def clean_queries(query_string)
|
||||||
|
@ -48,6 +63,7 @@ class SearchIndexBackendTest < ActiveSupport::TestCase
|
||||||
|
|
||||||
simple_queries = clean_queries %(
|
simple_queries = clean_queries %(
|
||||||
M
|
M
|
||||||
|
|
||||||
Max
|
Max
|
||||||
Max. # dot and underscore are acceptable characters in simple queries
|
Max. # dot and underscore are acceptable characters in simple queries
|
||||||
A_
|
A_
|
||||||
|
|
Loading…
Reference in a new issue