Просмотр исходного кода

Init version of ES support. Thanks to Roy!

Martin Edenhofer 11 лет назад
Родитель
Сommit
f04a12696c

+ 86 - 19
app/models/application_model.rb

@@ -4,6 +4,7 @@ class ApplicationModel < ActiveRecord::Base
   include ApplicationModel::Assets
   include ApplicationModel::HistoryLogBase
   include ApplicationModel::ActivityStreamBase
+  include ApplicationModel::SearchIndexBase
 
   self.abstract_class = true
 
@@ -23,9 +24,13 @@ class ApplicationModel < ActiveRecord::Base
   after_update  :history_update
   after_destroy :history_destroy
 
+  after_create  :search_index_update
+  after_update  :search_index_update
+  after_destroy :search_index_destroy
+
   # create instance accessor
   class << self
-    attr_accessor :activity_stream_support_config, :history_support_config
+    attr_accessor :activity_stream_support_config, :history_support_config, :search_index_support_config
   end
 
   attr_accessor :history_changes_last_done
@@ -453,28 +458,65 @@ class OwnModel < ApplicationModel
     )
   end
 
-  private
+=begin
+
+serve methode to configure and enable search index support for this model
+
+class Model < ApplicationModel
+  search_index_support :ignore_attributes => {
+    :create_article_type_id   => true,
+    :create_article_sender_id => true,
+    :article_count            => true,
+  }
+
+end
+
+=end
+
+  def self.search_index_support(data = {})
+    @search_index_support_config = data
+  end
 
 =begin
 
-check string/varchar size and cut them if needed
+update search index, if configured - will be executed automatically
+
+  model = Model.find(123)
+  model.search_index_update
 
 =end
 
