Fixes #3243 - Relational report filters do not work (ticket.organization.*, ticket.article.*, ticket.customer.*).

This commit is contained in:
Rolf Schmidt 2021-01-27 10:58:35 +01:00 committed by Thorsten Eckel
parent 90a67e9d94
commit d0b5cbfe4b
25 changed files with 227 additions and 380 deletions

View file

@ -13,20 +13,26 @@
- bundle exec rspec --tag searchindex --tag ~type:system - bundle exec rspec --tag searchindex --tag ~type:system
- bundle exec rails test test/integration/report_test.rb - bundle exec rails test test/integration/report_test.rb
es:5.6:
<<: *template_integration_es
variables:
ELASTICSEARCH_TAG: '5.6'
RAILS_ENV: "test"
es:6: es:6:
<<: *template_integration_es <<: *template_integration_es
variables: variables:
ELASTICSEARCH_TAG: '6' ELASTICSEARCH_TAG: '6'
RAILS_ENV: "test" RAILS_ENV: "test"
es:6.5:
<<: *template_integration_es
variables:
ELASTICSEARCH_TAG: '6.5'
RAILS_ENV: "test"
es:7: es:7:
<<: *template_integration_es <<: *template_integration_es
variables: variables:
ELASTICSEARCH_TAG: '7' ELASTICSEARCH_TAG: '7'
RAILS_ENV: "test" RAILS_ENV: "test"
es:7.0:
<<: *template_integration_es
variables:
ELASTICSEARCH_TAG: '7.0'
RAILS_ENV: "test"

View file

@ -733,6 +733,7 @@ Metrics/PerceivedComplexity:
- 'app/models/application_model/checks_attribute_values_and_length.rb' - 'app/models/application_model/checks_attribute_values_and_length.rb'
- 'app/models/application_model/checks_user_columns_fillup.rb' - 'app/models/application_model/checks_user_columns_fillup.rb'
- 'app/models/application_model/has_attachments.rb' - 'app/models/application_model/has_attachments.rb'
- 'app/models/application_model/can_lookup_search_index_attributes.rb'
- 'app/models/authorization.rb' - 'app/models/authorization.rb'
- 'app/models/avatar.rb' - 'app/models/avatar.rb'
- 'app/models/calendar.rb' - 'app/models/calendar.rb'

View file

@ -4,10 +4,13 @@ module ApplicationModel::CanLookupSearchIndexAttributes
=begin =begin
lookup name of ref. objects This function return the attributes for the elastic search with relation hash values.
It can be run with parameter include_references: false to skip the relational hashes to prevent endless loops.
ticket = Ticket.find(3) ticket = Ticket.find(3)
attributes = ticket.search_index_attribute_lookup attributes = ticket.search_index_attribute_lookup
attributes = ticket.search_index_attribute_lookup(include_references: false)
returns returns
@ -15,10 +18,11 @@ returns
=end =end
def search_index_attribute_lookup def search_index_attribute_lookup(include_references: true)
attributes = self.attributes attributes = self.attributes
self.attributes.each do |key, value| self.attributes.each do |key, value|
break if !include_references
attribute_name = key.to_s attribute_name = key.to_s
# ignore standard attribute if needed # ignore standard attribute if needed
@ -74,34 +78,7 @@ returns
relation_model = relation_class.lookup(id: attributes[attribute_name]) relation_model = relation_class.lookup(id: attributes[attribute_name])
return if !relation_model return if !relation_model
relation_model.search_index_value relation_model.search_index_attribute_lookup(include_references: false)
end
=begin
This function returns the relational search value.
organization = Organization.find(1)
value = organization.search_index_value
returns
value = {"name"=>"Zammad Foundation"}
=end
def search_index_value
# get name of ref object
value = nil
if respond_to?('name')
value = send('name')
elsif respond_to?('search_index_data')
value = send('search_index_data')
return if value == true
end
value
end end
=begin =begin

View file

@ -15,7 +15,7 @@ returns
=end =end
def search_index_attribute_lookup def search_index_attribute_lookup(include_references: true)
attributes = super attributes = super
return if !attributes return if !attributes

View file

@ -92,7 +92,7 @@ returns
# start background job to transfer data to search index # start background job to transfer data to search index
return true if !SearchIndexBackend.enabled? return true if !SearchIndexBackend.enabled?
new_search_index_value = search_index_value new_search_index_value = search_index_attribute_lookup(include_references: false)
return if new_search_index_value.blank? return if new_search_index_value.blank?
Models.indexable.each do |local_object| Models.indexable.each do |local_object|
@ -174,36 +174,6 @@ returns
true true
end end
=begin
get data to store in search index
ticket = Ticket.find(123)
result = ticket.search_index_data
returns
result = {
attribute1: 'some value',
attribute2: ['value 1', 'value 2'],
...
}
=end
def search_index_data
attributes = {}
%w[name note].each do |key|
next if !self[key]
next if self[key].respond_to?('blank?') && self[key].blank?
attributes[key] = self[key]
end
return true if attributes.blank?
attributes
end
def ignore_search_indexing?(_action) def ignore_search_indexing?(_action)
false false
end end

