search.rb 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224
  1. # Copyright (C) 2012-2023 Zammad Foundation, https://zammad-foundation.org/
  2. module Ticket::Search
  3. extend ActiveSupport::Concern
  4. # methods defined here are going to extend the class, not the instance of it
  5. class_methods do
  6. =begin
  7. search tickets preferences
  8. result = Ticket.search_preferences(user_model)
  9. returns if user has permissions to search
  10. result = {
  11. prio: 3000,
  12. direct_search_index: false
  13. }
  14. returns if user has no permissions to search
  15. result = false
  16. =end
  17. def search_preferences(_current_user)
  18. {
  19. prio: 3000,
  20. direct_search_index: false,
  21. }
  22. end
  23. =begin
  24. search tickets via search index
  25. result = Ticket.search(
  26. current_user: User.find(123),
  27. query: 'search something',
  28. scope: TicketPolicy::ReadScope, # defaults to ReadScope
  29. limit: 15,
  30. offset: 100,
  31. )
  32. returns
  33. result = [ticket_model1, ticket_model2]
  34. search tickets via search index
  35. result = Ticket.search(
  36. current_user: User.find(123),
  37. query: 'search something',
  38. limit: 15,
  39. offset: 100,
  40. full: false,
  41. )
  42. returns
  43. result = [1,3,5,6,7]
  44. search tickets via database
  45. result = Ticket.search(
  46. current_user: User.find(123),
  47. query: 'some query', # query or condition is required
  48. scope: TicketPolicy::ReadScope, # defaults to ReadScope
  49. condition: {
  50. 'tickets.owner_id' => {
  51. operator: 'is',
  52. value: user.id,
  53. },
  54. 'tickets.state_id' => {
  55. operator: 'is',
  56. value: Ticket::State.where(
  57. state_type_id: Ticket::StateType.where(
  58. name: [
  59. 'pending reminder',
  60. 'pending action',
  61. ],
  62. ).map(&:id),
  63. ),
  64. },
  65. },
  66. limit: 15,
  67. offset: 100,
  68. # sort single column
  69. sort_by: 'created_at',
  70. order_by: 'asc',
  71. # sort multiple columns
  72. sort_by: [ 'created_at', 'updated_at' ],
  73. order_by: [ 'asc', 'desc' ],
  74. full: false,
  75. )
  76. returns
  77. result = [1,3,5,6,7]
  78. =end
  79. def search(params)
  80. # get params
  81. query = params[:query]
  82. condition = params[:condition]
  83. scope = params[:scope] || TicketPolicy::ReadScope
  84. limit = params[:limit] || 12
  85. offset = params[:offset] || 0
  86. current_user = params[:current_user]
  87. full = false
  88. if params[:full] == true || params[:full] == 'true' || !params.key?(:full)
  89. full = true
  90. end
  91. sql_helper = ::SqlHelper.new(object: self)
  92. # check sort
  93. sort_by = sql_helper.get_sort_by(params, 'updated_at')
  94. # check order
  95. order_by = sql_helper.get_order_by(params, 'desc')
  96. # try search index backend
  97. if condition.blank? && SearchIndexBackend.enabled?
  98. query_or = []
  99. if current_user.permissions?('ticket.agent')
  100. group_ids = current_user.group_ids_access(scope.const_get(:ACCESS_TYPE))
  101. if group_ids.present?
  102. access_condition = {
  103. 'query_string' => { 'default_field' => 'group_id', 'query' => "\"#{group_ids.join('" OR "')}\"" }
  104. }
  105. query_or.push(access_condition)
  106. end
  107. end
  108. if current_user.permissions?('ticket.customer')
  109. organizations_query = current_user.all_organizations.where(shared: true).map { |o| "organization_id:#{o.id}" }.join(' OR ')
  110. access_condition = if organizations_query.present?
  111. {
  112. 'query_string' => { 'query' => "customer_id:#{current_user.id} OR #{organizations_query}" }
  113. }
  114. else
  115. {
  116. 'query_string' => { 'default_field' => 'customer_id', 'query' => current_user.id }
  117. }
  118. end
  119. query_or.push(access_condition)
  120. end
  121. return [] if query_or.blank?
  122. query_extension = {
  123. bool: {
  124. must: [
  125. {
  126. bool: {
  127. should: query_or,
  128. },
  129. },
  130. ],
  131. }
  132. }
  133. items = SearchIndexBackend.search(query, 'Ticket', limit: limit,
  134. query_extension: query_extension,
  135. from: offset,
  136. sort_by: sort_by,
  137. order_by: order_by)
  138. if !full
  139. ids = []
  140. items.each do |item|
  141. ids.push item[:id]
  142. end
  143. return ids
  144. end
  145. tickets = []
  146. items.each do |item|
  147. ticket = Ticket.lookup(id: item[:id])
  148. next if !ticket
  149. tickets.push ticket
  150. end
  151. return tickets
  152. end
  153. order_sql = sql_helper.get_order(sort_by, order_by, 'tickets.updated_at DESC')
  154. tickets_all = scope.new(current_user).resolve
  155. .order(Arel.sql(order_sql))
  156. .offset(offset)
  157. .limit(limit)
  158. ticket_ids = if query
  159. tickets_all.joins(:articles)
  160. .where(<<~SQL.squish, query: "%#{query.delete('*')}%")
  161. tickets.title LIKE :query
  162. OR tickets.number LIKE :query
  163. OR ticket_articles.body LIKE :query
  164. OR ticket_articles.from LIKE :query
  165. OR ticket_articles.to LIKE :query
  166. OR ticket_articles.subject LIKE :query
  167. SQL
  168. else
  169. query_condition, bind_condition, tables = selector2sql(condition)
  170. tickets_all.joins(tables)
  171. .where(query_condition, *bind_condition)
  172. end.group(:id).pluck(:id)
  173. if full
  174. ticket_ids.map { |id| Ticket.lookup(id: id) }
  175. else
  176. ticket_ids
  177. end
  178. end
  179. end
  180. end