Refactoring search index backend.
This commit is contained in:
parent
f6d0fb3596
commit
7f1ecaa67e
10 changed files with 473 additions and 375 deletions
|
@ -17,27 +17,8 @@ returns
|
||||||
def search_index_update_backend
|
def search_index_update_backend
|
||||||
return if !self.class.search_index_support_config
|
return if !self.class.search_index_support_config
|
||||||
|
|
||||||
# default ignored attributes
|
|
||||||
ignore_attributes = {}
|
|
||||||
if self.class.search_index_support_config[:ignore_attributes]
|
|
||||||
self.class.search_index_support_config[:ignore_attributes].each { |key, value|
|
|
||||||
ignore_attributes[key] = value
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
# for performance reasons, Model.search_index_reload will only collect if of object
|
|
||||||
# get whole data here
|
|
||||||
data = self.class.find(id)
|
|
||||||
|
|
||||||
# remove ignored attributes
|
|
||||||
attributes = data.attributes
|
|
||||||
ignore_attributes.each { |key, value|
|
|
||||||
next if value != true
|
|
||||||
attributes.delete(key.to_s)
|
|
||||||
}
|
|
||||||
|
|
||||||
# fill up with search data
|
# fill up with search data
|
||||||
attributes = search_index_attribute_lookup(attributes, data)
|
attributes = search_index_attribute_lookup
|
||||||
return if !attributes
|
return if !attributes
|
||||||
|
|
||||||
# update backend
|
# update backend
|
||||||
|
@ -53,16 +34,20 @@ get data to store in search index
|
||||||
|
|
||||||
returns
|
returns
|
||||||
|
|
||||||
result = true # false
|
result = {
|
||||||
|
attribute1: 'some value',
|
||||||
|
attribute2: ['value 1', 'value 2'],
|
||||||
|
...
|
||||||
|
}
|
||||||
|
|
||||||
=end
|
=end
|
||||||
|
|
||||||
def search_index_data
|
def search_index_data
|
||||||
attributes = {}
|
attributes = {}
|
||||||
%w(name note).each { |key|
|
%w(name note).each { |key|
|
||||||
if self[key] && !self[key].empty?
|
next if !self[key]
|
||||||
|
next if self[key].respond_to?('empty?') && self[key].empty?
|
||||||
attributes[key] = self[key]
|
attributes[key] = self[key]
|
||||||
end
|
|
||||||
}
|
}
|
||||||
return if attributes.empty?
|
return if attributes.empty?
|
||||||
attributes
|
attributes
|
||||||
|
@ -74,8 +59,8 @@ returns
|
||||||
|
|
||||||
lookup name of ref. objects
|
lookup name of ref. objects
|
||||||
|
|
||||||
ticket = Ticket.find(123)
|
ticket = Ticket.find(3)
|
||||||
attributes = ticket.search_index_attribute_lookup(attributes, Ticket)
|
attributes = ticket.search_index_attribute_lookup
|
||||||
|
|
||||||
returns
|
returns
|
||||||
|
|
||||||
|
@ -83,10 +68,10 @@ returns
|
||||||
|
|
||||||
=end
|
=end
|
||||||
|
|
||||||
def search_index_attribute_lookup(attributes, ref_object)
|
def search_index_attribute_lookup
|
||||||
|
|
||||||
attributes_new = {}
|
attributes = self.attributes
|
||||||
attributes.each { |key, value|
|
self.attributes.each { |key, value|
|
||||||
next if !value
|
next if !value
|
||||||
|
|
||||||
# get attribute name
|
# get attribute name
|
||||||
|
@ -96,10 +81,10 @@ returns
|
||||||
attribute_name = attribute_name[ 0, attribute_name.length - 3 ]
|
attribute_name = attribute_name[ 0, attribute_name.length - 3 ]
|
||||||
|
|
||||||
# check if attribute method exists
|
# check if attribute method exists
|
||||||
next if !ref_object.respond_to?(attribute_name)
|
next if !respond_to?(attribute_name)
|
||||||
|
|
||||||
# check if method has own class
|
# check if method has own class
|
||||||
relation_class = ref_object.send(attribute_name).class
|
relation_class = send(attribute_name).class
|
||||||
next if !relation_class
|
next if !relation_class
|
||||||
|
|
||||||
# lookup ref object
|
# lookup ref object
|
||||||
|
@ -111,11 +96,34 @@ returns
|
||||||
if relation_model.respond_to?('search_index_data')
|
if relation_model.respond_to?('search_index_data')
|
||||||
value = relation_model.send('search_index_data')
|
value = relation_model.send('search_index_data')
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if relation_model.respond_to?('name')
|
||||||
|
value = relation_model.send('name')
|
||||||
|
end
|
||||||
|
|
||||||
next if !value
|
next if !value
|
||||||
|
|
||||||
# save name of ref object
|
# save name of ref object
|
||||||
attributes_new[ attribute_name ] = value
|
attributes[ attribute_name ] = value
|
||||||
}
|
}
|
||||||
attributes_new.merge(attributes)
|
|
||||||
|
# default ignored attributes
|
||||||
|
config = self.class.search_index_support_config
|
||||||
|
if config
|
||||||
|
ignore_attributes = {}
|
||||||
|
if config[:ignore_attributes]
|
||||||
|
config[:ignore_attributes].each { |key, value|
|
||||||
|
ignore_attributes[key] = value
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
# remove ignored attributes
|
||||||
|
ignore_attributes.each { |key, value|
|
||||||
|
next if value != true
|
||||||
|
attributes.delete(key.to_s)
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
attributes
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
# Copyright (C) 2012-2014 Zammad Foundation, http://zammad-foundation.org/
|
# Copyright (C) 2012-2014 Zammad Foundation, http://zammad-foundation.org/
|
||||||
|
|
||||||
class Observer::Transaction < ActiveRecord::Observer
|
class Observer::Transaction < ActiveRecord::Observer
|
||||||
observe :ticket, 'ticket::_article', :user, :organization
|
observe :ticket, 'ticket::_article', :user, :organization, :tag
|
||||||
|
|
||||||
def self.commit(params = {})
|
def self.commit(params = {})
|
||||||
|
|
||||||
|
@ -206,13 +206,20 @@ class Observer::Transaction < ActiveRecord::Observer
|
||||||
# do not send anything if nothing has changed
|
# do not send anything if nothing has changed
|
||||||
return if real_changes.empty?
|
return if real_changes.empty?
|
||||||
|
|
||||||
|
changed_by_id = nil
|
||||||
|
if record.respond_to?('updated_by_id')
|
||||||
|
changed_by_id = record.updated_by_id
|
||||||
|
else
|
||||||
|
changed_by_id = record.created_by_id
|
||||||
|
end
|
||||||
|
|
||||||
e = {
|
e = {
|
||||||
object: record.class.name,
|
object: record.class.name,
|
||||||
type: 'update',
|
type: 'update',
|
||||||
data: record,
|
data: record,
|
||||||
changes: real_changes,
|
changes: real_changes,
|
||||||
id: record.id,
|
id: record.id,
|
||||||
user_id: record.updated_by_id,
|
user_id: changed_by_id,
|
||||||
}
|
}
|
||||||
EventBuffer.add('transaction', e)
|
EventBuffer.add('transaction', e)
|
||||||
end
|
end
|
||||||
|
|
|
@ -5,6 +5,7 @@ class Organization < ApplicationModel
|
||||||
load 'organization/assets.rb'
|
load 'organization/assets.rb'
|
||||||
include Organization::Assets
|
include Organization::Assets
|
||||||
extend Organization::Search
|
extend Organization::Search
|
||||||
|
load 'organization/search_index.rb'
|
||||||
include Organization::SearchIndex
|
include Organization::SearchIndex
|
||||||
|
|
||||||
has_and_belongs_to_many :users
|
has_and_belongs_to_many :users
|
||||||
|
|
|
@ -8,7 +8,7 @@ class Organization
|
||||||
lookup name of ref. objects
|
lookup name of ref. objects
|
||||||
|
|
||||||
organization = Organization.find(123)
|
organization = Organization.find(123)
|
||||||
attributes = organization.search_index_attribute_lookup(attributes, Organization)
|
attributes = organization.search_index_attribute_lookup
|
||||||
|
|
||||||
returns
|
returns
|
||||||
|
|
||||||
|
@ -16,47 +16,17 @@ returns
|
||||||
|
|
||||||
=end
|
=end
|
||||||
|
|
||||||
def search_index_attribute_lookup(attributes, ref_object)
|
def search_index_attribute_lookup
|
||||||
attributes_new = {}
|
attributes = super
|
||||||
attributes.each { |key, value|
|
|
||||||
next if !value
|
|
||||||
|
|
||||||
# get attribute name
|
# add org members for search index data
|
||||||
attribute_name = key.to_s
|
attributes['members'] = []
|
||||||
next if attribute_name[-3, 3] != '_id'
|
|
||||||
attribute_name = attribute_name[ 0, attribute_name.length - 3 ]
|
|
||||||
|
|
||||||
# check if attribute method exists
|
|
||||||
next if !ref_object.respond_to?(attribute_name)
|
|
||||||
|
|
||||||
# check if method has own class
|
|
||||||
relation_class = ref_object.send(attribute_name).class
|
|
||||||
next if !relation_class
|
|
||||||
|
|
||||||
# lookup ref object
|
|
||||||
relation_model = relation_class.lookup(id: value)
|
|
||||||
next if !relation_model
|
|
||||||
|
|
||||||
# get name of ref object
|
|
||||||
value = nil
|
|
||||||
if relation_model.respond_to?('search_index_data')
|
|
||||||
value = relation_model.send('search_index_data')
|
|
||||||
end
|
|
||||||
next if !value
|
|
||||||
|
|
||||||
# save name of ref object
|
|
||||||
attributes_new[ attribute_name ] = value
|
|
||||||
attributes.delete(key)
|
|
||||||
}
|
|
||||||
|
|
||||||
# add org member for search index data
|
|
||||||
attributes['member'] = []
|
|
||||||
users = User.where(organization_id: id)
|
users = User.where(organization_id: id)
|
||||||
users.each { |user|
|
users.each { |user|
|
||||||
attributes['member'].push user.search_index_data
|
attributes['members'].push user.search_index_data
|
||||||
}
|
}
|
||||||
|
|
||||||
attributes_new.merge(attributes)
|
attributes
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -108,10 +108,10 @@ returns
|
||||||
.where('groups.active = ?', true)
|
.where('groups.active = ?', true)
|
||||||
group_condition = []
|
group_condition = []
|
||||||
groups.each { |group|
|
groups.each { |group|
|
||||||
group_condition.push group.name
|
group_condition.push group.id
|
||||||
}
|
}
|
||||||
access_condition = {
|
access_condition = {
|
||||||
'query_string' => { 'default_field' => 'Ticket.group.name', 'query' => "\"#{group_condition.join('" OR "')}\"" }
|
'query_string' => { 'default_field' => 'Ticket.group_id', 'query' => "\"#{group_condition.join('" OR "')}\"" }
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
access_condition = if !current_user.organization || ( !current_user.organization.shared || current_user.organization.shared == false )
|
access_condition = if !current_user.organization || ( !current_user.organization.shared || current_user.organization.shared == false )
|
||||||
|
|
|
@ -3,48 +3,28 @@ module Ticket::SearchIndex
|
||||||
|
|
||||||
=begin
|
=begin
|
||||||
|
|
||||||
build and send data for search index to backend
|
lookup name of ref. objects
|
||||||
|
|
||||||
ticket = Ticket.find(123)
|
ticket = Ticket.find(123)
|
||||||
result = ticket.search_index_update_backend
|
result = ticket.search_index_attribute_lookup
|
||||||
|
|
||||||
returns
|
returns
|
||||||
|
|
||||||
result = true # false
|
attributes # object with lookup data
|
||||||
|
|
||||||
=end
|
=end
|
||||||
|
|
||||||
def search_index_update_backend
|
def search_index_attribute_lookup
|
||||||
return if !self.class.search_index_support_config
|
attributes = super
|
||||||
|
return if !attributes
|
||||||
# default ignored attributes
|
|
||||||
ignore_attributes = {}
|
|
||||||
if self.class.search_index_support_config[:ignore_attributes]
|
|
||||||
self.class.search_index_support_config[:ignore_attributes].each { |key, value|
|
|
||||||
ignore_attributes[key] = value
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
# for performance reasons, Model.search_index_reload will only collect if of object
|
|
||||||
# get whole data here
|
|
||||||
ticket = self.class.find(id)
|
|
||||||
|
|
||||||
# remove ignored attributes
|
|
||||||
attributes = ticket.attributes
|
|
||||||
ignore_attributes.each { |key, value|
|
|
||||||
next if value != true
|
|
||||||
attributes.delete( key.to_s )
|
|
||||||
}
|
|
||||||
|
|
||||||
|
# collect article data
|
||||||
# add tags
|
# add tags
|
||||||
tags = Tag.tag_list(object: 'Ticket', o_id: id)
|
tags = Tag.tag_list(object: 'Ticket', o_id: id)
|
||||||
if tags && !tags.empty?
|
if tags && !tags.empty?
|
||||||
attributes[:tag] = tags
|
attributes[:tag] = tags
|
||||||
end
|
end
|
||||||
|
|
||||||
# lookup attributes of ref. objects (normally name and note)
|
|
||||||
attributes = search_index_attribute_lookup( attributes, ticket )
|
|
||||||
|
|
||||||
# list ignored file extentions
|
# list ignored file extentions
|
||||||
attachments_ignore = Setting.get('es_attachment_ignore') || [ '.png', '.jpg', '.jpeg', '.mpeg', '.mpg', '.mov', '.bin', '.exe' ]
|
attachments_ignore = Setting.get('es_attachment_ignore') || [ '.png', '.jpg', '.jpeg', '.mpeg', '.mpg', '.mov', '.bin', '.exe' ]
|
||||||
|
|
||||||
|
@ -64,7 +44,7 @@ returns
|
||||||
}
|
}
|
||||||
|
|
||||||
# lookup attributes of ref. objects (normally name and note)
|
# lookup attributes of ref. objects (normally name and note)
|
||||||
article_attributes = search_index_attribute_lookup( article_attributes, article )
|
article_attributes = article.search_index_attribute_lookup
|
||||||
|
|
||||||
# index raw text body
|
# index raw text body
|
||||||
if article_attributes['content_type'] && article_attributes['content_type'] == 'text/html' && article_attributes['body']
|
if article_attributes['content_type'] && article_attributes['content_type'] == 'text/html' && article_attributes['body']
|
||||||
|
@ -98,7 +78,7 @@ returns
|
||||||
attributes['articles'].push article_attributes
|
attributes['articles'].push article_attributes
|
||||||
}
|
}
|
||||||
|
|
||||||
return if !attributes
|
attributes
|
||||||
SearchIndexBackend.add(self.class.to_s, attributes)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -28,6 +28,8 @@ class User < ApplicationModel
|
||||||
load 'user/assets.rb'
|
load 'user/assets.rb'
|
||||||
include User::Assets
|
include User::Assets
|
||||||
extend User::Search
|
extend User::Search
|
||||||
|
load 'user/search_index.rb'
|
||||||
|
include User::SearchIndex
|
||||||
|
|
||||||
before_create :check_name, :check_email, :check_login, :check_password, :check_preferences_default
|
before_create :check_name, :check_email, :check_login, :check_password, :check_preferences_default
|
||||||
before_update :check_password, :check_email, :check_login, :check_preferences_default
|
before_update :check_password, :check_email, :check_login, :check_preferences_default
|
||||||
|
|
50
app/models/user/search_index.rb
Normal file
50
app/models/user/search_index.rb
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
# Copyright (C) 2012-2014 Zammad Foundation, http://zammad-foundation.org/
|
||||||
|
|
||||||
|
class User
|
||||||
|
module SearchIndex
|
||||||
|
|
||||||
|
=begin
|
||||||
|
|
||||||
|
get data to store in search index
|
||||||
|
|
||||||
|
user = User.find(2)
|
||||||
|
result = user.search_index_data
|
||||||
|
|
||||||
|
returns
|
||||||
|
|
||||||
|
result = {
|
||||||
|
attribute1: 'some value',
|
||||||
|
attribute2: ['value 1', 'value 2'],
|
||||||
|
...
|
||||||
|
}
|
||||||
|
|
||||||
|
=end
|
||||||
|
|
||||||
|
def search_index_data
|
||||||
|
attributes = {}
|
||||||
|
self.attributes.each { |key, value|
|
||||||
|
next if key == 'created_at'
|
||||||
|
next if key == 'updated_at'
|
||||||
|
next if key == 'created_by_id'
|
||||||
|
next if key == 'updated_by_id'
|
||||||
|
next if key == 'preferences'
|
||||||
|
next if key == 'password'
|
||||||
|
next if !value
|
||||||
|
next if value.respond_to?('empty?') && value.empty?
|
||||||
|
attributes[key] = value
|
||||||
|
}
|
||||||
|
return if attributes.empty?
|
||||||
|
|
||||||
|
if attributes['organization_id']
|
||||||
|
organization = Organization.lookup(id: attributes['organization_id'])
|
||||||
|
if organization
|
||||||
|
attributes['organization'] = organization.name
|
||||||
|
attributes['organization_ref'] = organization.search_index_data
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
attributes
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
|
@ -52,6 +52,7 @@ class ElasticsearchTest < ActiveSupport::TestCase
|
||||||
roles = Role.where(name: 'Customer')
|
roles = Role.where(name: 'Customer')
|
||||||
organization1 = Organization.create_if_not_exists(
|
organization1 = Organization.create_if_not_exists(
|
||||||
name: 'Customer Organization Update',
|
name: 'Customer Organization Update',
|
||||||
|
note: 'some note',
|
||||||
updated_by_id: 1,
|
updated_by_id: 1,
|
||||||
created_by_id: 1,
|
created_by_id: 1,
|
||||||
)
|
)
|
||||||
|
@ -93,8 +94,87 @@ class ElasticsearchTest < ActiveSupport::TestCase
|
||||||
created_by_id: 1,
|
created_by_id: 1,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# check search attributes
|
||||||
|
test 'a - objects' do
|
||||||
|
|
||||||
|
# user
|
||||||
|
attributes = agent.search_index_data
|
||||||
|
assert_equal('E', attributes['firstname'])
|
||||||
|
assert_equal('S', attributes['lastname'])
|
||||||
|
assert_equal('es-agent@example.com', attributes['email'])
|
||||||
|
assert_not(attributes['password'])
|
||||||
|
assert_not(attributes['organization'])
|
||||||
|
|
||||||
|
attributes = agent.search_index_attribute_lookup
|
||||||
|
assert_equal('E', attributes['firstname'])
|
||||||
|
assert_equal('S', attributes['lastname'])
|
||||||
|
assert_equal('es-agent@example.com', attributes['email'])
|
||||||
|
assert_not(attributes['password'])
|
||||||
|
assert_not(attributes['organization'])
|
||||||
|
|
||||||
|
# organization
|
||||||
|
attributes = organization1.search_index_data
|
||||||
|
assert_equal('Customer Organization Update', attributes['name'])
|
||||||
|
assert_equal('some note', attributes['note'])
|
||||||
|
assert_not(attributes['members'])
|
||||||
|
|
||||||
|
attributes = organization1.search_index_attribute_lookup
|
||||||
|
assert_equal('Customer Organization Update', attributes['name'])
|
||||||
|
assert_equal('some note', attributes['note'])
|
||||||
|
assert(attributes['members'])
|
||||||
|
|
||||||
|
# ticket/article
|
||||||
|
ticket1 = Ticket.create(
|
||||||
|
title: 'some title äöüß',
|
||||||
|
group: Group.lookup(name: 'Users'),
|
||||||
|
customer_id: customer1.id,
|
||||||
|
state: Ticket::State.lookup(name: 'new'),
|
||||||
|
priority: Ticket::Priority.lookup(name: '2 normal'),
|
||||||
|
updated_by_id: 1,
|
||||||
|
created_by_id: 1,
|
||||||
|
)
|
||||||
|
article1 = Ticket::Article.create(
|
||||||
|
ticket_id: ticket1.id,
|
||||||
|
from: 'some_sender@example.com',
|
||||||
|
to: 'some_recipient@example.com',
|
||||||
|
subject: 'some subject',
|
||||||
|
message_id: 'some@id',
|
||||||
|
body: 'some message',
|
||||||
|
internal: false,
|
||||||
|
sender: Ticket::Article::Sender.where(name: 'Customer').first,
|
||||||
|
type: Ticket::Article::Type.where(name: 'email').first,
|
||||||
|
updated_by_id: 1,
|
||||||
|
created_by_id: 1,
|
||||||
|
)
|
||||||
|
|
||||||
|
attributes = ticket1.search_index_attribute_lookup
|
||||||
|
|
||||||
|
assert_equal('Users', attributes['group'])
|
||||||
|
assert_equal('new', attributes['state'])
|
||||||
|
assert_equal('2 normal', attributes['priority'])
|
||||||
|
|
||||||
|
assert_equal('ES', attributes['customer']['firstname'])
|
||||||
|
assert_equal('Customer1', attributes['customer']['lastname'])
|
||||||
|
assert_equal('es-customer1@example.com', attributes['customer']['email'])
|
||||||
|
assert_not(attributes['customer']['password'])
|
||||||
|
assert_equal('Customer Organization Update', attributes['customer']['organization'])
|
||||||
|
|
||||||
|
assert_equal('-', attributes['owner']['login'])
|
||||||
|
assert_equal('-', attributes['owner']['firstname'])
|
||||||
|
assert_not(attributes['owner']['password'])
|
||||||
|
assert_not(attributes['owner']['organization'])
|
||||||
|
|
||||||
|
ticket1.destroy
|
||||||
|
|
||||||
|
# execute background jobs
|
||||||
|
Scheduler.worker(true)
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
# check tickets and search it
|
# check tickets and search it
|
||||||
test 'a - tickets' do
|
test 'b - tickets' do
|
||||||
|
|
||||||
|
system('rake searchindex:rebuild')
|
||||||
|
|
||||||
ticket1 = Ticket.create(
|
ticket1 = Ticket.create(
|
||||||
title: "some title\n äöüß",
|
title: "some title\n äöüß",
|
||||||
|
@ -398,7 +478,7 @@ class ElasticsearchTest < ActiveSupport::TestCase
|
||||||
end
|
end
|
||||||
|
|
||||||
# check users and search it
|
# check users and search it
|
||||||
test 'b - users' do
|
test 'c - users' do
|
||||||
|
|
||||||
# search as agent
|
# search as agent
|
||||||
result = User.search(
|
result = User.search(
|
||||||
|
|
Loading…
Reference in a new issue