-  def check_limits
-    self.attributes.each {|attribute|
-      next if !self[ attribute[0] ]
-      next if self[ attribute[0] ].class != String
-      next if self[ attribute[0] ].empty?
-      column = self.class.columns_hash[ attribute[0] ]
-      limit = column.limit
-      if column && limit
-        current_length = attribute[1].to_s.length
-        if limit < current_length
-          puts "WARNING: cut string because of database length #{self.class.to_s}.#{attribute[0]}(#{limit} but is #{current_length}:#{attribute[1].to_s})"
-          self[attribute[0]] = attribute[1][ 0, limit ]
-        end
-      end
+  def search_index_update
+    return if !self.class.search_index_support_config
+    search_index_update_backend
+  end
+
+=begin
+
+delete search index object, will be executed automatically
+
+  model = Model.find(123)
+  model.search_index_destroy
+
+=end
+
+  def search_index_destroy
+    return if !self.class.search_index_support_config
+    SearchIndexBackend.remove( self.class.to_s, self.id )
+  end
+
+=begin
+
+reload search index with full data
+
+  Model.search_index_reload
+
+=end
+
+  def self.search_index_reload
+    return if !@search_index_support_config
+    self.all.each { |item|
+      item.search_index_update_backend
     }
   end
 
@@ -557,7 +599,7 @@ delete object activity stream, will be executed automatically
 =end
 
   def activity_stream_destroy
-    return if !@activity_stream_support_config
+    return if !self.class.activity_stream_support_config
     ActivityStream.remove( self.class.to_s, self.id )
   end
 
@@ -703,10 +745,35 @@ delete object history, will be executed automatically
 =end
 
   def history_destroy
-    return if !@history_support_config
+    return if !self.class.history_support_config
     History.remove( self.class.to_s, self.id )
   end
 
+  private
+
+=begin
+
+check string/varchar size and cut them if needed
+
+=end
+
+  def check_limits
+    self.attributes.each {|attribute|
+      next if !self[ attribute[0] ]
+      next if self[ attribute[0] ].class != String
+      next if self[ attribute[0] ].empty?
+      column = self.class.columns_hash[ attribute[0] ]
+      limit = column.limit
+      if column && limit
+        current_length = attribute[1].to_s.length
+        if limit < current_length
+          puts "WARNING: cut string because of database length #{self.class.to_s}.#{attribute[0]}(#{limit} but is #{current_length}:#{attribute[1].to_s})"
+          self[attribute[0]] = attribute[1][ 0, limit ]
+        end
+      end
+    }
+  end
+
 =begin
 
 destory object dependencies, will be executed automatically

+ 109 - 0
app/models/application_model/search_index_base.rb

@@ -0,0 +1,109 @@
+# Copyright (C) 2012-2013 Zammad Foundation, http://zammad-foundation.org/
+
+module ApplicationModel::SearchIndexBase
+
+=begin
+
+collect data to index and send to backend
+
+  ticket = Ticket.find(123)
+  result = ticket.search_index_update_backend
+
+returns
+
+  result = true # false
+
+=end
+
+  def search_index_update_backend
+    return if !self.class.search_index_support_config
+
+    # default ignored attributes
+    ignore_attributes = {
+      :created_at               => true,
+      :updated_at               => true,
+      :created_by_id            => true,
+      :updated_by_id            => true,
+      :active                   => true,
+    }
+    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
+
+    # remove ignored attributes
+    attributes = self.attributes
+    ignore_attributes.each {|key, value|
+      next if value != true
+      attributes.delete( key.to_s )
+    }
+
+    # fill up with search data
+    attributes = search_index_attribute_lookup(attributes, self)
+    return if !attributes
+
+    # update backend
+    if self.class.column_names.include? 'active'
+      if self.active
+        SearchIndexBackend.add( self.class.to_s, attributes )
+      else
+        SearchIndexBackend.remove( self.class.to_s, self.id )
+      end
+    else
+      SearchIndexBackend.add( self.class.to_s, attributes )
+    end
+  end
+
+  private
+
+=begin
+
+lookup name of ref. objects
+
+  attributes = search_index_attribute_lookup(attributes, Ticket)
+
+returns
+
+  attributes # object with lookup data
+
+=end
+
+  def search_index_attribute_lookup(attributes, ref_object)
+    attributes_new = {}
+    attributes.each {|key, value|
+      next if !value
+
+      # get attribute name
+      attribute_name = key.to_s
+      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['name']
+        value = relation_model['name']
+      elsif relation_model.respond_to?('fullname')
+        value = relation_model.send('fullname')
+      end
+      next if !value
+
+      # save name of ref object
+      attributes_new[ attribute_name ] = value
+      attributes.delete(key)
+    }
+    attributes_new.merge(attributes)
+  end
+
+end

+ 1 - 0
app/models/organization.rb

@@ -9,5 +9,6 @@ class Organization < ApplicationModel
 
   activity_stream_support  :role => 'Admin'
   history_support
+  search_index_support
 
 end

+ 8 - 0
app/models/ticket.rb

@@ -10,6 +10,8 @@ class Ticket < ApplicationModel
   include Ticket::HistoryLog
   require 'ticket/activity_stream_log'
   include Ticket::ActivityStreamLog
+  require 'ticket/search_index'
+  include Ticket::SearchIndex
   extend Ticket::Search
 
   before_create   :check_generate, :check_defaults
@@ -31,6 +33,12 @@ class Ticket < ApplicationModel
     :article_count            => true,
   }
 
+  search_index_support :ignore_attributes => {
+    :create_article_type_id   => true,
+    :create_article_sender_id => true,
+    :article_count            => true,
+  }
+
   belongs_to    :group
   has_many      :articles,              :class_name => 'Ticket::Article', :after_add => :cache_update, :after_remove => :cache_update
   belongs_to    :organization

+ 67 - 0
app/models/ticket/search_index.rb

