Refactoring: Move SearchIndex logic out of rake task file into SearchIndex class.

This commit is contained in:
Rolf Schmidt 2021-02-01 11:36:53 +01:00 committed by Thorsten Eckel
parent f3fb482992
commit c4a237a203
2 changed files with 256 additions and 227 deletions

View file

@ -926,4 +926,249 @@ helper method for making HTTP calls and raising error if response was not succes
) )
end end
=begin
This function will return a index mapping based on the
attributes of the database table of the existing object.
mapping = SearchIndexBackend.get_mapping_properties_object(Ticket)
Returns:
mapping = {
User: {
properties: {
firstname: {
type: 'keyword',
},
}
}
}
=end
def self.get_mapping_properties_object(object)
name = '_doc'
result = {
name => {
properties: {}
}
}
store_columns = %w[preferences data]
# for elasticsearch 6.x and later
string_type = 'text'
string_raw = { 'type': 'keyword', 'ignore_above': 5012 }
boolean_raw = { 'type': 'boolean' }
object.columns_hash.each do |key, value|
if value.type == :string && value.limit && value.limit <= 5000 && store_columns.exclude?(key)
result[name][:properties][key] = {
type: string_type,
fields: {
keyword: string_raw,
}
}
elsif value.type == :integer
result[name][:properties][key] = {
type: 'integer',
}
elsif value.type == :datetime || value.type == :date
result[name][:properties][key] = {
type: 'date',
}
elsif value.type == :boolean
result[name][:properties][key] = {
type: 'boolean',
fields: {
keyword: boolean_raw,
}
}
elsif value.type == :binary
result[name][:properties][key] = {
type: 'binary',
}
elsif value.type == :bigint
result[name][:properties][key] = {
type: 'long',
}
elsif value.type == :decimal
result[name][:properties][key] = {
type: 'float',
}
end
end
case object.name
when 'Ticket'
result[name][:_source] = {
excludes: ['article.attachment']
}
result[name][:properties][:article] = {
type: 'nested',
include_in_parent: true,
}
when 'KnowledgeBase::Answer::Translation'
result[name][:_source] = {
excludes: ['attachment']
}
end
return result if type_in_mapping?
result[name]
end
# get es version
def self.version
@version ||= begin
info = SearchIndexBackend.info
number = nil
if info.present?
number = info['version']['number'].to_s
end
number
end
end
def self.version_int
number = version
return 0 if !number
number_split = version.split('.')
"#{number_split[0]}#{format('%<minor>03d', minor: number_split[1])}#{format('%<patch>03d', patch: number_split[2])}".to_i
end
def self.version_supported?
# only versions greater/equal than 6.5.0 are supported
return if version_int < 6_005_000
true
end
# no type in mapping
def self.type_in_mapping?
return true if version_int < 7_000_000
false
end
# is es configured?
def self.configured?
return false if Setting.get('es_url').blank?
true
end
def self.settings
{
'index.mapping.total_fields.limit': 2000,
}
end
def self.create_index(models = Models.indexable)
models.each do |local_object|
SearchIndexBackend.index(
action: 'create',
name: local_object.name,
data: {
mappings: SearchIndexBackend.get_mapping_properties_object(local_object),
settings: SearchIndexBackend.settings,
}
)
end
end
def self.drop_index(models = Models.indexable)
models.each do |local_object|
SearchIndexBackend.index(
action: 'delete',
name: local_object.name,
)
end
end
def self.create_object_index(object)
models = Models.indexable.select { |c| c.to_s == object }
create_index(models)
end
def self.drop_object_index(object)
models = Models.indexable.select { |c| c.to_s == object }
drop_index(models)
end
def self.pipeline(create: false)
pipeline = Setting.get('es_pipeline')
if create && pipeline.blank?
pipeline = "zammad#{rand(999_999_999_999)}"
Setting.set('es_pipeline', pipeline)
end
pipeline
end
def self.pipeline_settings
{
ignore_failure: true,
ignore_missing: true,
}
end
def self.create_pipeline
SearchIndexBackend.processors(
"_ingest/pipeline/#{pipeline(create: true)}": [
{
action: 'delete',
},
{
action: 'create',
description: 'Extract zammad-attachment information from arrays',
processors: [
{
foreach: {
field: 'article',
processor: {
foreach: {
field: '_ingest._value.attachment',
processor: {
attachment: {
target_field: '_ingest._value',
field: '_ingest._value._content',
}.merge(pipeline_settings),
}
}.merge(pipeline_settings),
}
}.merge(pipeline_settings),
},
{
foreach: {
field: 'attachment',
processor: {
attachment: {
target_field: '_ingest._value',
field: '_ingest._value._content',
}.merge(pipeline_settings),
}
}.merge(pipeline_settings),
}
]
}
]
)
end
def self.drop_pipeline
return if pipeline.blank?
SearchIndexBackend.processors(
"_ingest/pipeline/#{pipeline}": [
{
action: 'delete',
},
]
)
end
end end

View file