View file

@ -51,7 +51,7 @@ class KnowledgeBase::Answer::Translation < ApplicationModel
[answer_id, title.parameterize].join('-') [answer_id, title.parameterize].join('-')
end end
def search_index_attribute_lookup def search_index_attribute_lookup(include_references: true)
attrs = super attrs = super
attrs['title'] = ActionController::Base.helpers.strip_tags attrs['title'] attrs['title'] = ActionController::Base.helpers.strip_tags attrs['title']

View file

@ -39,7 +39,7 @@ class KnowledgeBase::Answer::Translation::Content < ApplicationModel
attributes attributes
end end
def search_index_attribute_lookup def search_index_attribute_lookup(include_references: true)
attrs = super attrs = super
attrs['body'] = ActionController::Base.helpers.strip_tags attrs['body'] attrs['body'] = ActionController::Base.helpers.strip_tags attrs['body']
attrs attrs

View file

@ -29,7 +29,7 @@ class KnowledgeBase::Category::Translation < ApplicationModel
category.assets(data) category.assets(data)
end end
def search_index_attribute_lookup def search_index_attribute_lookup(include_references: true)
attrs = super attrs = super
attrs['title'] = ActionController::Base.helpers.strip_tags attrs['title'] attrs['title'] = ActionController::Base.helpers.strip_tags attrs['title']

View file

@ -19,7 +19,7 @@ class KnowledgeBase::Translation < ApplicationModel
knowledge_base.assets(data) knowledge_base.assets(data)
end end
def search_index_attribute_lookup def search_index_attribute_lookup(include_references: true)
attrs = super attrs = super
attrs['title'] = ActionController::Base.helpers.strip_tags attrs['title'] attrs['title'] = ActionController::Base.helpers.strip_tags attrs['title']

View file

@ -17,6 +17,8 @@ class Organization < ApplicationModel
has_many :members, class_name: 'User' has_many :members, class_name: 'User'
has_many :tickets, class_name: 'Ticket' has_many :tickets, class_name: 'Ticket'
belongs_to :created_by, class_name: 'User'
belongs_to :updated_by, class_name: 'User'
before_create :domain_cleanup before_create :domain_cleanup
before_update :domain_cleanup before_update :domain_cleanup
@ -25,7 +27,7 @@ class Organization < ApplicationModel
validates :name, presence: true validates :name, presence: true
validates :domain, presence: { message: 'required when Domain Based Assignment is enabled' }, if: :domain_assignment validates :domain, presence: { message: 'required when Domain Based Assignment is enabled' }, if: :domain_assignment
association_attributes_ignored :tickets association_attributes_ignored :tickets, :created_by, :updated_by
activity_stream_permission 'admin.role' activity_stream_permission 'admin.role'

View file

@ -4,27 +4,17 @@ class Organization
module SearchIndex module SearchIndex
extend ActiveSupport::Concern extend ActiveSupport::Concern
=begin def search_index_attribute_lookup(include_references: true)
lookup name of ref. objects
organization = Organization.find(123)
attributes = organization.search_index_attribute_lookup
returns
attributes # object with lookup data
=end
def search_index_attribute_lookup
attributes = super attributes = super
# add org members for search index data if include_references
attributes['members'] = []
users = User.where(organization_id: id) # add org members for search index data
users.each do |user| attributes['members'] = []
attributes['members'].push user.search_index_data users = User.where(organization_id: id)
users.each do |user|
attributes['members'].push user.search_index_attribute_lookup(include_references: false)
end
end end
attributes attributes

View file

@ -2,7 +2,6 @@
class StatsStore < ApplicationModel class StatsStore < ApplicationModel
include HasSearchIndexBackend include HasSearchIndexBackend
include StatsStore::SearchIndex
belongs_to :stats_storable, polymorphic: true belongs_to :stats_storable, polymorphic: true

View file

@ -1,20 +0,0 @@
# Copyright (C) 2012-2016 Zammad Foundation, http://zammad-foundation.org/
module StatsStore::SearchIndex
def search_index_attribute_lookup
attributes = super
return if !attributes
begin
record = attributes['stats_store_object'].constantize.lookup(id: o_id)
return if !record
attributes['stats_store_object_ref'] = record.search_index_attribute_lookup
rescue
return
end
attributes
end
end

View file

