From 8ac7356184bc3cec2efaaac3484ca49029b43652 Mon Sep 17 00:00:00 2001 From: Martin Edenhofer Date: Fri, 15 Dec 2017 14:58:41 +0100 Subject: [PATCH 1/4] Small refactoring of search index backend. --- lib/report/ticket_generic_time.rb | 2 +- lib/search_index_backend.rb | 38 +++++------ lib/tasks/search_index_es.rake | 109 +++++++++++++++++++++--------- 3 files changed, 95 insertions(+), 54 deletions(-) diff --git a/lib/report/ticket_generic_time.rb b/lib/report/ticket_generic_time.rb index 5372f1166..3869f4e79 100644 --- a/lib/report/ticket_generic_time.rb +++ b/lib/report/ticket_generic_time.rb @@ -30,7 +30,7 @@ returns } selector = params[:selector].clone - if params[:params] && params[:params][:selector] + if params[:params].present? && params[:params][:selector].present? selector = selector.merge(params[:params][:selector]) end diff --git a/lib/search_index_backend.rb b/lib/search_index_backend.rb index d909b8ed6..b5887c512 100644 --- a/lib/search_index_backend.rb +++ b/lib/search_index_backend.rb @@ -74,6 +74,7 @@ update processors ) Rails.logger.info "# #{response.code}" next if response.success? + next if response.code.to_s == '404' raise "Unable to process DELETE at #{url}\n#{response.inspect}" end Rails.logger.info "# curl -X PUT \"#{url}\" \\" @@ -133,7 +134,7 @@ create/update/delete index def self.index(data) url = build_url(data[:name]) - return if !url + return if url.blank? if data[:action] && data[:action] == 'delete' return SearchIndexBackend.remove(data[:name]) @@ -169,7 +170,7 @@ add new object to search index def self.add(type, data) url = build_url(type, data['id']) - return if !url + return if url.blank? Rails.logger.info "# curl -X POST \"#{url}\" \\" Rails.logger.debug "-d '#{data.to_json}'" @@ -202,7 +203,7 @@ remove whole data from index def self.remove(type, o_id = nil) url = build_url(type, o_id) - return if !url + return if url.blank? Rails.logger.info "# curl -X DELETE \"#{url}\"" @@ -217,7 +218,8 @@ remove whole data from index ) Rails.logger.info "# #{response.code}" return true if response.success? - #Rails.logger.info "NOTICE: can't delete index #{url}: " + response.inspect + return true if response.code.to_s == '400' + Rails.logger.info "NOTICE: can't delete index #{url}: " + response.inspect false end @@ -247,7 +249,7 @@ return search result =end def self.search(query, limit = 10, index = nil, query_extention = {}) - return [] if !query + return [] if query.blank? if index.class == Array ids = [] index.each do |local_index| @@ -260,10 +262,10 @@ return search result end def self.search_by_index(query, limit = 10, index = nil, query_extention = {}) - return [] if !query + return [] if query.blank? url = build_url - return if !url + return if url.blank? url += if index if index.class == Array "/#{index.join(',')}/_search" @@ -287,12 +289,8 @@ return search result ] data['query'] = query_extention || {} - if !data['query']['bool'] - data['query']['bool'] = {} - end - if !data['query']['bool']['must'] - data['query']['bool']['must'] = [] - end + data['query']['bool'] ||= {} + data['query']['bool']['must'] ||= [] # add * on simple query like "somephrase23" or "attribute: somephrase23" if query.present? @@ -391,7 +389,7 @@ get count of tickets and tickets which match on selector raise 'no selectors given' if !selectors url = build_url - return if !url + return if url.blank? url += if index if index.class == Array "/#{index.join(',')}/_search" @@ -425,7 +423,7 @@ get count of tickets and tickets which match on selector end Rails.logger.debug response.data.to_json - if !aggs_interval || !aggs_interval[:interval] + if aggs_interval.blank? || aggs_interval[:interval].blank? ticket_ids = [] response.data['hits']['hits'].each do |item| ticket_ids.push item['_id'] @@ -471,8 +469,8 @@ get count of tickets and tickets which match on selector } # add aggs to filter - if aggs_interval - if aggs_interval[:interval] + if aggs_interval.present? + if aggs_interval[:interval].present? data[:size] = 0 data[:aggs] = { time_buckets: { @@ -492,9 +490,7 @@ get count of tickets and tickets which match on selector query_must.push r end - if !data[:query][:bool] - data[:query][:bool] = {} - end + data[:query][:bool] ||= {} if query_must.present? data[:query][:bool][:must] = query_must @@ -504,7 +500,7 @@ get count of tickets and tickets which match on selector end # add sort - if aggs_interval && aggs_interval[:field] && !aggs_interval[:interval] + if aggs_interval.present? && aggs_interval[:field].present? && aggs_interval[:interval].blank? sort = [] sort[0] = {} sort[0][aggs_interval[:field]] = { diff --git a/lib/tasks/search_index_es.rake b/lib/tasks/search_index_es.rake index 7a68ab5c6..5014ac0bf 100644 --- a/lib/tasks/search_index_es.rake +++ b/lib/tasks/search_index_es.rake @@ -5,15 +5,17 @@ namespace :searchindex do task :drop, [:opts] => :environment do |_t, _args| # drop indexes - puts 'drop indexes...' + print 'drop indexes...' SearchIndexBackend.index( action: 'delete', ) + puts 'done' + Rake::Task['searchindex:drop_pipeline'].execute end task :create, [:opts] => :environment do |_t, _args| - puts 'create indexes...' + print 'create indexes...' # es with mapper-attachments plugin info = SearchIndexBackend.info @@ -45,6 +47,7 @@ namespace :searchindex do } } ) + puts 'done' Setting.set('es_pipeline', '') # es with ingest-attachment plugin @@ -61,44 +64,86 @@ namespace :searchindex do } } ) + puts 'done' + end - # update processors - pipeline = 'zammad-attachment' + Rake::Task['searchindex:create_pipeline'].execute + end + + task :create_pipeline, [:opts] => :environment do |_t, _args| + + # es with mapper-attachments plugin + info = SearchIndexBackend.info + number = nil + if info.present? + number = info['version']['number'].to_s + end + next if number.blank? || number =~ /^[2-4]\./ || number =~ /^5\.[0-5]\./ + + # update processors + pipeline = Setting.get('es_pipeline') + if pipeline.blank? + pipeline = "zammad#{rand(999_999_999_999)}" Setting.set('es_pipeline', pipeline) - SearchIndexBackend.processors( - "_ingest/pipeline/#{pipeline}": [ - { - action: 'delete', - }, - { - action: 'create', - description: 'Extract zammad-attachment information from arrays', - processors: [ - { - foreach: { - field: 'article', - ignore_failure: true, - processor: { - foreach: { - field: '_ingest._value.attachment', - ignore_failure: true, - processor: { - attachment: { - target_field: '_ingest._value', - field: '_ingest._value._content', - ignore_failure: true, - } + end + print 'create pipeline (pipeline)... ' + SearchIndexBackend.processors( + "_ingest/pipeline/#{pipeline}": [ + { + action: 'delete', + }, + { + action: 'create', + description: 'Extract zammad-attachment information from arrays', + processors: [ + { + foreach: { + field: 'article', + ignore_failure: true, + processor: { + foreach: { + field: '_ingest._value.attachment', + ignore_failure: true, + processor: { + attachment: { + target_field: '_ingest._value', + field: '_ingest._value._content', + ignore_failure: true, } } } } } - ] - } - ] - ) - end + } + ] + } + ] + ) + puts 'done' + end + task :drop_pipeline, [:opts] => :environment do |_t, _args| + + # es with mapper-attachments plugin + info = SearchIndexBackend.info + number = nil + if info.present? + number = info['version']['number'].to_s + end + next if number.blank? || number =~ /^[2-4]\./ || number =~ /^5\.[0-5]\./ + + # update processors + pipeline = Setting.get('es_pipeline') + next if pipeline.blank? + print 'delete pipeline (pipeline)... ' + SearchIndexBackend.processors( + "_ingest/pipeline/#{pipeline}": [ + { + action: 'delete', + }, + ] + ) + puts 'done' end task :reload, [:opts] => :environment do |_t, _args| From 6cd203a050cdb10654092850e6f230d6ff79d1e5 Mon Sep 17 00:00:00 2001 From: Felix Niklas Date: Sat, 16 Dec 2017 13:57:12 +0100 Subject: [PATCH 2/4] Chat: fix open/close icon and status alignment --- public/assets/chat/chat.css | 7 +++++-- public/assets/chat/chat.scss | 9 +++++++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/public/assets/chat/chat.css b/public/assets/chat/chat.css index 255fa5b7a..81d219049 100644 --- a/public/assets/chat/chat.css +++ b/public/assets/chat/chat.css @@ -71,8 +71,11 @@ height: 100%; width: 3.4em; text-align: center; - line-height: 3.5em; + line-height: 3.4em; cursor: pointer; } + .zammad-chat-header-icon:before { + content: ""; + display: inline-block; } .zammad-chat-header-icon-open, .zammad-chat-header-icon-close { @@ -107,7 +110,7 @@ font-weight: bold; } .zammad-chat-agent-status { - margin: 0 1em; + margin: 0.25em 1em; display: inline-block; line-height: 2em; padding: 0 .7em; diff --git a/public/assets/chat/chat.scss b/public/assets/chat/chat.scss index 64f0c4cca..2c85d78fc 100644 --- a/public/assets/chat/chat.scss +++ b/public/assets/chat/chat.scss @@ -80,8 +80,13 @@ height: 100%; width: 3.4em; text-align: center; - line-height: 3.5em; + line-height: 3.4em; cursor: pointer; + + &:before { // force the icon to align to center + content: ""; + display: inline-block; + } } .zammad-chat-header-icon-open, @@ -125,7 +130,7 @@ } .zammad-chat-agent-status { - margin: 0 1em; + margin: 0.25em 1em; display: inline-block; line-height: 2em; padding: 0 .7em; From 9c54b3382d2287efc4552bcf13e83fe8fc45d325 Mon Sep 17 00:00:00 2001 From: Martin Edenhofer Date: Mon, 18 Dec 2017 04:36:56 +0100 Subject: [PATCH 3/4] Added support to search for chat sessions. Added set name and tags for chats. --- .../javascripts/app/controllers/chat.coffee | 174 ++++++++++++------ .../javascripts/app/controllers/search.coffee | 1 + .../app/models/chat_sessions.coffee | 32 ++++ .../views/customer_chat/chat_window.jst.eco | 23 ++- .../customer_chat/chat_window_info.jst.eco | 17 -- app/controllers/search_controller.rb | 17 +- app/models/chat/session.rb | 11 ++ app/models/chat/session/assets.rb | 56 ++++++ app/models/chat/session/search.rb | 80 ++++++++ app/models/chat/session/search_index.rb | 36 ++++ lib/sessions/event/base.rb | 18 +- lib/sessions/event/chat_session_update.rb | 35 ++++ test/unit/model_test.rb | 4 +- 13 files changed, 417 insertions(+), 87 deletions(-) create mode 100644 app/assets/javascripts/app/models/chat_sessions.coffee delete mode 100644 app/assets/javascripts/app/views/customer_chat/chat_window_info.jst.eco create mode 100644 app/models/chat/session/assets.rb create mode 100644 app/models/chat/session/search.rb create mode 100644 app/models/chat/session/search_index.rb create mode 100644 lib/sessions/event/chat_session_update.rb diff --git a/app/assets/javascripts/app/controllers/chat.coffee b/app/assets/javascripts/app/controllers/chat.coffee index 2966e3b4c..8eb974089 100644 --- a/app/assets/javascripts/app/controllers/chat.coffee +++ b/app/assets/javascripts/app/controllers/chat.coffee @@ -35,7 +35,7 @@ class App.CustomerChat extends App.Controller active_agent_ids: [] @render() - @on 'layout-has-changed', @propagateLayoutChange + @on('layout-has-changed', @propagateLayoutChange) # update navbar on new status @bind('chat_status_agent', (data) => @@ -163,6 +163,11 @@ class App.CustomerChat extends App.Controller @title 'Customer Chat', true @navupdate '#customer_chat' + if params.session_id && App.ChatSession.exists(params.session_id) + session = App.ChatSession.find(params.session_id) + @addChat(session) + @navigate '#customer_chat' + active: (state) => return @shown if state is undefined @shown = state @@ -264,10 +269,11 @@ class App.CustomerChat extends App.Controller addChat: (session) -> return if @chatWindows[session.session_id] - chat = new ChatWindow + chat = new ChatWindow( session: session removeCallback: @removeChat messageCallback: @updateNavMenu + ) @workspace.append chat.el chat.render() @@ -289,7 +295,7 @@ class App.CustomerChat extends App.Controller propagateLayoutChange: (event) => # adjust scroll position on layoutChange for session_id, chat of @chatWindows - chat.trigger 'layout-changed' + chat.trigger('layout-changed') acceptChat: => return if @windowCount() >= @maxChatWindows @@ -324,19 +330,6 @@ class App.CustomerChat extends App.Controller currentPosition: => @$('.main').scrollTop() -class CustomerChatRouter extends App.ControllerPermanent - requiredPermission: 'chat.agent' - constructor: (params) -> - super - - App.TaskManager.execute( - key: 'CustomerChat' - controller: 'CustomerChat' - params: {} - show: true - persistent: true - ) - class ChatWindow extends App.Controller className: 'chat-window' @@ -348,6 +341,8 @@ class ChatWindow extends App.Controller 'click .js-close': 'close' 'click .js-disconnect': 'disconnect' 'click .js-scrollHint': 'onScrollHintClick' + 'click .js-info': 'toggleMeta' + 'submit .js-metaForm': 'sendMetaForm' elements: '.js-customerChatInput': 'input' @@ -355,8 +350,11 @@ class ChatWindow extends App.Controller '.js-close': 'closeButton' '.js-disconnect': 'disconnectButton' '.js-body': 'body' + '.js-meta': 'meta' + '.js-name': 'metaName' '.js-scrollHolder': 'scrollHolder' '.js-scrollHint': 'scrollHint' + '.js-metaForm': 'metaForm' sounds: message: new Audio('assets/sounds/chat_message.mp3') @@ -374,9 +372,11 @@ class ChatWindow extends App.Controller @scrollSnapTolerance = 10 # pixels @chat = App.Chat.find(@session.chat_id) - @name = "#{@chat.displayName()} ##{@session.id}" + @name = @chat.displayName() + if @session && !_.isEmpty(@session.name) + @name = @session.name - @on 'layout-change', @onLayoutChange + @on('layout-change', @onLayoutChange) @bind('chat_session_typing', (data) => return if data.session_id isnt @session.session_id @@ -413,12 +413,44 @@ class ChatWindow extends App.Controller onLayoutChange: => @scrollToBottom() - render: -> - @html App.view('customer_chat/chat_window') - name: @name + toggleMeta: => + if @meta.hasClass('hidden') + @showMeta() + else + @hideMeta() - @el.one 'transitionend', @onTransitionend - @scrollHolder.scroll @detectScrolledtoBottom + hideMeta: => + @body.removeClass('hidden') + @meta.addClass('hidden') + @sendMetaForm() + + showMeta: => + @body.addClass('hidden') + @meta.removeClass('hidden') + + sendMetaForm: (e) => + if e + e.preventDefault() + params = @formParam(@metaForm) + + App.WebSocket.send( + event:'chat_session_update' + data: + session_id: @session.session_id + name: params.name + tags: params.tags + ) + + @metaName.text(params.name) + + render: -> + @html App.view('customer_chat/chat_window')( + name: @name + session: @session + ) + + @el.one('transitionend', @onTransitionend) + @scrollHolder.scroll(@detectScrolledtoBottom) # force repaint @el.prop('offsetHeight') @@ -426,18 +458,24 @@ class ChatWindow extends App.Controller # @addMessage 'Hello. My name is Roger, how can I help you?', 'agent' if @session + + # set chat to offline if state is already closed + activeChat = true + if @session.state is 'closed' + activeChat = false + if @session && @session.preferences && @session.preferences.url - @addNoticeMessage(@session.preferences.url) + @addNoticeMessage(@session.preferences.url, undefined, activeChat) if @session.messages for message in @session.messages if message.created_by_id - @addMessage message.content, 'agent' + @addMessage(message.content, 'agent', false, activeChat) else - @addMessage message.content, 'customer' + @addMessage(message.content, 'customer', false, activeChat) # send init reply - if !@session.messages || _.isEmpty(@session.messages) + if activeChat && _.isEmpty(@session.messages) preferences = @Session.get('preferences') if preferences.chat && preferences.chat.phrase phrases = preferences.chat.phrase[@session.chat_id] @@ -447,20 +485,9 @@ class ChatWindow extends App.Controller @input.html(phrase) @sendMessage(1600) - @$('.js-info').popover( - trigger: 'hover' - html: true - animation: false - delay: 0 - placement: 'bottom' - container: 'body' # place in body do prevent it from animating - title: -> - App.i18n.translateContent('Details') - content: => - App.view('customer_chat/chat_window_info')( - session: @session - ) - ) + # set chat to offline if state is already closed + if !activeChat + @goOffline() # show text module UI new App.WidgetTextModule( @@ -470,6 +497,18 @@ class ChatWindow extends App.Controller config: App.Config.all() ) + configureAttributesOutbound = [ + { name: 'name', display: 'Name', tag: 'input', null: true, }, + { name: 'tags', display: 'Tags', tag: 'tag', null: true, }, + ] + new App.ControllerForm( + el: @$('.js-metaForm') + model: + configure_attributes: configureAttributesOutbound + className: '' + params: @session + ) + focus: => @input.focus() @@ -498,7 +537,8 @@ class ChatWindow extends App.Controller @goOffline() close: => - @el.one 'transitionend', { callback: @release }, @onTransitionend + @sendMetaForm() + @el.one('transitionend', { callback: @release }, @onTransitionend) @el.removeClass('is-open') if @removeCallback @removeCallback(@session.session_id) @@ -577,7 +617,8 @@ class ChatWindow extends App.Controller ) @delay(send, delay) - @addMessage content, 'agent' + @hideMeta() + @addMessage(content, 'agent') @input.html('') updateModified: (state) => @@ -614,18 +655,19 @@ class ChatWindow extends App.Controller @messageCallback(@session.session_id) @unreadMessagesCounter = 0 - addMessage: (message, sender, isNew) => - @maybeAddTimestamp() + addMessage: (message, sender, isNew, useMaybeAddTimestamp = true) => + @maybeAddTimestamp() if useMaybeAddTimestamp @lastAddedType = sender - @body.append App.view('customer_chat/chat_message') + @body.append App.view('customer_chat/chat_message')( message: message sender: sender isNew: isNew timestamp: Date.now() + ) - @scrollToBottom showHint: true + @scrollToBottom(showHint: true) showWritingLoader: => if !@isTyping @@ -667,33 +709,37 @@ class ChatWindow extends App.Controller @lastAddedType = 'timestamp' addTimestamp: (label, time) => - @body.append App.view('customer_chat/chat_timestamp') + @body.append App.view('customer_chat/chat_timestamp')( label: label time: time + ) updateLastTimestamp: (label, time) -> @body .find('.js-timestamp') .last() - .replaceWith App.view('customer_chat/chat_timestamp') + .replaceWith App.view('customer_chat/chat_timestamp')( label: label time: time + ) - addStatusMessage: (message, args) -> - @maybeAddTimestamp() + addStatusMessage: (message, args, useMaybeAddTimestamp = true) -> + @maybeAddTimestamp() if useMaybeAddTimestamp - @body.append App.view('customer_chat/chat_status_message') + @body.append App.view('customer_chat/chat_status_message')( message: message args: args + ) @scrollToBottom() - addNoticeMessage: (message, args) -> - @maybeAddTimestamp() + addNoticeMessage: (message, args, useMaybeAddTimestamp = true) -> + @maybeAddTimestamp() if useMaybeAddTimestamp - @body.append App.view('customer_chat/chat_notice_message') + @body.append App.view('customer_chat/chat_notice_message')( message: message args: args + ) @scrollToBottom() @@ -784,6 +830,24 @@ class Setting extends App.ControllerModal msg: App.i18n.translateContent(data.message) ) +class CustomerChatRouter extends App.ControllerPermanent + requiredPermission: 'chat.agent' + constructor: (params) -> + super + + # cleanup params + clean_params = + session_id: params.session_id + + App.TaskManager.execute( + key: 'CustomerChat' + controller: 'CustomerChat' + params: clean_params + show: true + persistent: true + ) + App.Config.set('customer_chat', CustomerChatRouter, 'Routes') +App.Config.set('customer_chat/session/:session_id', CustomerChatRouter, 'Routes') App.Config.set('CustomerChat', { controller: 'CustomerChat', permission: ['chat.agent'] }, 'permanentTask') App.Config.set('CustomerChat', { prio: 1200, parent: '', name: 'Customer Chat', target: '#customer_chat', key: 'CustomerChat', shown: false, permission: ['chat.agent'], class: 'chat' }, 'NavBar') diff --git a/app/assets/javascripts/app/controllers/search.coffee b/app/assets/javascripts/app/controllers/search.coffee index 9a6f45052..67e9c009a 100644 --- a/app/assets/javascripts/app/controllers/search.coffee +++ b/app/assets/javascripts/app/controllers/search.coffee @@ -79,6 +79,7 @@ class App.Search extends App.Controller @tabs = [] for model in App.Config.get('models_searchable') + model = model.replace(/::/, '') tab = name: model model: model diff --git a/app/assets/javascripts/app/models/chat_sessions.coffee b/app/assets/javascripts/app/models/chat_sessions.coffee new file mode 100644 index 000000000..d32fccd18 --- /dev/null +++ b/app/assets/javascripts/app/models/chat_sessions.coffee @@ -0,0 +1,32 @@ +class App.ChatSession extends App.Model + @configure 'ChatSession', 'name', 'note' + @extend Spine.Model.Ajax + @url: @apiPath + '/chat_sessions' + + @configure_attributes = [ + { name: 'name', display: 'Name', tag: 'input', type: 'text', limit: 100, 'null': false } + { name: 'state', display: 'State', readonly: 1 } + { name: 'created_by_id', display: 'Created by', relation: 'User', readonly: 1 } + { name: 'created_at', display: 'Created', tag: 'datetime', readonly: 1 } + { name: 'updated_by_id', display: 'Updated by', relation: 'User', readonly: 1 } + { name: 'updated_at', display: 'Updated', tag: 'datetime', readonly: 1 } + ] + + @configure_overview = [ + 'name', + 'state', + 'created_at', + ] + + uiUrl: -> + "#customer_chat/session/#{@id}" + + searchResultAttributes: -> + displayName = '' + if !_.isEmpty(@name) + displayName = @displayName() + display: "##{@id} #{displayName}" + id: @id + class: 'chat_session chat_session-popover' + url: @uiUrl() + icon: 'chat' diff --git a/app/assets/javascripts/app/views/customer_chat/chat_window.jst.eco b/app/assets/javascripts/app/views/customer_chat/chat_window.jst.eco index 1fef2ed9b..de2f271da 100644 --- a/app/assets/javascripts/app/views/customer_chat/chat_window.jst.eco +++ b/app/assets/javascripts/app/views/customer_chat/chat_window.jst.eco @@ -7,9 +7,7 @@
- <%= @name %>
-
<%- @Icon('info') %>
-
+ <%= @name %> #<%= @session.id %>
<%- @T('disconnect') %>
@@ -24,6 +22,25 @@
+
diff --git a/app/assets/javascripts/app/views/customer_chat/chat_window_info.jst.eco b/app/assets/javascripts/app/views/customer_chat/chat_window_info.jst.eco deleted file mode 100644 index a7c3b1ff3..000000000 --- a/app/assets/javascripts/app/views/customer_chat/chat_window_info.jst.eco +++ /dev/null @@ -1,17 +0,0 @@ -
-
    -<% if @session: %> -
  • <%- @T('Created at') %>: <%- @Ttimestamp(@session.created_at) %> -<% end %> -<% if @session && @session.preferences: %> - <% if @session.preferences.geo_ip: %> -
  • GeoIP: <%= @session.preferences.geo_ip.country_name %> <%= @session.preferences.geo_ip.city_name %> - <% end %> - <% if @session.preferences.remote_ip: %> -
  • IP: <%= @session.preferences.remote_ip %> - <% end %> - <% if @session.preferences.dns_name: %> -
  • DNS: <%= @session.preferences.dns_name %> - <% end %> -<% end %> -