@ -6,12 +6,7 @@ namespace :searchindex do
print 'drop indexes...' print 'drop indexes...'
# drop indexes # drop indexes
Models.indexable.each do |local_object| SearchIndexBackend.drop_index
SearchIndexBackend.index(
action: 'delete',
name: local_object.name,
)
end
puts 'done' puts 'done'
@ -21,21 +16,7 @@ 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...'
settings = { SearchIndexBackend.create_index
'index.mapping.total_fields.limit': 2000,
}
# create indexes
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
puts 'done' puts 'done'
@ -43,77 +24,18 @@ 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|
# update processors
pipeline = Setting.get('es_pipeline')
if pipeline.blank?
pipeline = "zammad#{rand(999_999_999_999)}"
Setting.set('es_pipeline', pipeline)
end
pipeline_field_attributes = {
ignore_failure: true,
ignore_missing: true,
}
print 'create pipeline (pipeline)... ' print 'create pipeline (pipeline)... '
SearchIndexBackend.processors(
"_ingest/pipeline/#{pipeline}": [ SearchIndexBackend.create_pipeline
{
action: 'delete',
},
{
action: 'create',
description: 'Extract zammad-attachment information from arrays',
processors: [
{
foreach: {
field: 'article',
processor: {
foreach: {
field: '_ingest._value.attachment',
processor: {
attachment: {
target_field: '_ingest._value',
field: '_ingest._value._content',
}.merge(pipeline_field_attributes),
}
}.merge(pipeline_field_attributes),
}
}.merge(pipeline_field_attributes),
},
{
foreach: {
field: 'attachment',
processor: {
attachment: {
target_field: '_ingest._value',
field: '_ingest._value._content',
}.merge(pipeline_field_attributes),
}
}.merge(pipeline_field_attributes),
}
]
}
]
)
puts 'done' puts 'done'
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|
# update processors
pipeline = Setting.get('es_pipeline')
next if pipeline.blank?
print 'delete pipeline (pipeline)... ' print 'delete pipeline (pipeline)... '
SearchIndexBackend.processors(
"_ingest/pipeline/#{pipeline}": [ SearchIndexBackend.drop_pipeline
{
action: 'delete',
},
]
)
puts 'done' puts 'done'
end end
@ -127,7 +49,6 @@ namespace :searchindex do
took = Time.zone.now - started_at took = Time.zone.now - started_at
puts " - took #{took.to_i} seconds" puts " - took #{took.to_i} seconds"
end end
end end
task :refresh, [:opts] => %i[environment searchindex:configured searchindex:version_supported] do |_t, _args| task :refresh, [:opts] => %i[environment searchindex:configured searchindex:version_supported] do |_t, _args|
@ -143,151 +64,14 @@ namespace :searchindex do
end end
task :version_supported, [:opts] => :environment do |_t, _args| task :version_supported, [:opts] => :environment do |_t, _args|
next if es_version_supported? next if SearchIndexBackend.version_supported?
abort "Your Elasticsearch version is not supported! Please update your version to a greater equal than 6.5.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: #{SearchIndexBackend.version})."
end end
task :configured, [:opts] => :environment do |_t, _args| task :configured, [:opts] => :environment do |_t, _args|
next if es_configured? next if SearchIndexBackend.configured?
abort "You have not configured Elasticsearch (Setting.get('es_url'))." abort "You have not configured Elasticsearch (Setting.get('es_url'))."
end end
end end
=begin
This function will return a index mapping based on the
attributes of the database table of the existing object.
mapping = get_mapping_properties_object(Ticket)
Returns:
mapping = {
User: {
properties: {
firstname: {
type: 'keyword',
},
}
}
}
=end
def get_mapping_properties_object(object)
name = '_doc'
result = {
name => {
properties: {}
}
}
store_columns = %w[preferences data]
# for elasticsearch 6.x and later
string_type = 'text'
string_raw = { 'type': 'keyword', 'ignore_above': 5012 }
boolean_raw = { 'type': 'boolean' }
object.columns_hash.each do |key, value|
if value.type == :string && value.limit && value.limit <= 5000 && store_columns.exclude?(key)
result[name][:properties][key] = {
type: string_type,
fields: {
keyword: string_raw,
}
}
elsif value.type == :integer
result[name][:properties][key] = {
type: 'integer',
}
elsif value.type == :datetime || value.type == :date
result[name][:properties][key] = {
type: 'date',
}
elsif value.type == :boolean
result[name][:properties][key] = {
type: 'boolean',
fields: {
keyword: boolean_raw,
}
}
elsif value.type == :binary
result[name][:properties][key] = {
type: 'binary',
}
elsif value.type == :bigint
result[name][:properties][key] = {
type: 'long',
}
elsif value.type == :decimal
result[name][:properties][key] = {
type: 'float',
}
end
end
case object.name
when 'Ticket'
result[name][:_source] = {
excludes: ['article.attachment']
}
result[name][:properties][:article] = {
type: 'nested',
include_in_parent: true,
}
when 'KnowledgeBase::Answer::Translation'
result[name][:_source] = {
excludes: ['attachment']
}
end
return result if es_type_in_mapping?
result[name]
end
# get es version
def es_version
@es_version ||= begin
info = SearchIndexBackend.info
number = nil
if info.present?
number = info['version']['number'].to_s
end
number
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?
# only versions greater/equal than 6.5.0 are supported
return if es_version_int < 6_005_000
true
end
# no type in mapping
def es_type_in_mapping?
return true if es_version_int < 7_000_000
false
end
# is es configured?
def es_configured?
return false if Setting.get('es_url').blank?
true
end