search_index_es.rake 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314
  1. $LOAD_PATH << './lib'
  2. require 'rubygems'
  3. namespace :searchindex do
  4. task :drop, [:opts] => :environment do |_t, _args|
  5. print 'drop indexes...'
  6. # drop indexes
  7. if es_multi_index?
  8. Models.indexable.each do |local_object|
  9. SearchIndexBackend.index(
  10. action: 'delete',
  11. name: local_object.name,
  12. )
  13. end
  14. else
  15. SearchIndexBackend.index(
  16. action: 'delete',
  17. )
  18. end
  19. puts 'done'
  20. Rake::Task['searchindex:drop_pipeline'].execute
  21. end
  22. task :create, [:opts] => :environment do |_t, _args|
  23. print 'create indexes...'
  24. if es_multi_index?
  25. Setting.set('es_multi_index', true)
  26. else
  27. Setting.set('es_multi_index', false)
  28. end
  29. settings = {
  30. 'index.mapping.total_fields.limit': 2000,
  31. }
  32. # create indexes
  33. if es_multi_index?
  34. Models.indexable.each do |local_object|
  35. SearchIndexBackend.index(
  36. action: 'create',
  37. name: local_object.name,
  38. data: {
  39. mappings: get_mapping_properties_object(local_object),
  40. settings: settings,
  41. }
  42. )
  43. end
  44. else
  45. mapping = {}
  46. Models.indexable.each do |local_object|
  47. mapping.merge!(get_mapping_properties_object(local_object))
  48. end
  49. SearchIndexBackend.index(
  50. action: 'create',
  51. data: {
  52. mappings: mapping,
  53. settings: settings,
  54. }
  55. )
  56. end
  57. puts 'done'
  58. Rake::Task['searchindex:create_pipeline'].execute
  59. end
  60. task :create_pipeline, [:opts] => :environment do |_t, _args|
  61. if !es_pipeline?
  62. Setting.set('es_pipeline', '')
  63. next
  64. end
  65. # update processors
  66. pipeline = Setting.get('es_pipeline')
  67. if pipeline.blank?
  68. pipeline = "zammad#{rand(999_999_999_999)}"
  69. Setting.set('es_pipeline', pipeline)
  70. end
  71. print 'create pipeline (pipeline)... '
  72. SearchIndexBackend.processors(
  73. "_ingest/pipeline/#{pipeline}": [
  74. {
  75. action: 'delete',
  76. },
  77. {
  78. action: 'create',
  79. description: 'Extract zammad-attachment information from arrays',
  80. processors: [
  81. {
  82. foreach: {
  83. field: 'article',
  84. ignore_failure: true,
  85. processor: {
  86. foreach: {
  87. field: '_ingest._value.attachment',
  88. ignore_failure: true,
  89. processor: {
  90. attachment: {
  91. target_field: '_ingest._value',
  92. field: '_ingest._value._content',
  93. ignore_failure: true,
  94. }
  95. }
  96. }
  97. }
  98. }
  99. }
  100. ]
  101. }
  102. ]
  103. )
  104. puts 'done'
  105. end
  106. task :drop_pipeline, [:opts] => :environment do |_t, _args|
  107. next if !es_pipeline?
  108. # update processors
  109. pipeline = Setting.get('es_pipeline')
  110. next if pipeline.blank?
  111. print 'delete pipeline (pipeline)... '
  112. SearchIndexBackend.processors(
  113. "_ingest/pipeline/#{pipeline}": [
  114. {
  115. action: 'delete',
  116. },
  117. ]
  118. )
  119. puts 'done'
  120. end
  121. task :reload, [:opts] => :environment do |_t, _args|
  122. puts 'reload data...'
  123. Models.indexable.each do |model_class|
  124. puts " reload #{model_class}"
  125. started_at = Time.zone.now
  126. puts " - started at #{started_at}"
  127. model_class.search_index_reload
  128. took = Time.zone.now - started_at
  129. puts " - took #{took.to_i} seconds"
  130. end
  131. end
  132. task :rebuild, [:opts] => :environment do |_t, _args|
  133. Rake::Task['searchindex:drop'].execute
  134. Rake::Task['searchindex:create'].execute
  135. Rake::Task['searchindex:reload'].execute
  136. end
  137. end
  138. =begin
  139. This function will return a index mapping based on the
  140. attributes of the database table of the existing object.
  141. mapping = get_mapping_properties_object(Ticket)
  142. Returns:
  143. mapping = {
  144. User: {
  145. properties: {
  146. firstname: {
  147. type: 'keyword',
  148. },
  149. }
  150. }
  151. }
  152. =end
  153. def get_mapping_properties_object(object)
  154. name = object.name
  155. if es_multi_index?
  156. name = '_doc'
  157. end
  158. result = {
  159. name => {
  160. properties: {}
  161. }
  162. }
  163. store_columns = %w[preferences data]
  164. # for elasticsearch 6.x and later
  165. string_type = 'text'
  166. string_raw = { 'type': 'keyword' }
  167. boolean_raw = { 'type': 'boolean' }
  168. # for elasticsearch 5.6 and lower
  169. if !es_multi_index?
  170. string_type = 'string'
  171. string_raw = { 'type': 'string', 'index': 'not_analyzed' }
  172. boolean_raw = { 'type': 'boolean', 'index': 'not_analyzed' }
  173. end
  174. object.columns_hash.each do |key, value|
  175. if value.type == :string && value.limit && value.limit <= 5000 && store_columns.exclude?(key)
  176. result[name][:properties][key] = {
  177. type: string_type,
  178. fields: {
  179. raw: string_raw,
  180. }
  181. }
  182. elsif value.type == :integer
  183. result[name][:properties][key] = {
  184. type: 'integer',
  185. }
  186. elsif value.type == :datetime
  187. result[name][:properties][key] = {
  188. type: 'date',
  189. }
  190. elsif value.type == :boolean
  191. result[name][:properties][key] = {
  192. type: 'boolean',
  193. fields: {
  194. raw: boolean_raw,
  195. }
  196. }
  197. elsif value.type == :binary
  198. result[name][:properties][key] = {
  199. type: 'binary',
  200. }
  201. elsif value.type == :bigint
  202. result[name][:properties][key] = {
  203. type: 'long',
  204. }
  205. elsif value.type == :decimal
  206. result[name][:properties][key] = {
  207. type: 'float',
  208. }
  209. elsif value.type == :date
  210. result[name][:properties][key] = {
  211. type: 'date',
  212. }
  213. end
  214. end
  215. # es with mapper-attachments plugin
  216. if object.name == 'Ticket'
  217. # do not server attachments if document is requested
  218. result[name][:_source] = {
  219. excludes: ['article.attachment']
  220. }
  221. # for elasticsearch 5.5 and lower
  222. if !es_pipeline?
  223. result[name][:_source] = {
  224. excludes: ['article.attachment']
  225. }
  226. result[name][:properties][:article] = {
  227. type: 'nested',
  228. include_in_parent: true,
  229. properties: {
  230. attachment: {
  231. type: 'attachment',
  232. }
  233. }
  234. }
  235. end
  236. end
  237. return result if es_type_in_mapping?
  238. result[name]
  239. end
  240. # get es version
  241. def es_version
  242. @es_version ||= begin
  243. info = SearchIndexBackend.info
  244. number = nil
  245. if info.present?
  246. number = info['version']['number'].to_s
  247. end
  248. number
  249. end
  250. end
  251. # no es_pipeline for elasticsearch 5.5 and lower
  252. def es_pipeline?
  253. number = es_version
  254. return false if number.blank?
  255. return false if number =~ /^[2-4]\./
  256. return false if number =~ /^5\.[0-5]\./
  257. true
  258. end
  259. # no mulit index for elasticsearch 5.6 and lower
  260. def es_multi_index?
  261. number = es_version
  262. return false if number.blank?
  263. return false if number =~ /^[2-5]\./
  264. true
  265. end
  266. # no type in mapping
  267. def es_type_in_mapping?
  268. number = es_version
  269. return true if number.blank?
  270. return true if number =~ /^[2-6]\./
  271. false
  272. end