search_index.rb 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337
  1. # Copyright (C) 2012-2023 Zammad Foundation, https://zammad-foundation.org/
  2. class Ticket::Selector::SearchIndex < Ticket::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(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. hour: 'h',
  112. minute: 'm',
  113. }
  114. operators_is_isnot = ['is', 'is not']
  115. data = block_condition.clone
  116. key = data[:name]
  117. table, key_tmp = key.split('.')
  118. if key_tmp.blank?
  119. key_tmp = table
  120. table = 'ticket'
  121. end
  122. wildcard_or_term = 'term'
  123. if data[:value].is_a?(Array)
  124. wildcard_or_term = 'terms'
  125. end
  126. t = {}
  127. # use .keyword in case of compare exact values
  128. if data[:operator] == 'is' || data[:operator] == 'is not'
  129. case data[:pre_condition]
  130. when 'not_set'
  131. data[:value] = if key_tmp.match?(%r{^(created_by|updated_by|owner|customer|user)_id})
  132. 1
  133. end
  134. when 'current_user.id'
  135. raise "Use current_user.id in selector, but no current_user is set #{data.inspect}" if !current_user_id
  136. data[:value] = []
  137. wildcard_or_term = 'terms'
  138. if key_tmp == 'out_of_office_replacement_id'
  139. data[:value].push User.find(current_user_id).out_of_office_agent_of.pluck(:id)
  140. else
  141. data[:value].push current_user_id
  142. end
  143. when 'current_user.organization_id'
  144. raise "Use current_user.id in selector, but no current_user is set #{data.inspect}" if !current_user_id
  145. user = User.find_by(id: current_user_id)
  146. data[:value] = user.organization_id
  147. end
  148. if data[:value].is_a?(Array)
  149. data[:value].each do |value|
  150. next if !value.is_a?(String) || value !~ %r{[A-z]}
  151. key_tmp += '.keyword'
  152. break
  153. end
  154. elsif data[:value].is_a?(String) && %r{[A-z]}.match?(data[:value])
  155. key_tmp += '.keyword'
  156. end
  157. end
  158. # use .keyword and wildcard search in cases where query contains non A-z chars
  159. if data[:operator] == 'contains' || data[:operator] == 'contains not'
  160. if data[:value].is_a?(Array)
  161. data[:value].each_with_index do |value, index|
  162. next if !value.is_a?(String) || value !~ %r{[A-z]}
  163. data[:value][index] = "*#{value}*"
  164. key_tmp += '.keyword'
  165. wildcard_or_term = 'wildcards'
  166. break
  167. end
  168. elsif data[:value].is_a?(String) && %r{[A-z]}.match?(data[:value])
  169. data[:value] = "*#{data[:value]}*"
  170. key_tmp += '.keyword'
  171. wildcard_or_term = 'wildcard'
  172. end
  173. end
  174. if table != 'ticket'
  175. key_tmp = "#{table}.#{key_tmp}"
  176. end
  177. # for pre condition not_set we want to check if values are defined for the object by exists
  178. if data[:pre_condition] == 'not_set' && operators_is_isnot.include?(data[:operator]) && data[:value].nil?
  179. t['exists'] = {
  180. field: key_tmp,
  181. }
  182. case data[:operator]
  183. when 'is'
  184. query_must_not.push t
  185. when 'is not'
  186. query_must.push t
  187. end
  188. # is/is not/contains/contains not
  189. elsif ['is', 'is not', 'contains', 'contains not'].include?(data[:operator])
  190. t[wildcard_or_term] = {}
  191. t[wildcard_or_term][key_tmp] = data[:value]
  192. case data[:operator]
  193. when 'is', 'contains'
  194. query_must.push t
  195. when 'is not', 'contains not'
  196. query_must_not.push t
  197. end
  198. elsif ['contains all', 'contains one', 'contains all not', 'contains one not'].include?(data[:operator])
  199. values = data[:value]
  200. if data[:value].is_a?(String)
  201. values = values.split(',').map(&:strip)
  202. end
  203. t[:query_string] = {}
  204. case data[:operator]
  205. when 'contains all'
  206. t[:query_string][:query] = "#{key_tmp}:(\"#{values.join('" AND "')}\")"
  207. query_must.push t
  208. when 'contains one not'
  209. t[:query_string][:query] = "#{key_tmp}:(\"#{values.join('" OR "')}\")"
  210. query_must_not.push t
  211. when 'contains one'
  212. t[:query_string][:query] = "#{key_tmp}:(\"#{values.join('" OR "')}\")"
  213. query_must.push t
  214. when 'contains all not'
  215. t[:query_string][:query] = "#{key_tmp}:(\"#{values.join('" AND "')}\")"
  216. query_must_not.push t
  217. end
  218. # within last/within next (relative)
  219. elsif ['within last (relative)', 'within next (relative)'].include?(data[:operator])
  220. range = relative_map[data[:range].to_sym]
  221. if range.blank?
  222. raise "Invalid relative_map for range '#{data[:range]}'."
  223. end
  224. t[:range] = {}
  225. t[:range][key_tmp] = {}
  226. if data[:operator] == 'within last (relative)'
  227. t[:range][key_tmp][:gte] = "now-#{data[:value]}#{range}"
  228. else
  229. t[:range][key_tmp][:lt] = "now+#{data[:value]}#{range}"
  230. end
  231. query_must.push t
  232. # before/after (relative)
  233. elsif ['before (relative)', 'after (relative)'].include?(data[:operator])
  234. range = relative_map[data[:range].to_sym]
  235. if range.blank?
  236. raise "Invalid relative_map for range '#{data[:range]}'."
  237. end
  238. t[:range] = {}
  239. t[:range][key_tmp] = {}
  240. if data[:operator] == 'before (relative)'
  241. t[:range][key_tmp][:lt] = "now-#{data[:value]}#{range}"
  242. else
  243. t[:range][key_tmp][:gt] = "now+#{data[:value]}#{range}"
  244. end
  245. query_must.push t
  246. # till/from (relative)
  247. elsif ['till (relative)', 'from (relative)'].include?(data[:operator])
  248. range = relative_map[data[:range].to_sym]
  249. if range.blank?
  250. raise "Invalid relative_map for range '#{data[:range]}'."
  251. end
  252. t[:range] = {}
  253. t[:range][key_tmp] = {}
  254. if data[:operator] == 'till (relative)'
  255. t[:range][key_tmp][:lt] = "now+#{data[:value]}#{range}"
  256. else
  257. t[:range][key_tmp][:gt] = "now-#{data[:value]}#{range}"
  258. end
  259. query_must.push t
  260. # before/after (absolute)
  261. elsif ['before (absolute)', 'after (absolute)'].include?(data[:operator])
  262. t[:range] = {}
  263. t[:range][key_tmp] = {}
  264. if data[:operator] == 'before (absolute)'
  265. t[:range][key_tmp][:lt] = (data[:value])
  266. else
  267. t[:range][key_tmp][:gt] = (data[:value])
  268. end
  269. query_must.push t
  270. else
  271. raise "unknown operator '#{data[:operator]}' for #{key}"
  272. end
  273. data = {
  274. bool: {},
  275. }
  276. if query_must.present?
  277. data[:bool][:must] = query_must
  278. end
  279. if query_must_not.present?
  280. data[:bool][:must_not] = query_must_not
  281. end
  282. data
  283. end
  284. end