From f5fa05bf96a2025809fa1a1d983223b893403f57 Mon Sep 17 00:00:00 2001 From: Martin Edenhofer Date: Wed, 6 Jan 2016 01:45:03 +0100 Subject: [PATCH] Added set of auto offline if agent is not answering chats for 120 seconds. --- .../javascripts/app/controllers/chat.coffee | 26 ++++++++-- app/models/chat.rb | 51 +++++++++++++++++++ .../observer/chat/leave/background_job.rb | 1 + db/migrate/20160106000001_update_chat5.rb | 25 +++++++++ db/seeds.rb | 20 ++++++++ lib/sessions/event/chat_agent_state.rb | 2 +- lib/sessions/event/chat_base.rb | 31 ----------- lib/sessions/event/chat_session_close.rb | 4 +- lib/sessions/event/chat_session_init.rb | 2 +- lib/sessions/event/chat_session_start.rb | 4 +- script/build/test_startup.sh | 1 + test/browser/chat_test.rb | 16 ++++++ 12 files changed, 143 insertions(+), 40 deletions(-) create mode 100644 db/migrate/20160106000001_update_chat5.rb diff --git a/app/assets/javascripts/app/controllers/chat.coffee b/app/assets/javascripts/app/controllers/chat.coffee index f4b0f98f5..0c63ae240 100644 --- a/app/assets/javascripts/app/controllers/chat.coffee +++ b/app/assets/javascripts/app/controllers/chat.coffee @@ -18,6 +18,7 @@ class App.CustomerChat extends App.Controller @maxChatWindows = parseInt(preferences.chat.max_windows) @pushStateIntervalOn = undefined + @idleTimeout = parseInt(@Config.get('chat_agent_idle_timeout') || 120) @messageCounter = 0 @meta = active: false @@ -107,10 +108,10 @@ class App.CustomerChat extends App.Controller @el.find('.js-waitingCustomers').popover( trigger: 'hover' - container: 'body' html: true animation: false delay: 100 + placement: 'bottom' title: -> App.i18n.translateContent('Waiting Customers') content: => @@ -119,10 +120,10 @@ class App.CustomerChat extends App.Controller @el.find('.js-chattingCustomers').popover( trigger: 'hover' - container: 'body' html: true animation: false delay: 100 + placement: 'bottom' title: -> App.i18n.translateContent('Chatting Customers') content: => @@ -131,10 +132,10 @@ class App.CustomerChat extends App.Controller @el.find('.js-activeAgents').popover( trigger: 'hover' - container: 'body' html: true animation: false delay: 100 + placement: 'bottom' title: -> App.i18n.translateContent('Active Agents') content: => @@ -225,8 +226,11 @@ class App.CustomerChat extends App.Controller updateMeta: => if @meta.waiting_chat_count && @maxChatWindows > @windowCount() @$('.js-acceptChat').addClass('is-clickable is-blinking') + @idleTimeoutStart() else @$('.js-acceptChat').removeClass('is-clickable is-blinking') + @idleTimeoutStop() + @$('.js-badgeWaitingCustomers').text(@meta.waiting_chat_count) @$('.js-badgeChattingCustomers').text(@meta.running_chat_count) @$('.js-badgeActiveAgents').text(@meta.active_agent_count) @@ -271,6 +275,7 @@ class App.CustomerChat extends App.Controller acceptChat: => return if @windowCount() >= @maxChatWindows App.WebSocket.send(event:'chat_session_start') + @idleTimeoutStop() settings: (errors = {}) -> new Setting( @@ -278,6 +283,21 @@ class App.CustomerChat extends App.Controller errors: errors ) + idleTimeoutStart: => + return if @idleTimeoutId + switchOff = => + @switch(false) + @notify( + type: 'notice' + msg: App.i18n.translateContent('Chat not answered, set to offline automatically.') + ) + @idleTimeoutId = @delay(switchOff, @idleTimeout * 1000) + + idleTimeoutStop: => + return if !@idleTimeoutId + @clearDelay(@idleTimeoutId) + @idleTimeoutId = undefined + class CustomerChatRouter extends App.ControllerPermanent constructor: (params) -> super diff --git a/app/models/chat.rb b/app/models/chat.rb index 914304dd8..a061db3a7 100644 --- a/app/models/chat.rb +++ b/app/models/chat.rb @@ -165,6 +165,57 @@ class Chat < ApplicationModel =begin +broadcast new agent status to all agents + + Chat.broadcast_agent_state_update + +optional you can ignore it for dedecated user + + Chat.broadcast_agent_state_update(ignore_user_id) + +=end + + def self.broadcast_agent_state_update(ignore_user_id = nil) + + # send broadcast to agents + Chat::Agent.where('active = ? OR updated_at > ?', true, Time.zone.now - 15.minutes).each {|item| + next if item.updated_by_id == ignore_user_id + data = { + event: 'chat_status_agent', + data: Chat.agent_state(item.updated_by_id), + } + Sessions.send_to(item.updated_by_id, data) + } + end + +=begin + +broadcast new customer queue position to all waiting customers + + Chat.broadcast_customer_state_update + +=end + + def self.broadcast_customer_state_update + + # send position update to other waiting sessions + position = 0 + Chat::Session.where(state: 'waiting').order('created_at ASC').each {|local_chat_session| + position += 1 + data = { + event: 'chat_session_queue', + data: { + state: 'queue', + position: position, + session_id: local_chat_session.session_id, + }, + } + local_chat_session.send_to_recipients(data) + } + end + +=begin + cleanup old chat messages Chat.cleanup diff --git a/app/models/observer/chat/leave/background_job.rb b/app/models/observer/chat/leave/background_job.rb index 9b897b187..53c5eec5b 100644 --- a/app/models/observer/chat/leave/background_job.rb +++ b/app/models/observer/chat/leave/background_job.rb @@ -31,6 +31,7 @@ class Observer::Chat::Leave::BackgroundJob } chat_session.send_to_recipients(message, @client_id) + Chat.broadcast_agent_state_update end end diff --git a/db/migrate/20160106000001_update_chat5.rb b/db/migrate/20160106000001_update_chat5.rb new file mode 100644 index 000000000..f0691772e --- /dev/null +++ b/db/migrate/20160106000001_update_chat5.rb @@ -0,0 +1,25 @@ +class UpdateChat5 < ActiveRecord::Migration + def change + + Setting.create_if_not_exists( + title: 'Agent idle timeout', + name: 'chat_agent_idle_timeout', + area: 'Chat::Extended', + description: 'Idle timeout in seconds till agent is set offline automatically.', + options: { + form: [ + { + display: '', + null: false, + name: 'chat_agent_idle_timeout', + tag: 'input', + }, + ], + }, + preferences: {}, + state: '120', + frontend: true + ) + + end +end diff --git a/db/seeds.rb b/db/seeds.rb index 56340cc45..a760854c2 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -1214,6 +1214,26 @@ Setting.create_if_not_exists( frontend: true ) +Setting.create_if_not_exists( + title: 'Agent idle timeout', + name: 'chat_agent_idle_timeout', + area: 'Chat::Extended', + description: 'Idle timeout in seconds till agent is set offline automatically.', + options: { + form: [ + { + display: '', + null: false, + name: 'chat_agent_idle_timeout', + tag: 'input', + }, + ], + }, + preferences: {}, + state: '120', + frontend: true +) + Setting.create_if_not_exists( title: 'Define searchable models.', name: 'models_searchable', diff --git a/lib/sessions/event/chat_agent_state.rb b/lib/sessions/event/chat_agent_state.rb index 56ff7ee55..a73a286ab 100644 --- a/lib/sessions/event/chat_agent_state.rb +++ b/lib/sessions/event/chat_agent_state.rb @@ -9,7 +9,7 @@ class Sessions::Event::ChatAgentState < Sessions::Event::ChatBase Chat::Agent.state(@session['id'], @payload['data']['active']) # broadcast new state to agents - broadcast_agent_state_update(@session['id']) + Chat.broadcast_agent_state_update(@session['id']) { event: 'chat_agent_state', diff --git a/lib/sessions/event/chat_base.rb b/lib/sessions/event/chat_base.rb index fe36b7418..2463c952b 100644 --- a/lib/sessions/event/chat_base.rb +++ b/lib/sessions/event/chat_base.rb @@ -23,37 +23,6 @@ class Sessions::Event::ChatBase < Sessions::Event::Base } end - def broadcast_agent_state_update(ignore_user_id = nil) - - # send broadcast to agents - Chat::Agent.where(active: true).each {|item| - next if item.updated_by_id == ignore_user_id - data = { - event: 'chat_status_agent', - data: Chat.agent_state(item.updated_by_id), - } - Sessions.send_to(item.updated_by_id, data) - } - end - - def broadcast_customer_state_update - - # send position update to other waiting sessions - position = 0 - Chat::Session.where(state: 'waiting').order('created_at ASC').each {|local_chat_session| - position += 1 - data = { - event: 'chat_session_queue', - data: { - state: 'queue', - position: position, - session_id: local_chat_session.session_id, - }, - } - local_chat_session.send_to_recipients(data) - } - end - def agent_permission_check if !@session error = { diff --git a/lib/sessions/event/chat_session_close.rb b/lib/sessions/event/chat_session_close.rb index 4170f82a5..f6df119dc 100644 --- a/lib/sessions/event/chat_session_close.rb +++ b/lib/sessions/event/chat_session_close.rb @@ -32,10 +32,10 @@ class Sessions::Event::ChatSessionClose < Sessions::Event::ChatBase chat_session.save # set state update to all agents - broadcast_agent_state_update + Chat.broadcast_agent_state_update # send position update to other waiting sessions - broadcast_customer_state_update + Chat.broadcast_customer_state_update # notify about "leaving" else diff --git a/lib/sessions/event/chat_session_init.rb b/lib/sessions/event/chat_session_init.rb index e2e0a5f05..bef090545 100644 --- a/lib/sessions/event/chat_session_init.rb +++ b/lib/sessions/event/chat_session_init.rb @@ -23,7 +23,7 @@ class Sessions::Event::ChatSessionInit < Sessions::Event::ChatBase ) # send broadcast to agents - broadcast_agent_state_update + Chat.broadcast_agent_state_update # return new session { diff --git a/lib/sessions/event/chat_session_start.rb b/lib/sessions/event/chat_session_start.rb index 202228939..cc81edaaf 100644 --- a/lib/sessions/event/chat_session_start.rb +++ b/lib/sessions/event/chat_session_start.rb @@ -52,10 +52,10 @@ class Sessions::Event::ChatSessionStart < Sessions::Event::ChatBase Sessions.send(@client_id, data) # send state update with sessions to agents - broadcast_agent_state_update + Chat.broadcast_agent_state_update # send position update to other waiting sessions - broadcast_customer_state_update + Chat.broadcast_customer_state_update nil end diff --git a/script/build/test_startup.sh b/script/build/test_startup.sh index cf05ef245..7f905dd74 100755 --- a/script/build/test_startup.sh +++ b/script/build/test_startup.sh @@ -8,6 +8,7 @@ export ZAMMAD_SETTING_TTL=15 rails r "Setting.set('developer_mode', true)" rails r "Setting.set('websocket_port', '$WS_PORT')" rails r "Setting.set('fqdn', '$IP:$BROWSER_PORT')" +rails r "Setting.set('chat_agent_idle_timeout', '45')" pumactl start --pidfile tmp/pids/puma.pid -d -p $APP_PORT -e $RAILS_ENV script/websocket-server.rb start -d -p $WS_PORT diff --git a/test/browser/chat_test.rb b/test/browser/chat_test.rb index 98b68c540..d56f527e5 100644 --- a/test/browser/chat_test.rb +++ b/test/browser/chat_test.rb @@ -576,6 +576,11 @@ class ChatTest < TestCase ) agent.find_elements( { css: '.active .chat-window .js-close' } ).each(&:click) + exists( + browser: agent, + css: '#navigation .js-switch input[checked]' + ) + # no customer action, hide widget customer = browser_instance location( @@ -618,6 +623,17 @@ class ChatTest < TestCase timeout: 120, ) + # check if agent is offline, idle timeout, chat not answered + exists_not( + browser: agent, + css: '#navigation .js-switch input[checked]' + ) + switch( + browser: agent, + css: '#navigation .js-switch', + type: 'on', + ) + # no customer action, show sorry screen reload( browser: customer,