@ -2,20 +2,7 @@
module Ticket::SearchIndex module Ticket::SearchIndex
extend ActiveSupport::Concern extend ActiveSupport::Concern
=begin def search_index_attribute_lookup(include_references: true)
lookup name of ref. objects
ticket = Ticket.find(123)
result = ticket.search_index_attribute_lookup
returns
attributes # object with lookup data
=end
def search_index_attribute_lookup
attributes = super attributes = super
return if !attributes return if !attributes
@ -40,7 +27,7 @@ returns
articles.each do |article| articles.each do |article|
# lookup attributes of ref. objects (normally name and note) # lookup attributes of ref. objects (normally name and note)
article_attributes = article.search_index_attribute_lookup article_attributes = article.search_index_attribute_lookup(include_references: false)
# remove note needed attributes # remove note needed attributes
ignore = %w[message_id_md5 ticket] ignore = %w[message_id_md5 ticket]

View file

@ -4,68 +4,19 @@ class User
module SearchIndex module SearchIndex
extend ActiveSupport::Concern extend ActiveSupport::Concern
=begin def search_index_attribute_lookup(include_references: true)
lookup name of ref. objects
user = User.find(123)
attributes = user.search_index_attribute_lookup
returns
attributes # object with lookup data
=end
def search_index_attribute_lookup
attributes = super attributes = super
attributes.delete('password')
attributes['permissions'] = [] if include_references
permissions_with_child_ids.each do |permission_id| attributes['permissions'] = []
permission = ::Permission.lookup(id: permission_id) permissions_with_child_ids.each do |permission_id|
next if !permission permission = ::Permission.lookup(id: permission_id)
next if !permission
attributes['permissions'].push permission.name attributes['permissions'].push permission.name
end
attributes['role_ids'] = role_ids
attributes
end
=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 do |key, value|
next if key == 'password'
next if !value
next if value.respond_to?('blank?') && value.blank?
attributes[key] = value
end
return if attributes.blank?
if attributes['organization_id'].present?
organization = Organization.lookup(id: attributes['organization_id'])
if organization
attributes['organization'] = organization.name
attributes['organization_ref'] = organization.search_index_data
end end
attributes['role_ids'] = role_ids
end end
attributes attributes

View file

@ -0,0 +1,9 @@
class ElasticSearchLower65Obsolete < ActiveRecord::Migration[5.2]
def change
# return if it's a new setup
return if !Setting.exists?(name: 'system_init_done')
Setting.find_by(name: 'es_multi_index').destroy
end
end

View file

