search_index.rb 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363
  1. # Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/
  2. class Selector::SearchIndex < Selector::Base
  3. def get
  4. result = {
  5. size: options[:limit] || SearchIndexBackend::DEFAULT_QUERY_OPTIONS[:limit],
  6. }
  7. query = run(selector, 0)
  8. if query.present?
  9. result[:query] = query
  10. end
  11. result = query_aggs_range(result)
  12. query_sort(result)
  13. end
  14. def query_sort(query)
  15. if options[:aggs_interval].present? && options[:aggs_interval][:field].present? && options[:aggs_interval][:interval].blank?
  16. query_sort_by_aggs_interval(query)
  17. else
  18. query_sort_by_index(query)
  19. end
  20. query
  21. end
  22. def query_sort_by_index(query)
  23. query[:sort] = SearchIndexBackend.search_by_index_sort(index: target_class.to_s, sort_by: options[:sort_by], order_by: options[:order_by])
  24. query
  25. end
  26. def query_sort_by_aggs_interval(query)
  27. query[:sort] = [
  28. {
  29. options[:aggs_interval][:field] => {
  30. order: 'desc',
  31. }
  32. },
  33. '_score'
  34. ]
  35. query
  36. end
  37. def query_aggs_range(query)
  38. return query if options[:aggs_interval].blank?
  39. query = query_aggs_interval(query)
  40. query[:query] = {
  41. bool: {
  42. must: [
  43. {
  44. range: {
  45. options[:aggs_interval][:field] => {
  46. from: options[:aggs_interval][:from],
  47. to: options[:aggs_interval][:to],
  48. },
  49. },
  50. },
  51. query[:query],
  52. ],
  53. },
  54. }
  55. query
  56. end
  57. def query_aggs_interval(query)
  58. return query if options[:aggs_interval][:interval].blank?
  59. query[:size] = 0
  60. query[:aggs] = {
  61. time_buckets: {
  62. date_histogram: {
  63. field: options[:aggs_interval][:field],
  64. calendar_interval: options[:aggs_interval][:interval],
  65. }
  66. }
  67. }
  68. query_aggs_interval_timezone(query)
  69. end
  70. def query_aggs_interval_timezone(query)
  71. return query if options[:aggs_interval][:timezone].blank?
  72. query[:aggs][:time_buckets][:date_histogram][:time_zone] = options[:aggs_interval][:timezone]
  73. query
  74. end
  75. def run(block, level)
  76. if block.key?(:conditions)
  77. block_query = []
  78. block[:conditions].each do |sub_block|
  79. block_query << run(sub_block, level + 1)
  80. end
  81. block_query = block_query.compact
  82. return if block_query.blank?
  83. operator = :must
  84. case block[:operator]
  85. when 'NOT'
  86. operator = :must_not
  87. when 'OR'
  88. operator = :should
  89. end
  90. {
  91. bool: {
  92. operator => block_query
  93. }
  94. }
  95. else
  96. condition_query(block)
  97. end
  98. end
  99. def condition_query(block_condition)
  100. query_must = []
  101. query_must_not = []
  102. current_user = options[:current_user]
  103. current_user_id = UserInfo.current_user_id
  104. if current_user
  105. current_user_id = current_user.id
  106. end
  107. relative_map = {
  108. day: 'd',
  109. year: 'y',
  110. month: 'M',
  111. week: 'w',
  112. hour: 'h',
  113. minute: 'm',
  114. }
  115. operators_is_isnot = ['is', 'is not']
  116. data = block_condition.clone
  117. key = data[:name]
  118. table, key_tmp = key.split('.')
  119. if key_tmp.blank?
  120. key_tmp = table
  121. table = target_name
  122. end
  123. wildcard_or_term = 'term'
  124. if data[:value].is_a?(Array)
  125. wildcard_or_term = 'terms'
  126. end
  127. t = {}
  128. # use .keyword in case of compare exact values
  129. if ['is', 'is not', 'is any of', 'is none of', 'starts with one of', 'ends with one of'].include?(data[:operator])
  130. case data[:pre_condition]
  131. when 'not_set'
  132. data[:value] = if key_tmp.match?(%r{^(created_by|updated_by|owner|customer|user)_id})
  133. 1
  134. end
  135. when 'current_user.id'
  136. raise "Use current_user.id in selector, but no current_user is set #{data.inspect}" if !current_user_id
  137. data[:value] = []
  138. wildcard_or_term = 'terms'
  139. if key_tmp == 'out_of_office_replacement_id'
  140. data[:value].push User.find(current_user_id).out_of_office_agent_of.pluck(:id)
  141. else
  142. data[:value].push current_user_id
  143. end
  144. when 'current_user.organization_id'
  145. raise "Use current_user.id in selector, but no current_user is set #{data.inspect}" if !current_user_id
  146. user = User.find_by(id: current_user_id)
  147. data[:value] = user.organization_id
  148. end
  149. if data[:value].is_a?(Array)
  150. data[:value].each do |value|
  151. next if !value.is_a?(String) || value !~ %r{[A-z]}
  152. key_tmp += '.keyword'
  153. break
  154. end
  155. elsif data[:value].is_a?(String) && %r{[A-z]}.match?(data[:value])
  156. key_tmp += '.keyword'
  157. end
  158. end
  159. # use .keyword and wildcard search in cases where query contains non A-z chars
  160. value_is_string = Array.wrap(data[:value]).any? { |v| v.is_a?(String) && v.match?(%r{[A-z]}) }
  161. if ['contains', 'contains not', 'starts with one of', 'ends with one of'].include?(data[:operator]) && value_is_string
  162. wildcard_or_term = 'wildcard'
  163. if !key_tmp.ends_with?('.keyword')
  164. key_tmp += '.keyword'
  165. end
  166. if data[:value].is_a?(Array)
  167. or_condition = {
  168. bool: {
  169. should: [],
  170. }
  171. }
  172. data[:value].each do |value|
  173. t = {}
  174. t[wildcard_or_term] = {}
  175. t[wildcard_or_term][key_tmp] = if data[:operator] == 'starts with one of'
  176. "#{value}*"
  177. elsif data[:operator] == 'ends with one of'
  178. "*#{value}"
  179. else
  180. "*#{value}*"
  181. end
  182. or_condition[:bool][:should] << t
  183. end
  184. data[:value] = or_condition
  185. else
  186. data[:value] = "*#{data[:value]}*"
  187. end
  188. end
  189. if table != target_name
  190. key_tmp = "#{table}.#{key_tmp}"
  191. end
  192. # for pre condition not_set we want to check if values are defined for the object by exists
  193. if data[:pre_condition] == 'not_set' && operators_is_isnot.include?(data[:operator]) && data[:value].nil?
  194. t['exists'] = {
  195. field: key_tmp,
  196. }
  197. case data[:operator]
  198. when 'is'
  199. query_must_not.push t
  200. when 'is not'
  201. query_must.push t
  202. end
  203. elsif data[:value].is_a?(Hash) && data[:value][:bool].present?
  204. query_must.push data[:value]
  205. # is/is not/contains/contains not
  206. elsif ['is', 'is not', 'contains', 'contains not', 'is any of', 'is none of'].include?(data[:operator])
  207. t[wildcard_or_term] = {}
  208. t[wildcard_or_term][key_tmp] = data[:value]
  209. case data[:operator]
  210. when 'is', 'contains', 'is any of'
  211. query_must.push t
  212. when 'is not', 'contains not', 'is none of'
  213. query_must_not.push t
  214. end
  215. elsif ['contains all', 'contains one', 'contains all not', 'contains one not'].include?(data[:operator])
  216. values = data[:value]
  217. if data[:value].is_a?(String)
  218. values = values.split(',').map(&:strip)
  219. end
  220. t[:query_string] = {}
  221. case data[:operator]
  222. when 'contains all'
  223. t[:query_string][:query] = "#{key_tmp}:(\"#{values.join('" AND "')}\")"
  224. query_must.push t
  225. when 'contains one not'
  226. t[:query_string][:query] = "#{key_tmp}:(\"#{values.join('" OR "')}\")"
  227. query_must_not.push t
  228. when 'contains one'
  229. t[:query_string][:query] = "#{key_tmp}:(\"#{values.join('" OR "')}\")"
  230. query_must.push t
  231. when 'contains all not'
  232. t[:query_string][:query] = "#{key_tmp}:(\"#{values.join('" AND "')}\")"
  233. query_must_not.push t
  234. end
  235. # within last/within next (relative)
  236. elsif ['within last (relative)', 'within next (relative)'].include?(data[:operator])
  237. range = relative_map[data[:range].to_sym]
  238. if range.blank?
  239. raise "Invalid relative_map for range '#{data[:range]}'."
  240. end
  241. t[:range] = {}
  242. t[:range][key_tmp] = {}
  243. if data[:operator] == 'within last (relative)'
  244. t[:range][key_tmp][:gte] = "now-#{data[:value]}#{range}"
  245. else
  246. t[:range][key_tmp][:lt] = "now+#{data[:value]}#{range}"
  247. end
  248. query_must.push t
  249. # before/after (relative)
  250. elsif ['before (relative)', 'after (relative)'].include?(data[:operator])
  251. range = relative_map[data[:range].to_sym]
  252. if range.blank?
  253. raise "Invalid relative_map for range '#{data[:range]}'."
  254. end
  255. t[:range] = {}
  256. t[:range][key_tmp] = {}
  257. if data[:operator] == 'before (relative)'
  258. t[:range][key_tmp][:lt] = "now-#{data[:value]}#{range}"
  259. else
  260. t[:range][key_tmp][:gt] = "now+#{data[:value]}#{range}"
  261. end
  262. query_must.push t
  263. # till/from (relative)
  264. elsif ['till (relative)', 'from (relative)'].include?(data[:operator])
  265. range = relative_map[data[:range].to_sym]
  266. if range.blank?
  267. raise "Invalid relative_map for range '#{data[:range]}'."
  268. end
  269. t[:range] = {}
  270. t[:range][key_tmp] = {}
  271. if data[:operator] == 'till (relative)'
  272. t[:range][key_tmp][:lt] = "now+#{data[:value]}#{range}"
  273. else
  274. t[:range][key_tmp][:gt] = "now-#{data[:value]}#{range}"
  275. end
  276. query_must.push t
  277. # before/after (absolute)
  278. elsif ['before (absolute)', 'after (absolute)'].include?(data[:operator])
  279. t[:range] = {}
  280. t[:range][key_tmp] = {}
  281. if data[:operator] == 'before (absolute)'
  282. t[:range][key_tmp][:lt] = (data[:value])
  283. else
  284. t[:range][key_tmp][:gt] = (data[:value])
  285. end
  286. query_must.push t
  287. elsif data[:operator] == 'today'
  288. t[:range] = {}
  289. t[:range][key_tmp] = {}
  290. t[:range][key_tmp][:gte] = "#{Time.zone.today}T00:00:00Z"
  291. t[:range][key_tmp][:lte] = "#{Time.zone.today}T23:59:59Z"
  292. query_must.push t
  293. else
  294. raise "unknown operator '#{data[:operator]}' for #{key}"
  295. end
  296. data = {
  297. bool: {},
  298. }
  299. if query_must.present?
  300. data[:bool][:must] = query_must
  301. end
  302. if query_must_not.present?
  303. data[:bool][:must_not] = query_must_not
  304. end
  305. data
  306. end
  307. end