@@ -0,0 +1,67 @@
+# Copyright (C) 2012-2013 Zammad Foundation, http://zammad-foundation.org/
+
+module Ticket::SearchIndex
+
+=begin
+
+log activity for this object
+
+  ticket = Ticket.find(123)
+  result = ticket.search_index_update_backend
+
+returns
+
+  result = true # false
+
+=end
+
+  def search_index_update_backend
+    return if !self.class.search_index_support_config
+
+    # default ignored attributes
+    ignore_attributes = {
+      :created_at               => true,
+      :updated_at               => true,
+      :created_by_id            => true,
+      :updated_by_id            => true,
+      :active                   => true,
+    }
+    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
+
+    attributes = self.attributes
+    ignore_attributes.each {|key, value|
+      next if value != true
+      attributes.delete( key.to_s )
+    }
+    attributes = search_index_attribute_lookup( attributes, self )
+
+    # add article data
+    articles = Ticket::Article.where( :ticket_id => self.id )
+    attributes['articles_all'] = []
+    attributes['articles_external'] = []
+    articles.each {|article|
+      article_attributes = article.attributes
+      article_attributes.delete('created_by_id')
+      article_attributes.delete('updated_by_id')
+      article_attributes.delete('updated_at')
+      article_attributes.delete('references')
+      article_attributes.delete('message_id_md5')
+      article_attributes.delete('message_id')
+      article_attributes.delete('in_reply_to')
+      article_attributes.delete('ticket_id')
+      article_attributes = search_index_attribute_lookup( article_attributes, article )
+      attributes['articles_all'].push article_attributes
+      if !article.internal
+        attributes['articles_external'].push article_attributes
+      end
+    }
+
+    return if !attributes
+    SearchIndexBackend.add(self.class.to_s, attributes)
+  end
+
+end

+ 11 - 0
app/models/user.rb

@@ -36,6 +36,17 @@ class User < ApplicationModel
       :image_source => true,
     }
   )
+  search_index_support(
+    :ignore_attributes => {
+      :password     => true,
+      :image        => true,
+      :image_source => true,
+      :source       => true,
+      :login_failed => true,
+      :preferences  => true,
+      :locale       => true,
+    }
+  )
 
 =begin
 

+ 82 - 0
lib/search_index_backend.rb

@@ -0,0 +1,82 @@
+# Copyright (C) 2012-2013 Zammad Foundation, http://zammad-foundation.org/
+
+class SearchIndexBackend
+  @@index = "zammad_#{Rails.env}"
+  @@url   = 'http://127.0.0.1:9000'
+  @@user  = 'elasticsearch'
+  @@pw    = 'zammad'
+
+=begin
+
+add new object to search index
+
+  SearchIndexBackend.add( 'Ticket', some_data_object )
+
+=end
+
+  def self.add(type, data)
+
+    url = "#{@@url}/#{@@index}/#{type}/#{data[:id]}"
+
+    puts "# curl -X POST \"#{url}\" -d '#{data.to_json}'"
+
+    conn = Faraday.new( :url => url )
+    if @@user && @@pw
+      conn.basic_auth( @@user, @@pw )
+    end
+
+    response = conn.post do |req|
+      req.url url
+      req.headers['Content-Type'] = 'application/json'
+      req.body = data.to_json
+    end
+#    puts response.body.to_s
+    puts "# #{response.status.to_s}"
+    return true if response.success?
+    data = JSON.parse( response.body )
+    return { :data => data, :response => response }
+  end
+
+=begin
+
+remove whole data from index
+
+  SearchIndexBackend.remove( 'Ticket', 123 )
+
+  SearchIndexBackend.remove( 'Ticket' )
+
+=end
+
+  def self.remove( type, o_id = nil )
+    if o_id
+      url = "#{@@url}/#{@@index}/#{type}/#{o_id}"
+    else
+      url = "#{@@url}/#{@@index}/#{type}"
+    end
+
+    puts "# curl -X DELETE \"#{url}\""
+
+    conn = Faraday.new( :url => url )
+    if @@user && @@pw
+      conn.basic_auth( @@user, @@pw )
+    end
+    response = conn.delete url
+#    puts response.body.to_s
+    puts "# #{response.status.to_s}"
+    return true if response.success?
+    data = JSON.parse( response.body )
+    return { :data => data, :response => response }
+  end
+
+=begin
+
+return all activity entries of an user
+
+  result = SearchIndexBackend.search( user )
+
+=end
+
+  def self.search(user,limit)
+  end
+
+end