@ -2980,15 +2980,6 @@ Setting.create_if_not_exists(
preferences: { online_service_disable: true }, preferences: { online_service_disable: true },
frontend: false frontend: false
) )
Setting.create_if_not_exists(
title: 'Elasticsearch Multi Index',
name: 'es_multi_index',
area: 'SearchIndex::Elasticsearch',
description: 'Define if Elasticsearch is using multiple indexes.',
state: false,
preferences: { online_service_disable: true },
frontend: false
)
Setting.create_if_not_exists( Setting.create_if_not_exists(
title: 'Import Mode', title: 'Import Mode',

View file

@ -33,7 +33,7 @@ returns
} }
without_merged_tickets = { without_merged_tickets = {
'state' => { 'state.name' => {
'operator' => 'is not', 'operator' => 'is not',
'value' => 'merged' 'value' => 'merged'
} }
@ -156,7 +156,7 @@ returns
end end
without_merged_tickets = { without_merged_tickets = {
'state' => { 'state.name' => {
'operator' => 'is not', 'operator' => 'is not',
'value' => 'merged' 'value' => 'merged'
} }

View file

@ -479,9 +479,9 @@ example for aggregations within one year
current_user_id = current_user.id current_user_id = current_user.id
end end
query_must = [] query_must = []
query_must_not = [] query_must_not = []
relative_map = { relative_map = {
day: 'd', day: 'd',
year: 'y', year: 'y',
month: 'M', month: 'M',
@ -490,7 +490,13 @@ example for aggregations within one year
} }
if selector.present? if selector.present?
selector.each do |key, data| selector.each do |key, data|
key_tmp = key.sub(/^.+?\./, '') data = data.clone
table, key_tmp = key.split(/\./)
if key_tmp.blank?
key_tmp = table
table = 'ticket'
end
wildcard_or_term = 'term' wildcard_or_term = 'term'
if data['value'].is_a?(Array) if data['value'].is_a?(Array)
wildcard_or_term = 'terms' wildcard_or_term = 'terms'
@ -539,22 +545,27 @@ example for aggregations within one year
# use .keyword and wildcard search in cases where query contains non A-z chars # use .keyword and wildcard search in cases where query contains non A-z chars
if data['operator'] == 'contains' || data['operator'] == 'contains not' if data['operator'] == 'contains' || data['operator'] == 'contains not'
if data['value'].is_a?(Array) if data['value'].is_a?(Array)
data['value'].each_with_index do |value, index| data['value'].each_with_index do |value, index|
next if !value.is_a?(String) || value !~ /[A-z]/ || value !~ /\W/ next if !value.is_a?(String) || value !~ /[A-z]/
data['value'][index] = "*#{value}*" data['value'][index] = "*#{value}*"
key_tmp += '.keyword' key_tmp += '.keyword'
wildcard_or_term = 'wildcards' wildcard_or_term = 'wildcards'
break break
end end
elsif data['value'].is_a?(String) && /[A-z]/.match?(data['value']) && data['value'] =~ /\W/ elsif data['value'].is_a?(String) && /[A-z]/.match?(data['value'])
data['value'] = "*#{data['value']}*" data['value'] = "*#{data['value']}*"
key_tmp += '.keyword' key_tmp += '.keyword'
wildcard_or_term = 'wildcard' wildcard_or_term = 'wildcard'
end end
end end
if table != 'ticket'
key_tmp = "#{table}.#{key_tmp}"
end
# is/is not/contains/contains not # is/is not/contains/contains not
case data['operator'] case data['operator']
when 'is', 'is not', 'contains', 'contains not' when 'is', 'is not', 'contains', 'contains not'
@ -702,29 +713,12 @@ return true if backend is configured
def self.build_index_name(index = nil) def self.build_index_name(index = nil)
local_index = "#{Setting.get('es_index')}_#{Rails.env}" local_index = "#{Setting.get('es_index')}_#{Rails.env}"
return local_index if index.blank? return local_index if index.blank?
return "#{local_index}/#{index}" if lower_equal_es56?
"#{local_index}_#{index.underscore.tr('/', '_')}" "#{local_index}_#{index.underscore.tr('/', '_')}"
end end
=begin =begin
return true if the elastic search version is lower equal 5.6
result = SearchIndexBackend.lower_equal_es56?
returns
result = true
=end
def self.lower_equal_es56?
Setting.get('es_multi_index') == false
end
=begin
generate url for index or document access (only for internal use) generate url for index or document access (only for internal use)
# url to access single document in index (in case with_pipeline or not) # url to access single document in index (in case with_pipeline or not)
@ -770,7 +764,7 @@ generate url for index or document access (only for internal use)
url = "#{url}/#{index}" url = "#{url}/#{index}"
# add document type # add document type
if with_document_type && !lower_equal_es56? if with_document_type
url = "#{url}/_doc" url = "#{url}/_doc"
end end
@ -801,7 +795,7 @@ generate url for index or document access (only for internal use)
message = if response&.error&.match?('Connection refused') message = if response&.error&.match?('Connection refused')
"Elasticsearch is not reachable, probably because it's not running or even installed." "Elasticsearch is not reachable, probably because it's not running or even installed."
elsif url.end_with?('pipeline/zammad-attachment', 'pipeline=zammad-attachment') && response.code == 400 elsif url.end_with?('pipeline/zammad-attachment', '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).' 'The installed attachment plugin could not handle the request payload. Ensure that the correct attachment plugin is installed (ingest-attachment).'
else else
'Check the response and payload for detailed information: ' 'Check the response and payload for detailed information: '
end end

View file

@ -6,18 +6,13 @@ namespace :searchindex do
print 'drop indexes...' print 'drop indexes...'
# drop indexes # drop indexes
if es_multi_index? Models.indexable.each do |local_object|
Models.indexable.each do |local_object|
SearchIndexBackend.index(
action: 'delete',
name: local_object.name,
)
end
else
SearchIndexBackend.index( SearchIndexBackend.index(
action: 'delete', action: 'delete',
name: local_object.name,
) )
end end
puts 'done' puts 'done'
Rake::Task['searchindex:drop_pipeline'].execute Rake::Task['searchindex:drop_pipeline'].execute
@ -26,37 +21,17 @@ namespace :searchindex do
task :create, [:opts] => %i[environment searchindex:configured searchindex:version_supported] do |_t, _args| task :create, [:opts] => %i[environment searchindex:configured searchindex:version_supported] do |_t, _args|
print 'create indexes...' print 'create indexes...'
if es_multi_index?
Setting.set('es_multi_index', true)
else
Setting.set('es_multi_index', false)
end
settings = { settings = {
'index.mapping.total_fields.limit': 2000, 'index.mapping.total_fields.limit': 2000,
} }
# create indexes # create indexes
if es_multi_index? Models.indexable.each do |local_object|
Models.indexable.each do |local_object|
SearchIndexBackend.index(
action: 'create',
name: local_object.name,
data: {
mappings: get_mapping_properties_object(local_object),
settings: settings,
}
)
end
else
mapping = {}
Models.indexable.each do |local_object|
mapping.merge!(get_mapping_properties_object(local_object))
end
SearchIndexBackend.index( SearchIndexBackend.index(
action: 'create', action: 'create',
name: local_object.name,
data: { data: {
mappings: mapping, mappings: get_mapping_properties_object(local_object),
settings: settings, settings: settings,
} }
) )
@ -68,10 +43,6 @@ namespace :searchindex do
end end
task :create_pipeline, [:opts] => %i[environment searchindex:configured searchindex:version_supported] do |_t, _args| task :create_pipeline, [:opts] => %i[environment searchindex:configured searchindex:version_supported] do |_t, _args|
if !es_pipeline?
Setting.set('es_pipeline', '')
next
end
# update processors # update processors
pipeline = Setting.get('es_pipeline') pipeline = Setting.get('es_pipeline')
@ -80,17 +51,11 @@ namespace :searchindex do
Setting.set('es_pipeline', pipeline) Setting.set('es_pipeline', pipeline)
end end
# define pipeline_field_attributes
# ES 5.6 and nower has no ignore_missing support
pipeline_field_attributes = { pipeline_field_attributes = {
ignore_failure: true, ignore_failure: true,
ignore_missing: true,
} }
if es_multi_index?
pipeline_field_attributes = {
ignore_failure: true,
ignore_missing: true,
}
end
print 'create pipeline (pipeline)... ' print 'create pipeline (pipeline)... '
SearchIndexBackend.processors( SearchIndexBackend.processors(
"_ingest/pipeline/#{pipeline}": [ "_ingest/pipeline/#{pipeline}": [
@ -136,7 +101,6 @@ namespace :searchindex do
end end
task :drop_pipeline, [:opts] => %i[environment searchindex:configured searchindex:version_supported] do |_t, _args| task :drop_pipeline, [:opts] => %i[environment searchindex:configured searchindex:version_supported] do |_t, _args|
next if !es_pipeline?
# update processors # update processors
pipeline = Setting.get('es_pipeline') pipeline = Setting.get('es_pipeline')
@ -181,7 +145,7 @@ namespace :searchindex do
task :version_supported, [:opts] => :environment do |_t, _args| task :version_supported, [:opts] => :environment do |_t, _args|
next if es_version_supported? next if es_version_supported?
abort "Your Elasticsearch version is not supported! Please update your version to a greater equal than 5.6.0 (Your current version: #{es_version})." abort "Your Elasticsearch version is not supported! Please update your version to a greater equal than 6.5.0 (Your current version: #{es_version})."
end end
task :configured, [:opts] => :environment do |_t, _args| task :configured, [:opts] => :environment do |_t, _args|
@ -214,10 +178,7 @@ mapping = {
def get_mapping_properties_object(object) def get_mapping_properties_object(object)
name = object.name name = '_doc'
if es_multi_index?
name = '_doc'
end
result = { result = {
name => { name => {
properties: {} properties: {}
@ -231,13 +192,6 @@ def get_mapping_properties_object(object)
string_raw = { 'type': 'keyword', 'ignore_above': 5012 } string_raw = { 'type': 'keyword', 'ignore_above': 5012 }
boolean_raw = { 'type': 'boolean' } boolean_raw = { 'type': 'boolean' }
# for elasticsearch 5.6 and lower
if !es_multi_index?
string_type = 'string'
string_raw = { 'type': 'string', 'index': 'not_analyzed' }
boolean_raw = { 'type': 'boolean', 'index': 'not_analyzed' }
end
object.columns_hash.each do |key, value| object.columns_hash.each do |key, value|
if value.type == :string && value.limit && value.limit <= 5000 && store_columns.exclude?(key) if value.type == :string && value.limit && value.limit <= 5000 && store_columns.exclude?(key)
result[name][:properties][key] = { result[name][:properties][key] = {
@ -276,46 +230,19 @@ def get_mapping_properties_object(object)
end end
end end
# es with mapper-attachments plugin case object.name
if object.name == 'Ticket' when 'Ticket'
# do not server attachments if document is requested
result[name][:_source] = { result[name][:_source] = {
excludes: ['article.attachment'] excludes: ['article.attachment']
} }
result[name][:properties][:article] = {
# for elasticsearch 5.5 and lower type: 'nested',
if !es_pipeline? include_in_parent: true,
result[name][:_source] = { }
excludes: ['article.attachment'] when 'KnowledgeBase::Answer::Translation'
}
result[name][:properties][:article] = {
type: 'nested',
include_in_parent: true,
properties: {
attachment: {
type: 'attachment',
}
}
}
end
end
if object.name == 'KnowledgeBase::Answer::Translation'
# do not server attachments if document is requested
result[name][:_source] = { result[name][:_source] = {
excludes: ['attachment'] excludes: ['attachment']
} }
# for elasticsearch 5.5 and lower
if !es_pipeline?
result[name][:_source] = {
excludes: ['attachment']
}
result[name][:properties][:attachment] = {
type: 'attachment',
}
end
end end
return result if es_type_in_mapping? return result if es_type_in_mapping?
@ -335,40 +262,25 @@ def es_version
end end
end end
def es_version_int
number = es_version
return 0 if !number
number_split = es_version.split('.')
"#{number_split[0]}#{format('%<minor>03d', minor: number_split[1])}#{format('%<patch>03d', patch: number_split[2])}".to_i
end
def es_version_supported? def es_version_supported?
version_split = es_version.split('.')
version = "#{version_split[0]}#{format('%<minor>03d', minor: version_split[1])}#{format('%<patch>03d', patch: version_split[2])}".to_i
# only versions greater/equal than 5.6.0 are supported # only versions greater/equal than 6.5.0 are supported
return if version < 5_006_000 return if es_version_int < 6_005_000
true
end
# no es_pipeline for elasticsearch 5.5 and lower
def es_pipeline?
number = es_version
return false if number.blank?
return false if number.match?(/^[2-4]\./)
return false if number.match?(/^5\.[0-5]\./)
true
end
# no multi index for elasticsearch 5.6 and lower
def es_multi_index?
number = es_version
return false if number.blank?
return false if number.match?(/^[2-5]\./)
true true
end end
# no type in mapping # no type in mapping
def es_type_in_mapping? def es_type_in_mapping?
number = es_version return true if es_version_int < 7_000_000
return true if number.blank?
return true if number.match?(/^[2-6]\./)
false false
end end

View file

@ -164,17 +164,32 @@ RSpec.describe SearchIndexBackend, searchindex: true do
describe '.selectors' do describe '.selectors' do
let(:organization1) { create :organization } let(:group1) { create :group }
let(:agent1) { create :agent, organization: organization1 } let(:organization1) { create :organization, note: 'hihi' }
let(:customer1) { create :customer, organization: organization1 } let(:agent1) { create :agent, organization: organization1, groups: [group1] }
let(:ticket1) { create :ticket, title: 'some-title1', state_id: 1, created_by: agent1 } let(:customer1) { create :customer, organization: organization1, firstname: 'special-first-name' }
let(:ticket2) { create :ticket, title: 'some_title2', state_id: 4 } let(:ticket1) do
let(:ticket3) { create :ticket, title: 'some::title3', state_id: 1 } ticket = create :ticket, title: 'some-title1', state_id: 1, created_by: agent1
ticket.tag_add('t1', 1)
ticket
end
let(:ticket2) do
ticket = create :ticket, title: 'some_title2', state_id: 4
ticket.tag_add('t2', 1)
ticket
end
let(:ticket3) do
ticket = create :ticket, title: 'some::title3', state_id: 1
ticket.tag_add('t1', 1)
ticket.tag_add('t2', 1)
ticket
end
let(:ticket4) { create :ticket, title: 'phrase some-title4', state_id: 1 } let(:ticket4) { create :ticket, title: 'phrase some-title4', state_id: 1 }
let(:ticket5) { create :ticket, title: 'phrase some_title5', state_id: 1 } let(:ticket5) { create :ticket, title: 'phrase some_title5', state_id: 1 }
let(:ticket6) { create :ticket, title: 'phrase some::title6', state_id: 1 } let(:ticket6) { create :ticket, title: 'phrase some::title6', state_id: 1 }
let(:ticket7) { create :ticket, title: 'some title7', state_id: 1 } let(:ticket7) { create :ticket, title: 'some title7', state_id: 1 }
let(:ticket8) { create :ticket, title: 'sometitle', state_id: 1, owner: agent1, customer: customer1 } let(:ticket8) { create :ticket, title: 'sometitle', group: group1, state_id: 1, owner: agent1, customer: customer1, organization: organization1 }
let(:article8) { create :ticket_article, ticket: ticket8, subject: 'lorem ipsum' }
before do before do
Ticket.destroy_all # needed to remove not created tickets Ticket.destroy_all # needed to remove not created tickets
@ -192,11 +207,96 @@ RSpec.describe SearchIndexBackend, searchindex: true do
travel 1.second travel 1.second
ticket7.search_index_update_backend ticket7.search_index_update_backend
travel 1.second travel 1.second
ticket8.search_index_update_backend article8.ticket.search_index_update_backend
described_class.refresh described_class.refresh
end end
context 'query with contains' do context 'query with contains' do
it 'finds records with tags which contains all' do
result = described_class.selectors('Ticket',
{ 'ticket.tags'=>{ 'operator' => 'contains all', 'value' => 't1, t2' } },
{},
{
field: 'created_at', # sort to verify result
})
expect(result).to eq({ count: 1, ticket_ids: [ticket3.id.to_s] })
end
it 'finds records with tags which contains one' do
result = described_class.selectors('Ticket',
{ 'ticket.tags'=>{ 'operator' => 'contains one', 'value' => 't1, t2' } },
{},
{
field: 'created_at', # sort to verify result
})
expect(result).to eq({ count: 3, ticket_ids: [ticket3.id.to_s, ticket2.id.to_s, ticket1.id.to_s] })
end
it 'finds records with tags which contains all not' do
result = described_class.selectors('Ticket',
{ 'ticket.tags'=>{ 'operator' => 'contains all not', 'value' => 't2' } },
{},
{
field: 'created_at', # sort to verify result
})
expect(result).to eq({ count: 6, ticket_ids: [ticket8.id.to_s, ticket7.id.to_s, ticket6.id.to_s, ticket5.id.to_s, ticket4.id.to_s, ticket1.id.to_s] })
end
it 'finds records with tags which contains one not' do
result = described_class.selectors('Ticket',
{ 'ticket.tags'=>{ 'operator' => 'contains one not', 'value' => 't1' } },
{},
{
field: 'created_at', # sort to verify result
})
expect(result).to eq({ count: 6, ticket_ids: [ticket8.id.to_s, ticket7.id.to_s, ticket6.id.to_s, ticket5.id.to_s, ticket4.id.to_s, ticket2.id.to_s] })
end
it 'finds records with organization note' do
result = described_class.selectors('Ticket',
{
'organization.note' => {
'operator' => 'contains',
'value' => 'hihi',
},
},
{},
{
field: 'created_at', # sort to verify result
})
expect(result).to eq({ count: 1, ticket_ids: [ticket8.id.to_s] })
end
it 'finds records with customer firstname' do
result = described_class.selectors('Ticket',
{
'customer.firstname' => {
'operator' => 'contains',
'value' => 'special',
},
},
{},
{
field: 'created_at', # sort to verify result
})
expect(result).to eq({ count: 1, ticket_ids: [ticket8.id.to_s] })
end
it 'finds records with article subject' do
result = described_class.selectors('Ticket',
{
'article.subject' => {
'operator' => 'contains',
'value' => 'ipsum',
},
},
{},
{
field: 'created_at', # sort to verify result
})
expect(result).to eq({ count: 1, ticket_ids: [ticket8.id.to_s] })
end
it 'finds records with pre_condition not_set' do it 'finds records with pre_condition not_set' do
result = described_class.selectors('Ticket', result = described_class.selectors('Ticket',
{ {

View file

@ -17,7 +17,7 @@ RSpec.describe Chat::Session, type: :model do
end end
it 'verify chat attribute' do it 'verify chat attribute' do
expect(chat_session.search_index_attribute_lookup['chat']).to eq chat.name expect(chat_session.search_index_attribute_lookup['chat']['name']).to eq chat.name
end end
end end

View file

@ -5,16 +5,7 @@ RSpec.shared_examples 'CanLookupSearchIndexAttributes' do
user = create(:agent, organization: organization) user = create(:agent, organization: organization)
value = user.search_index_value_by_attribute('organization_id') value = user.search_index_value_by_attribute('organization_id')
expect(value).to eq('Tomato42') expect(value['name']).to eq('Tomato42')
end
end
describe '.search_index_value' do
it 'returns correct value' do
organization = create(:organization, name: 'Tomato42', note: 'special recipe')
value = organization.search_index_value
expect(value).to eq('Tomato42')
end end
end end

View file

@ -363,7 +363,7 @@ RSpec.describe 'Search', type: :request, searchindex: true do
expect(json_response['assets']['Organization'][organization_nested.id.to_s]).to be_truthy expect(json_response['assets']['Organization'][organization_nested.id.to_s]).to be_truthy
expect(json_response['assets']['User'][customer_nested.id.to_s]).to be_truthy expect(json_response['assets']['User'][customer_nested.id.to_s]).to be_truthy
post '/api/v1/search/User', params: { query: 'organization:Tomato42' }, as: :json post '/api/v1/search/User', params: { query: 'organization.name:Tomato42' }, as: :json
expect(response).to have_http_status(:ok) expect(response).to have_http_status(:ok)
expect(json_response).to be_a_kind_of(Hash) expect(json_response).to be_a_kind_of(Hash)
expect(json_response).to be_truthy expect(json_response).to be_truthy
@ -383,7 +383,7 @@ RSpec.describe 'Search', type: :request, searchindex: true do
expect(json_response['assets']['Organization'][organization_nested.id.to_s]).to be_truthy expect(json_response['assets']['Organization'][organization_nested.id.to_s]).to be_truthy
expect(json_response['assets']['User'][customer_nested.id.to_s]).to be_truthy expect(json_response['assets']['User'][customer_nested.id.to_s]).to be_truthy
post '/api/v1/search/User', params: { query: 'organization:Cucumber43' }, as: :json post '/api/v1/search/User', params: { query: 'organization.name:Cucumber43' }, as: :json
expect(response).to have_http_status(:ok) expect(response).to have_http_status(:ok)
expect(json_response).to be_a_kind_of(Hash) expect(json_response).to be_a_kind_of(Hash)
expect(json_response).to be_truthy expect(json_response).to be_truthy
@ -400,7 +400,7 @@ RSpec.describe 'Search', type: :request, searchindex: true do
expect(json_response['assets']['Organization'][organization_nested.id.to_s]).to be_truthy expect(json_response['assets']['Organization'][organization_nested.id.to_s]).to be_truthy
expect(json_response['assets']['Ticket'][ticket_nested.id.to_s]).to be_truthy expect(json_response['assets']['Ticket'][ticket_nested.id.to_s]).to be_truthy
post '/api/v1/search/Ticket', params: { query: 'organization:Tomato42' }, as: :json post '/api/v1/search/Ticket', params: { query: 'organization.name:Tomato42' }, as: :json
expect(response).to have_http_status(:ok) expect(response).to have_http_status(:ok)
expect(json_response).to be_a_kind_of(Hash) expect(json_response).to be_a_kind_of(Hash)
expect(json_response).to be_truthy expect(json_response).to be_truthy
@ -418,7 +418,7 @@ RSpec.describe 'Search', type: :request, searchindex: true do
expect(json_response['assets']['Organization'][organization_nested.id.to_s]).to be_truthy expect(json_response['assets']['Organization'][organization_nested.id.to_s]).to be_truthy
expect(json_response['assets']['Ticket'][ticket_nested.id.to_s]).to be_truthy expect(json_response['assets']['Ticket'][ticket_nested.id.to_s]).to be_truthy
post '/api/v1/search/Ticket', params: { query: 'organization:Cucumber43' }, as: :json post '/api/v1/search/Ticket', params: { query: 'organization.name:Cucumber43' }, as: :json
expect(response).to have_http_status(:ok) expect(response).to have_http_status(:ok)
expect(json_response).to be_a_kind_of(Hash) expect(json_response).to be_a_kind_of(Hash)
expect(json_response).to be_truthy expect(json_response).to be_truthy

View file

@ -82,14 +82,6 @@ class ElasticsearchTest < ActiveSupport::TestCase
test 'a - objects' do test 'a - objects' do
# user # 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(attributes['preferences'])
assert_not(attributes['password'])
assert_not(attributes['organization'])
attributes = @agent.search_index_attribute_lookup attributes = @agent.search_index_attribute_lookup
assert_equal('E', attributes['firstname']) assert_equal('E', attributes['firstname'])
assert_equal('S', attributes['lastname']) assert_equal('S', attributes['lastname'])
@ -104,14 +96,9 @@ class ElasticsearchTest < ActiveSupport::TestCase
assert_equal('es-customer1@example.com', attributes['email']) assert_equal('es-customer1@example.com', attributes['email'])
assert(attributes['preferences']) assert(attributes['preferences'])
assert_not(attributes['password']) assert_not(attributes['password'])
assert_equal('Customer Organization Update', attributes['organization']) assert_equal('Customer Organization Update', attributes['organization']['name'])
# 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 attributes = @organization1.search_index_attribute_lookup
assert_equal('Customer Organization Update', attributes['name']) assert_equal('Customer Organization Update', attributes['name'])
assert_equal('some note', attributes['note']) assert_equal('some note', attributes['note'])
@ -150,15 +137,15 @@ class ElasticsearchTest < ActiveSupport::TestCase
) )
attributes = ticket1.search_index_attribute_lookup attributes = ticket1.search_index_attribute_lookup
assert_equal('Users', attributes['group']) assert_equal('Users', attributes['group']['name'])
assert_equal('new', attributes['state']) assert_equal('new', attributes['state']['name'])
assert_equal('2 normal', attributes['priority']) assert_equal('2 normal', attributes['priority']['name'])
assert_equal('ES', attributes['customer']['firstname']) assert_equal('ES', attributes['customer']['firstname'])
assert_equal('Customer1', attributes['customer']['lastname']) assert_equal('Customer1', attributes['customer']['lastname'])
assert_equal('es-customer1@example.com', attributes['customer']['email']) assert_equal('es-customer1@example.com', attributes['customer']['email'])
assert_not(attributes['customer']['password']) assert_not(attributes['customer']['password'])
assert_equal('Customer Organization Update', attributes['customer']['organization']) assert_nil(attributes['customer']['organization'])
assert_equal('-', attributes['owner']['login']) assert_equal('-', attributes['owner']['login'])
assert_equal('-', attributes['owner']['firstname']) assert_equal('-', attributes['owner']['firstname'])
@ -466,7 +453,7 @@ class ElasticsearchTest < ActiveSupport::TestCase
result = Ticket.search( result = Ticket.search(
current_user: @agent, current_user: @agent,
query: 'state:open', query: 'state.name:open',
limit: 15, limit: 15,
) )
assert(result[0], 'record 1') assert(result[0], 'record 1')