\ No newline at end of file diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb index 1ec9c76f8..fad61f23c 100644 --- a/app/controllers/search_controller.rb +++ b/app/controllers/search_controller.rb @@ -33,9 +33,10 @@ class SearchController < ApplicationController objects_in_order = [] objects_in_order_hash = {} objects.each do |object| - preferences = object.constantize.search_preferences(current_user) + local_class = object.constantize + preferences = local_class.search_preferences(current_user) next if !preferences - objects_in_order_hash[preferences[:prio]] = object + objects_in_order_hash[preferences[:prio]] = local_class end objects_in_order_hash.keys.sort.reverse_each do |prio| objects_in_order.push objects_in_order_hash[prio] @@ -64,16 +65,18 @@ class SearchController < ApplicationController items = SearchIndexBackend.search(query, limit, objects_with_direct_search_index) items.each do |item| require item[:type].to_filename - record = Kernel.const_get(item[:type]).lookup(id: item[:id]) + local_class = Kernel.const_get(item[:type]) + record = local_class.lookup(id: item[:id]) next if !record assets = record.assets(assets) + item[:type] = local_class.to_app_model.to_s result.push item end end # e. g. do ticket query by Ticket class to handle ticket permissions objects_without_direct_search_index.each do |object| - object_result = search_generic_backend(object, query, limit, current_user, assets) + object_result = search_generic_backend(object.constantize, query, limit, current_user, assets) if object_result.present? result = result.concat(object_result) end @@ -83,7 +86,7 @@ class SearchController < ApplicationController result_in_order = [] objects_in_order.each do |object| result.each do |item| - next if item[:type] != object + next if item[:type] != object.to_app_model.to_s item[:id] = item[:id].to_i result_in_order.push item end @@ -110,7 +113,7 @@ class SearchController < ApplicationController private def search_generic_backend(object, query, limit, current_user, assets) - found_objects = object.constantize.search( + found_objects = object.search( query: query, limit: limit, current_user: current_user, @@ -119,7 +122,7 @@ class SearchController < ApplicationController found_objects.each do |found_object| item = { id: found_object.id, - type: found_object.class.to_s + type: found_object.class.to_app_model.to_s } result.push item assets = found_object.assets(assets) diff --git a/app/models/chat/session.rb b/app/models/chat/session.rb index 70cd0d818..cf83beea4 100644 --- a/app/models/chat/session.rb +++ b/app/models/chat/session.rb @@ -1,4 +1,15 @@ class Chat::Session < ApplicationModel + include HasSearchIndexBackend + include HasTags + + extend Chat::Session::Search + load 'chat/session/search_index.rb' + include Chat::Session::SearchIndex + load 'chat/session/assets.rb' + include Chat::Session::Assets + + has_many :messages, class_name: 'Chat::Message', foreign_key: 'chat_session_id' + before_create :generate_session_id store :preferences diff --git a/app/models/chat/session/assets.rb b/app/models/chat/session/assets.rb new file mode 100644 index 000000000..6410e2692 --- /dev/null +++ b/app/models/chat/session/assets.rb @@ -0,0 +1,56 @@ +# Copyright (C) 2012-2016 Zammad Foundation, http://zammad-foundation.org/ +module Chat::Session::Assets + +=begin + +get all assets / related models for this chat + + chat = Chat::Session.find(123) + result = Chat::Session.assets(assets_if_exists) + +returns + + result = { + users: { + 123: user_model_123, + 1234: user_model_1234, + }, + chat_sessions: [ chat_session_model1 ] + } + +=end + + def assets(data) + + app_model_chat_session = Chat::Session.to_app_model + app_model_chat = Chat.to_app_model + app_model_user = User.to_app_model + + data[ app_model_chat_session ] ||= {} + + if !data[ app_model_chat_session ][ id ] + data[ app_model_chat_session ][ id ] = attributes_with_association_ids + data[ app_model_chat_session ][ id ]['messages'] = [] + messages.each do |message| + data[ app_model_chat_session ][ id ]['messages'].push message.attributes + end + data[ app_model_chat_session ][ id ]['tags'] = tag_list + end + + if !data[ app_model_chat ] || !data[ app_model_chat ][ chat_id ] + chat = Chat.lookup(id: chat_id) + if chat + data = chat.assets(data) + end + end + + %w[created_by_id updated_by_id].each do |local_user_id| + next if !self[ local_user_id ] + next if data[ app_model_user ] && data[ app_model_user ][ self[ local_user_id ] ] + user = User.lookup(id: self[ local_user_id ]) + next if !user + data = user.assets(data) + end + data + end +end diff --git a/app/models/chat/session/search.rb b/app/models/chat/session/search.rb new file mode 100644 index 000000000..87d12f1c9 --- /dev/null +++ b/app/models/chat/session/search.rb @@ -0,0 +1,80 @@ +# Copyright (C) 2012-2016 Zammad Foundation, http://zammad-foundation.org/ + +class Chat::Session + module Search + +=begin + +search organizations preferences + + result = Chat::Session.search_preferences(user_model) + +returns if user has permissions to search + + result = { + prio: 1000, + direct_search_index: true + } + +returns if user has no permissions to search + + result = false + +=end + + def search_preferences(current_user) + return false if Setting.get('chat') != true || !current_user.permissions?('chat.agent') + { + prio: 900, + direct_search_index: true, + } + end + +=begin + +search organizations + + result = Chat::Session.search( + current_user: User.find(123), + query: 'search something', + limit: 15, + ) + +returns + + result = [organization_model1, organization_model2] + +=end + + def search(params) + + # get params + query = params[:query] + limit = params[:limit] || 10 + current_user = params[:current_user] + + # enable search only for agents and admins + return [] if !search_preferences(current_user) + + # try search index backend + if SearchIndexBackend.enabled? + items = SearchIndexBackend.search(query, limit, 'Chat::Session') + chat_sessions = [] + items.each do |item| + chat_session = Chat::Session.lookup(id: item[:id]) + next if !chat_session + chat_sessions.push chat_session + end + return chat_sessions + end + + # fallback do sql query + # - stip out * we already search for *query* - + query.delete! '*' + chat_sessions = Chat::Session.where( + 'name LIKE ?', "%#{query}%" + ).order('name').limit(limit).to_a + chat_sessions + end + end +end diff --git a/app/models/chat/session/search_index.rb b/app/models/chat/session/search_index.rb new file mode 100644 index 000000000..b81196722 --- /dev/null +++ b/app/models/chat/session/search_index.rb @@ -0,0 +1,36 @@ +# Copyright (C) 2012-2016 Zammad Foundation, http://zammad-foundation.org/ +module Chat::Session::SearchIndex + +=begin + +lookup name of ref. objects + + chat_session = Chat::Session.find(123) + result = chat_session.search_index_attribute_lookup + +returns + + attributes # object with lookup data + +=end + + def search_index_attribute_lookup + attributes = super + return if !attributes + + attributes[:tag] = tag_list + + messages = Chat::Message.where(chat_session_id: id) + attributes['messages'] = [] + messages.each do |message| + + # lookup attributes of ref. objects (normally name and note) + message_attributes = message.search_index_attribute_lookup + + attributes['messages'].push message_attributes + end + + attributes + end + +end diff --git a/lib/sessions/event/base.rb b/lib/sessions/event/base.rb index 5c1b659f2..1b02e6bfe 100644 --- a/lib/sessions/event/base.rb +++ b/lib/sessions/event/base.rb @@ -49,7 +49,7 @@ class Sessions::Event::Base true end - def permission_check(key, event) + def current_user_id if !@session error = { event: "#{event}_error", @@ -60,7 +60,7 @@ class Sessions::Event::Base Sessions.send(@client_id, error) return end - if !@session['id'] + if @session['id'].blank? error = { event: "#{event}_error", data: { @@ -70,7 +70,13 @@ class Sessions::Event::Base Sessions.send(@client_id, error) return end - user = User.lookup(id: @session['id']) + @session['id'] + end + + def current_user + user_id = current_user_id + return if !user_id + user = User.find_by(id: user_id) if !user error = { event: "#{event}_error", @@ -81,6 +87,12 @@ class Sessions::Event::Base Sessions.send(@client_id, error) return end + user + end + + def permission_check(key, event) + user = current_user + return if !user if !user.permissions?(key) error = { event: "#{event}_error", diff --git a/lib/sessions/event/chat_session_update.rb b/lib/sessions/event/chat_session_update.rb new file mode 100644 index 000000000..0f93dfc31 --- /dev/null +++ b/lib/sessions/event/chat_session_update.rb @@ -0,0 +1,35 @@ +class Sessions::Event::ChatSessionUpdate < Sessions::Event::ChatBase + + def run + return super if super + return if !check_chat_session_exists + return if !permission_check('chat.agent', 'chat') + chat_session = current_chat_session + + if @payload['data']['name'] != chat_session.name + chat_session.name = @payload['data']['name'] + chat_session.save! + end + + if @payload['data']['tags'] + new_tags = @payload['data']['tags'].split(',') + + new_tags.each(&:strip!) + + tags = chat_session.tag_list + new_tags.each do |new_tag| + next if new_tag.blank? + next if tags.include?(new_tag) + chat_session.tag_add(new_tag, current_user_id) + end + + tags.each do |tag| + next if new_tags.include?(tag) + chat_session.tag_remove(tag, current_user_id) + end + end + + nil + end + +end diff --git a/test/unit/model_test.rb b/test/unit/model_test.rb index 9c5431539..d31e69fbb 100644 --- a/test/unit/model_test.rb +++ b/test/unit/model_test.rb @@ -243,8 +243,8 @@ class ModelTest < ActiveSupport::TestCase assert(searchable.include?(Ticket)) assert(searchable.include?(User)) assert(searchable.include?(Organization)) - assert_equal(3, searchable.count) - + assert(searchable.include?(Chat::Session)) + assert_equal(4, searchable.count) end end From fe2c77df9fa8765fa447da4fdca644dc3c82b212 Mon Sep 17 00:00:00 2001 From: Martin Edenhofer Date: Mon, 18 Dec 2017 04:49:08 +0100 Subject: [PATCH 4/4] Just update chat window title if title exists. --- app/assets/javascripts/app/controllers/chat.coffee | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/app/controllers/chat.coffee b/app/assets/javascripts/app/controllers/chat.coffee index 8eb974089..091c771b2 100644 --- a/app/assets/javascripts/app/controllers/chat.coffee +++ b/app/assets/javascripts/app/controllers/chat.coffee @@ -441,7 +441,8 @@ class ChatWindow extends App.Controller tags: params.tags ) - @metaName.text(params.name) + if !_.isEmpty(params.name) + @metaName.text(params.name) render: -> @html App.view('customer_chat/chat_window')(