2016-10-19 03:11:36 +00:00
# Copyright (C) 2012-2016 Zammad Foundation, http://zammad-foundation.org/
2014-01-27 22:59:41 +00:00
class SearchIndexBackend
= begin
2017-12-04 00:24:58 +00:00
info about used search index machine
SearchIndexBackend . info
= end
def self . info
url = Setting . get ( 'es_url' ) . to_s
2017-12-04 01:12:55 +00:00
return if url . blank?
2017-12-04 00:24:58 +00:00
Rails . logger . info " # curl -X GET \" #{ url } \" "
response = UserAgent . get (
url ,
{ } ,
{
json : true ,
open_timeout : 8 ,
read_timeout : 12 ,
user : Setting . get ( 'es_user' ) ,
password : Setting . get ( 'es_password' ) ,
}
)
Rails . logger . info " # #{ response . code } "
2018-03-08 15:30:07 +00:00
return response . data if response . success?
raise humanized_error (
verb : 'GET' ,
url : url ,
response : response ,
)
2017-12-04 00:24:58 +00:00
end
= begin
update processors
SearchIndexBackend . processors (
_ingest / pipeline / attachment : {
description : 'Extract attachment information from arrays' ,
processors : [
{
foreach : {
field : 'ticket.articles.attachments' ,
processor : {
attachment : {
target_field : '_ingest._value.attachment' ,
field : '_ingest._value.data'
}
}
}
}
]
}
)
= end
def self . processors ( data )
data . each do | key , items |
url = " #{ Setting . get ( 'es_url' ) } / #{ key } "
items . each do | item |
if item [ :action ] == 'delete'
2017-12-04 00:48:41 +00:00
Rails . logger . info " # curl -X DELETE \" #{ url } \" "
response = UserAgent . delete (
url ,
{
json : true ,
open_timeout : 8 ,
read_timeout : 12 ,
user : Setting . get ( 'es_user' ) ,
password : Setting . get ( 'es_password' ) ,
}
)
Rails . logger . info " # #{ response . code } "
next if response . success?
2017-12-15 13:58:41 +00:00
next if response . code . to_s == '404'
2018-03-08 15:30:07 +00:00
raise humanized_error (
verb : 'DELETE' ,
url : url ,
response : response ,
)
2017-12-04 00:24:58 +00:00
end
Rails . logger . info " # curl -X PUT \" #{ url } \" \\ "
Rails . logger . debug " -d ' #{ data . to_json } ' "
item . delete ( :action )
response = UserAgent . put (
url ,
item ,
{
json : true ,
open_timeout : 8 ,
read_timeout : 12 ,
user : Setting . get ( 'es_user' ) ,
password : Setting . get ( 'es_password' ) ,
}
)
Rails . logger . info " # #{ response . code } "
next if response . success?
2018-03-08 15:30:07 +00:00
raise humanized_error (
verb : 'PUT' ,
url : url ,
payload : item ,
response : response ,
)
2017-12-04 00:24:58 +00:00
end
end
true
end
= begin
2014-02-03 12:08:41 +00:00
create / update / delete index
SearchIndexBackend . index (
:action = > 'create' , # create/update/delete
:data = > {
:mappings = > {
:Ticket = > {
:properties = > {
2015-04-06 19:00:16 +00:00
:articles = > {
2014-02-03 12:08:41 +00:00
:type = > 'nested' ,
:properties = > {
2017-06-16 22:56:28 +00:00
'attachment' = > { :type = > 'attachment' }
2014-04-28 15:30:06 +00:00
}
2014-02-03 12:08:41 +00:00
}
2014-04-28 15:30:06 +00:00
}
}
2014-02-03 12:08:41 +00:00
}
}
)
SearchIndexBackend . index (
:action = > 'delete' , # create/update/delete
:name = > 'Ticket' , # optional
)
SearchIndexBackend . index (
:action = > 'delete' , # create/update/delete
)
= end
def self . index ( data )
2016-04-26 12:42:55 +00:00
url = build_url ( data [ :name ] )
2017-12-15 13:58:41 +00:00
return if url . blank?
2014-02-03 12:08:41 +00:00
if data [ :action ] && data [ :action ] == 'delete'
2016-04-26 12:42:55 +00:00
return SearchIndexBackend . remove ( data [ :name ] )
2014-02-03 12:08:41 +00:00
end
2015-05-04 18:58:28 +00:00
Rails . logger . info " # curl -X PUT \" #{ url } \" \\ "
2015-10-20 08:48:43 +00:00
Rails . logger . debug " -d ' #{ data [ :data ] . to_json } ' "
2014-02-03 12:08:41 +00:00
2015-03-23 00:31:30 +00:00
response = UserAgent . put (
url ,
data [ :data ] ,
{
2015-04-27 13:42:53 +00:00
json : true ,
2017-12-04 00:24:58 +00:00
open_timeout : 8 ,
read_timeout : 12 ,
2015-04-27 13:42:53 +00:00
user : Setting . get ( 'es_user' ) ,
password : Setting . get ( 'es_password' ) ,
2015-03-23 00:31:30 +00:00
}
)
2015-05-05 08:26:53 +00:00
Rails . logger . info " # #{ response . code } "
2014-02-03 12:08:41 +00:00
return true if response . success?
2018-03-08 15:30:07 +00:00
raise humanized_error (
verb : 'PUT' ,
url : url ,
payload : data [ :data ] ,
response : response ,
)
2014-02-03 12:08:41 +00:00
end
= begin
2014-01-27 22:59:41 +00:00
add new object to search index
2016-04-26 12:42:55 +00:00
SearchIndexBackend . add ( 'Ticket' , some_data_object )
2014-01-27 22:59:41 +00:00
= end
def self . add ( type , data )
2016-04-26 12:42:55 +00:00
url = build_url ( type , data [ 'id' ] )
2017-12-15 13:58:41 +00:00
return if url . blank?
2014-01-27 22:59:41 +00:00
2015-05-04 18:58:28 +00:00
Rails . logger . info " # curl -X POST \" #{ url } \" \\ "
2015-10-20 08:48:43 +00:00
Rails . logger . debug " -d ' #{ data . to_json } ' "
2014-01-27 22:59:41 +00:00
2015-03-23 00:31:30 +00:00
response = UserAgent . post (
url ,
data ,
{
2015-04-27 13:42:53 +00:00
json : true ,
2017-12-04 00:24:58 +00:00
open_timeout : 8 ,
read_timeout : 16 ,
2015-04-27 13:42:53 +00:00
user : Setting . get ( 'es_user' ) ,
password : Setting . get ( 'es_password' ) ,
2015-03-23 00:31:30 +00:00
}
)
2015-05-06 09:30:39 +00:00
Rails . logger . info " # #{ response . code } "
2014-01-27 22:59:41 +00:00
return true if response . success?
2018-03-08 15:30:07 +00:00
raise humanized_error (
verb : 'POST' ,
url : url ,
payload : data ,
response : response ,
)
2014-01-27 22:59:41 +00:00
end
= begin
remove whole data from index
2016-04-26 12:42:55 +00:00
SearchIndexBackend . remove ( 'Ticket' , 123 )
2014-01-27 22:59:41 +00:00
2016-04-26 12:42:55 +00:00
SearchIndexBackend . remove ( 'Ticket' )
2014-01-27 22:59:41 +00:00
= end
2016-04-26 12:42:55 +00:00
def self . remove ( type , o_id = nil )
url = build_url ( type , o_id )
2017-12-15 13:58:41 +00:00
return if url . blank?
2014-01-27 22:59:41 +00:00
2015-05-05 08:26:53 +00:00
Rails . logger . info " # curl -X DELETE \" #{ url } \" "
2014-01-27 22:59:41 +00:00
2015-03-23 00:31:30 +00:00
response = UserAgent . delete (
url ,
{
2017-12-04 00:24:58 +00:00
open_timeout : 8 ,
read_timeout : 16 ,
2015-04-27 13:42:53 +00:00
user : Setting . get ( 'es_user' ) ,
password : Setting . get ( 'es_password' ) ,
2015-03-23 00:31:30 +00:00
}
)
2015-10-20 08:48:43 +00:00
Rails . logger . info " # #{ response . code } "
2015-03-23 00:31:30 +00:00
return true if response . success?
2017-12-15 13:58:41 +00:00
return true if response . code . to_s == '400'
2018-03-08 15:30:07 +00:00
humanized_error = humanized_error (
verb : 'DELETE' ,
url : url ,
response : response ,
)
Rails . logger . info " NOTICE: can't delete index: #{ humanized_error } "
2015-04-06 19:00:16 +00:00
false
2014-01-27 22:59:41 +00:00
end
= begin
2014-01-29 23:51:55 +00:00
return search result
2014-01-27 22:59:41 +00:00
2016-04-26 12:42:55 +00:00
result = SearchIndexBackend . search ( 'search query' , limit , [ 'User' , 'Organization' ] )
2014-09-19 21:35:40 +00:00
2016-04-26 12:42:55 +00:00
result = SearchIndexBackend . search ( 'search query' , limit , 'User' )
2014-01-27 22:59:41 +00:00
2014-09-19 21:35:40 +00:00
result = [
{
:id = > 123 ,
:type = > 'User' ,
} ,
{
:id = > 125 ,
:type = > 'User' ,
} ,
{
:id = > 15 ,
:type = > 'Organization' ,
}
]
2014-01-27 22:59:41 +00:00
= end
2016-04-26 12:42:55 +00:00
def self . search ( query , limit = 10 , index = nil , query_extention = { } )
2017-12-15 13:58:41 +00:00
return [ ] if query . blank?
2016-07-07 23:22:09 +00:00
if index . class == Array
ids = [ ]
2017-10-01 12:25:52 +00:00
index . each do | local_index |
2016-07-07 23:22:09 +00:00
local_ids = search_by_index ( query , limit , local_index , query_extention )
ids = ids . concat ( local_ids )
2017-10-01 12:25:52 +00:00
end
2016-07-07 23:22:09 +00:00
return ids
end
search_by_index ( query , limit , index , query_extention )
end
def self . search_by_index ( query , limit = 10 , index = nil , query_extention = { } )
2017-12-15 13:58:41 +00:00
return [ ] if query . blank?
2014-01-29 23:51:55 +00:00
2017-12-04 00:24:58 +00:00
url = build_url
2017-12-15 13:58:41 +00:00
return if url . blank?
2016-01-15 17:22:57 +00:00
url += if index
if index . class == Array
" / #{ index . join ( ',' ) } /_search "
else
" / #{ index } /_search "
end
else
'/_search'
end
2014-01-29 23:51:55 +00:00
data = { }
data [ 'from' ] = 0
2015-08-16 00:53:27 +00:00
data [ 'size' ] = limit
2014-04-28 15:30:06 +00:00
data [ 'sort' ] =
2016-01-15 17:22:57 +00:00
[
{
updated_at : {
order : 'desc'
}
} ,
'_score'
]
2014-02-03 12:08:41 +00:00
2014-02-02 18:58:31 +00:00
data [ 'query' ] = query_extention || { }
2017-12-15 13:58:41 +00:00
data [ 'query' ] [ 'bool' ] || = { }
data [ 'query' ] [ 'bool' ] [ 'must' ] || = [ ]
2014-01-29 23:51:55 +00:00
2017-08-23 22:53:36 +00:00
# add * on simple query like "somephrase23" or "attribute: somephrase23"
if query . present?
query . strip!
2017-12-04 00:24:58 +00:00
if query . match? ( / ^([[:alpha:],0-9]+|[[:alpha:],0-9]+ \ : \ s+[[:alpha:],0-9]+)$ / )
2017-08-23 22:53:36 +00:00
query += '*'
end
2016-03-14 22:29:39 +00:00
end
2014-02-03 12:08:41 +00:00
# real search condition
2014-02-02 18:58:31 +00:00
condition = {
'query_string' = > {
2017-05-15 16:20:39 +00:00
'query' = > query ,
'default_operator' = > 'AND' ,
2014-02-02 18:58:31 +00:00
}
}
data [ 'query' ] [ 'bool' ] [ 'must' ] . push condition
2014-01-29 23:51:55 +00:00
2015-05-04 18:58:28 +00:00
Rails . logger . info " # curl -X POST \" #{ url } \" \\ "
2015-10-20 08:48:43 +00:00
Rails . logger . debug " -d' #{ data . to_json } ' "
2014-01-29 23:51:55 +00:00
2015-03-23 00:31:30 +00:00
response = UserAgent . get (
url ,
data ,
{
2015-04-27 13:42:53 +00:00
json : true ,
open_timeout : 5 ,
read_timeout : 14 ,
user : Setting . get ( 'es_user' ) ,
password : Setting . get ( 'es_password' ) ,
2015-03-23 00:31:30 +00:00
}
)
2015-05-06 09:30:39 +00:00
Rails . logger . info " # #{ response . code } "
2014-02-03 18:26:22 +00:00
if ! response . success?
2018-03-08 15:30:07 +00:00
Rails . logger . error humanized_error (
verb : 'GET' ,
url : url ,
payload : data ,
response : response ,
)
2014-02-03 19:40:42 +00:00
return [ ]
2014-02-03 18:26:22 +00:00
end
2015-03-23 00:31:30 +00:00
data = response . data
2014-02-03 18:26:22 +00:00
2014-01-29 23:51:55 +00:00
ids = [ ]
return ids if ! data
return ids if ! data [ 'hits' ]
return ids if ! data [ 'hits' ] [ 'hits' ]
2017-10-01 12:25:52 +00:00
data [ 'hits' ] [ 'hits' ] . each do | item |
2015-05-06 09:30:39 +00:00
Rails . logger . info " ... #{ item [ '_type' ] } #{ item [ '_id' ] } "
2014-09-19 21:35:40 +00:00
data = {
2015-04-27 13:42:53 +00:00
id : item [ '_id' ] ,
type : item [ '_type' ] ,
2014-09-19 21:35:40 +00:00
}
ids . push data
2017-10-01 12:25:52 +00:00
end
2014-02-03 18:26:22 +00:00
ids
2014-01-27 22:59:41 +00:00
end
2014-02-02 18:58:31 +00:00
= begin
2015-10-29 02:33:36 +00:00
get count of tickets and tickets which match on selector
2015-10-20 08:48:43 +00:00
2015-10-29 02:33:36 +00:00
aggs_interval = {
from : '2015-01-01' ,
to : '2015-12-31' ,
interval : 'month' , # year, quarter, month, week, day, hour, minute, second
field : 'created_at' ,
}
2015-10-20 08:48:43 +00:00
2015-10-29 02:33:36 +00:00
result = SearchIndexBackend . selectors ( index , params [ :condition ] , limit , current_user , aggs_interval )
2015-10-20 08:48:43 +00:00
2015-10-29 02:33:36 +00:00
# for aggregations
2015-10-20 08:48:43 +00:00
result = {
hits : {
total : 4819 ,
} ,
aggregations : {
time_buckets : {
buckets : [
{
key_as_string : " 2014-10-01T00:00:00.000Z " ,
key : 1412121600000 ,
doc_count : 420
} ,
{
key_as_string : " 2014-11-01T00:00:00.000Z " ,
key : 1414800000000 ,
doc_count : 561
} ,
...
]
}
}
}
= end
2015-10-29 18:20:22 +00:00
def self . selectors ( index = nil , selectors = nil , limit = 10 , current_user = nil , aggs_interval = nil )
2016-03-01 14:26:46 +00:00
raise 'no selectors given' if ! selectors
2015-10-20 08:48:43 +00:00
2017-12-04 00:24:58 +00:00
url = build_url
2017-12-15 13:58:41 +00:00
return if url . blank?
2016-01-15 17:22:57 +00:00
url += if index
if index . class == Array
" / #{ index . join ( ',' ) } /_search "
else
" / #{ index } /_search "
end
else
'/_search'
end
2015-10-20 08:48:43 +00:00
2015-10-29 18:20:22 +00:00
data = selector2query ( selectors , current_user , aggs_interval , limit )
2015-10-20 08:48:43 +00:00
Rails . logger . info " # curl -X POST \" #{ url } \" \\ "
Rails . logger . debug " -d' #{ data . to_json } ' "
response = UserAgent . get (
url ,
data ,
{
json : true ,
open_timeout : 5 ,
read_timeout : 14 ,
user : Setting . get ( 'es_user' ) ,
password : Setting . get ( 'es_password' ) ,
}
)
Rails . logger . info " # #{ response . code } "
if ! response . success?
2018-03-08 15:30:07 +00:00
raise humanized_error (
verb : 'GET' ,
url : url ,
payload : data ,
response : response ,
)
2015-10-20 08:48:43 +00:00
end
Rails . logger . debug response . data . to_json
2015-10-29 02:33:36 +00:00
2017-12-15 13:58:41 +00:00
if aggs_interval . blank? || aggs_interval [ :interval ] . blank?
2015-10-29 02:33:36 +00:00
ticket_ids = [ ]
2017-10-01 12:25:52 +00:00
response . data [ 'hits' ] [ 'hits' ] . each do | item |
2015-10-29 02:33:36 +00:00
ticket_ids . push item [ '_id' ]
2017-10-01 12:25:52 +00:00
end
2015-10-29 02:33:36 +00:00
return {
count : response . data [ 'hits' ] [ 'total' ] ,
ticket_ids : ticket_ids ,
}
end
2015-10-20 08:48:43 +00:00
response . data
end
2015-10-29 18:20:22 +00:00
def self . selector2query ( selector , _current_user , aggs_interval , limit )
2015-10-29 02:33:36 +00:00
query_must = [ ]
query_must_not = [ ]
2017-12-04 00:24:58 +00:00
if selector . present?
2017-10-01 12:25:52 +00:00
selector . each do | key , data |
2015-10-29 02:33:36 +00:00
key_tmp = key . sub ( / ^.+? \ . / , '' )
t = { }
if data [ 'value' ] . class == Array
t [ :terms ] = { }
t [ :terms ] [ key_tmp ] = data [ 'value' ]
else
t [ :term ] = { }
t [ :term ] [ key_tmp ] = data [ 'value' ]
end
if data [ 'operator' ] == 'is'
2016-11-18 14:42:06 +00:00
query_must . push t
2015-10-29 02:33:36 +00:00
elsif data [ 'operator' ] == 'is not'
2016-11-18 14:42:06 +00:00
query_must_not . push t
2015-10-29 02:33:36 +00:00
elsif data [ 'operator' ] == 'contains'
query_must . push t
elsif data [ 'operator' ] == 'contains not'
query_must_not . push t
else
2018-01-15 11:54:26 +00:00
raise " unknown operator ' #{ data [ 'operator' ] } ' for #{ key } "
2015-10-29 02:33:36 +00:00
end
2017-10-01 12:25:52 +00:00
end
2015-10-29 02:33:36 +00:00
end
data = {
2015-10-29 09:45:29 +00:00
query : { } ,
2015-10-29 18:20:22 +00:00
size : limit ,
2015-10-29 02:33:36 +00:00
}
# add aggs to filter
2017-12-15 13:58:41 +00:00
if aggs_interval . present?
if aggs_interval [ :interval ] . present?
2015-10-29 02:33:36 +00:00
data [ :size ] = 0
data [ :aggs ] = {
time_buckets : {
date_histogram : {
field : aggs_interval [ :field ] ,
interval : aggs_interval [ :interval ] ,
}
}
}
end
r = { }
r [ :range ] = { }
r [ :range ] [ aggs_interval [ :field ] ] = {
from : aggs_interval [ :from ] ,
to : aggs_interval [ :to ] ,
}
2016-11-18 14:42:06 +00:00
query_must . push r
2015-10-29 02:33:36 +00:00
end
2017-12-15 13:58:41 +00:00
data [ :query ] [ :bool ] || = { }
2016-11-18 14:42:06 +00:00
2017-11-23 08:09:44 +00:00
if query_must . present?
2016-11-18 14:42:06 +00:00
data [ :query ] [ :bool ] [ :must ] = query_must
2015-10-29 02:33:36 +00:00
end
2017-11-23 08:09:44 +00:00
if query_must_not . present?
2016-11-18 14:42:06 +00:00
data [ :query ] [ :bool ] [ :must_not ] = query_must_not
2015-10-29 02:33:36 +00:00
end
2015-10-29 09:07:45 +00:00
# add sort
2017-12-15 13:58:41 +00:00
if aggs_interval . present? && aggs_interval [ :field ] . present? && aggs_interval [ :interval ] . blank?
2015-10-29 09:07:45 +00:00
sort = [ ]
sort [ 0 ] = { }
sort [ 0 ] [ aggs_interval [ :field ] ] = {
order : 'desc'
}
sort [ 1 ] = '_score'
data [ 'sort' ] = sort
end
2015-10-29 02:33:36 +00:00
data
end
2015-10-20 08:48:43 +00:00
= begin
2014-04-28 15:30:06 +00:00
return true if backend is configured
2014-02-02 18:58:31 +00:00
result = SearchIndexBackend . enabled?
= end
def self . enabled?
2017-06-28 17:13:52 +00:00
return false if Setting . get ( 'es_url' ) . blank?
2014-02-02 18:58:31 +00:00
true
end
2016-04-26 12:42:55 +00:00
def self . build_url ( type = nil , o_id = nil )
2014-02-02 18:58:31 +00:00
return if ! SearchIndexBackend . enabled?
2016-07-07 23:22:09 +00:00
index = " #{ Setting . get ( 'es_index' ) } _ #{ Rails . env } "
2014-01-28 09:58:49 +00:00
url = Setting . get ( 'es_url' )
2016-01-15 17:22:57 +00:00
url = if type
2017-12-04 00:24:58 +00:00
url_pipline = Setting . get ( 'es_pipeline' )
if url_pipline . present?
url_pipline = " ?pipeline= #{ url_pipline } "
end
2016-01-15 17:22:57 +00:00
if o_id
2017-12-04 00:24:58 +00:00
" #{ url } / #{ index } / #{ type } / #{ o_id } #{ url_pipline } "
2016-01-15 17:22:57 +00:00
else
2017-12-04 00:24:58 +00:00
" #{ url } / #{ index } / #{ type } #{ url_pipline } "
2016-01-15 17:22:57 +00:00
end
else
" #{ url } / #{ index } "
end
2014-01-28 09:58:49 +00:00
url
end
2018-03-08 15:30:07 +00:00
def self . humanized_error ( verb : , url : , payload : nil , response : )
prefix = " Unable to process #{ verb } request to elasticsearch URL ' #{ url } '. "
suffix = " \n \n Response: \n #{ response . inspect } \n \n Payload: \n #{ payload . inspect } "
if payload . respond_to? ( :to_json )
suffix += " \n \n Payload size: #{ payload . to_json . bytesize / 1024 / 1024 } M "
end
message = if response & . error & . match? ( 'Connection refused' )
" Elasticsearch is not reachable, probably because it's not running or even installed. "
elsif url . end_with? ( 'pipeline/zammad-attachment' ) && response . code == 400
'The installed attachment plugin could not handle the request payload. Ensure that the correct attachment plugin is installed (5.6 => ingest-attachment, 2.4 - 5.5 => mapper-attachments).'
else
'Check the response and payload for detailed information: '
end
" #{ prefix } #{ message } #{ suffix } "
end
2015-04-27 14:15:29 +00:00
end