Update queue position also if customer closed chat. Get new chat state from server after client closed chat. Added more indexes.

This commit is contained in:
Martin Edenhofer 2015-11-13 17:03:58 +01:00
parent 543c27a0bc
commit 646c11406c
10 changed files with 527 additions and 51 deletions

View file

@ -26,7 +26,7 @@ class Chat < ApplicationModel
} }
# get queue postion if needed # get queue postion if needed
session = Chat.session_state(session_id) session = Chat::Session.messages_by_session_id(session_id)
if session if session
return { return {
state: 'reconnect', state: 'reconnect',
@ -51,10 +51,10 @@ class Chat < ApplicationModel
end end
# if all seads are used # if all seads are used
if active_chat_count >= max_queue if Chat.active_chat_count >= max_queue
return { return {
state: 'no_seats_available', state: 'no_seats_available',
queue: seads_available, queue: Chat.seads_available,
} }
end end
@ -62,32 +62,14 @@ class Chat < ApplicationModel
{ state: 'online' } { state: 'online' }
end end
def self.session_state(session_id)
session_attributes = []
chat_session = Chat::Session.find_by(session_id: session_id)
return if !chat_session
Chat::Message.where(chat_session_id: chat_session.id).each { |message|
session_attributes.push message.attributes
}
session_attributes
end
def self.agent_state(user_id) def self.agent_state(user_id)
return { state: 'chat_disabled' } if !Setting.get('chat') return { state: 'chat_disabled' } if !Setting.get('chat')
actice_sessions = []
Chat::Session.where(state: 'running', user_id: user_id).order('created_at ASC').each {|session|
session_attributes = session.attributes
session_attributes['messages'] = []
Chat::Message.where(chat_session_id: session.id).each { |message|
session_attributes['messages'].push message.attributes
}
actice_sessions.push session_attributes
}
{ {
waiting_chat_count: waiting_chat_count, waiting_chat_count: waiting_chat_count,
running_chat_count: running_chat_count, running_chat_count: running_chat_count,
#available_agents: available_agents, active_sessions: Chat::Session.active_chats_by_user_id(user_id),
active_sessions: actice_sessions, seads_available: seads_available,
seads_total: seads_total,
active: Chat::Agent.state(user_id) active: Chat::Agent.state(user_id)
} }
end end
@ -100,11 +82,11 @@ class Chat < ApplicationModel
Chat::Session.where(state: ['running']).count Chat::Session.where(state: ['running']).count
end end
def active_chat_count def self.active_chat_count
Chat::Session.where(state: %w(waiting running)).count Chat::Session.where(state: %w(waiting running)).count
end end
def available_agents(diff = 2.minutes) def self.available_agents(diff = 2.minutes)
agents = {} agents = {}
Chat::Agent.where(active: true).where('updated_at > ?', Time.zone.now - diff).each {|record| Chat::Agent.where(active: true).where('updated_at > ?', Time.zone.now - diff).each {|record|
agents[record.updated_by_id] = record.concurrent agents[record.updated_by_id] = record.concurrent
@ -112,15 +94,15 @@ class Chat < ApplicationModel
agents agents
end end
def seads_total(diff = 2.minutes) def self.seads_total(diff = 2.minutes)
total = 0 total = 0
available_agents(diff).each {|_record, concurrent| available_agents(diff).each {|_user_id, concurrent|
total += concurrent total += concurrent
} }
total total
end end
def seads_available(diff = 2.minutes) def self.seads_available(diff = 2.minutes)
seads_total(diff) - active_chat_count seads_total(diff) - active_chat_count
end end
end end

View file

@ -35,4 +35,27 @@ class Chat::Session < ApplicationModel
} }
position position
end end
def self.messages_by_session_id(session_id)
chat_session = Chat::Session.find_by(session_id: session_id)
return if !chat_session
session_attributes = []
Chat::Message.where(chat_session_id: chat_session.id).each { |message|
session_attributes.push message.attributes
}
session_attributes
end
def self.active_chats_by_user_id(user_id)
actice_sessions = []
Chat::Session.where(state: 'running', user_id: user_id).order('created_at ASC').each {|session|
session_attributes = session.attributes
session_attributes['messages'] = []
Chat::Message.where(chat_session_id: session.id).each { |message|
session_attributes['messages'].push message.attributes
}
actice_sessions.push session_attributes
}
actice_sessions
end
end end

View file

@ -0,0 +1,9 @@
class UpdateChat < ActiveRecord::Migration
def up
add_index :chat_sessions, [:session_id]
add_index :chat_sessions, [:chat_id]
add_index :chat_messages, [:chat_session_id]
add_index :chat_agents, [:active]
add_index :chat_agents, [:updated_by_id], unique: true
end
end

View file

@ -4,7 +4,6 @@ class Sessions::Event::ChatBase
@data = data @data = data
@session = session @session = session
@client_id = client_id @client_id = client_id
end end
def pre def pre
@ -35,4 +34,22 @@ class Sessions::Event::ChatBase
} }
end 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
end end

View file

@ -49,6 +49,9 @@ class Sessions::Event::ChatSessionClose < Sessions::Event::ChatBase
# set state update to all agents # set state update to all agents
broadcast_agent_state_update broadcast_agent_state_update
# send position update to other waiting sessions
broadcast_customer_state_update
# notify about "leaving" # notify about "leaving"
else else
message = { message = {

View file

@ -39,20 +39,9 @@ class Sessions::Event::ChatSessionStart < Sessions::Event::ChatBase
chat_session.send_to_recipients(data) chat_session.send_to_recipients(data)
# send position update to other waiting sessions # send position update to other waiting sessions
position = 0 broadcast_customer_state_update
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)
}
# send state update to agents
broadcast_agent_state_update broadcast_agent_state_update
nil nil

View file

@ -132,12 +132,15 @@ do($ = window.jQuery, window) ->
@log 'debug', 'Zammad Chat: ready' @log 'debug', 'Zammad Chat: ready'
when 'offline' when 'offline'
@onError 'Zammad Chat: No agent online' @onError 'Zammad Chat: No agent online'
@hide()
@wsClose() @wsClose()
when 'chat_disabled' when 'chat_disabled'
@onError 'Zammad Chat: Chat is disabled' @onError 'Zammad Chat: Chat is disabled'
@hide()
@wsClose() @wsClose()
when 'no_seats_available' when 'no_seats_available'
@onError 'Zammad Chat: Too many clients in queue. Clients in queue: ', pipe.data.queue @onError 'Zammad Chat: Too many clients in queue. Clients in queue: ', pipe.data.queue
@hide()
@wsClose() @wsClose()
when 'reconnect' when 'reconnect'
@log 'debug', 'old messages', pipe.data.session @log 'debug', 'old messages', pipe.data.session
@ -291,9 +294,6 @@ do($ = window.jQuery, window) ->
if @onInitialQueueDelayId if @onInitialQueueDelayId
clearTimeout(@onInitialQueueDelayId) clearTimeout(@onInitialQueueDelayId)
sessionStorage.removeItem 'sessionId'
sessionStorage.removeItem 'unfinished_message'
@closeWindow() @closeWindow()
closeWindow: => closeWindow: =>
@ -309,6 +309,10 @@ do($ = window.jQuery, window) ->
session_id: @sessionId session_id: @sessionId
@setSessionId undefined @setSessionId undefined
sessionStorage.removeItem 'unfinished_message'
# restart connection
@onWebSocketOpen()
hide: -> hide: ->
@el.removeClass('zammad-chat-is-visible') @el.removeClass('zammad-chat-is-visible')

View file

@ -2,7 +2,6 @@
<meta charset="utf-8"> <meta charset="utf-8">
<title>Zammad Chat</title> <title>Zammad Chat</title>
<link rel="stylesheet" href="chat.css"> <link rel="stylesheet" href="chat.css">
<link rel="stylesheet" href="znuny.css">
<meta name="viewport" content="width=device-width"> <meta name="viewport" content="width=device-width">
<script>document.write('<script src="http://' + (location.host || 'localhost').split(':')[0] + ':35738/livereload.js?snipver=1"></' + 'script>')</script> <script>document.write('<script src="http://' + (location.host || 'localhost').split(':')[0] + ':35738/livereload.js?snipver=1"></' + 'script>')</script>
<style> <style>
@ -66,7 +65,7 @@
} }
</style> </style>
<img class="mockup" width="100%" src="znuny.png"> <img class="mockup" width="100%" src="website.png">
<div class="settings"> <div class="settings">
<table> <table>
@ -142,9 +141,6 @@
host: 'ws://localhost', host: 'ws://localhost',
port: 6042, port: 6042,
debug: true, debug: true,
background: '#494d52',
flat: true,
show: false
}); });
$('.settings :input').on({ $('.settings :input').on({

View file

@ -0,0 +1,147 @@
<!doctype html>
<meta charset="utf-8">
<title>Zammad Chat</title>
<link rel="stylesheet" href="chat.css">
<link rel="stylesheet" href="znuny.css">
<meta name="viewport" content="width=device-width">
<script>document.write('<script src="http://' + (location.host || 'localhost').split(':')[0] + ':35738/livereload.js?snipver=1"></' + 'script>')</script>
<style>
body {
margin: 0;
font-family: sans-serif;
}
.mockup {
vertical-align: bottom;
}
.settings {
position: fixed;
left: 20px;
top: 20px;
background: white;
font-size: 14px;
padding: 10px;
border-radius: 5px;
box-shadow: 0 3px 10px rgba(0,0,0,.3);
}
.settings input {
vertical-align: middle;
}
.settings input + input {
margin-right: 3px;
}
table td:first-child {
text-align: right;
padding-right: 0;
}
td {
padding: 5px;
}
h2 {
font-size: 1em;
margin: 0;
}
@media only screen and (max-width: 768px) {
.settings {
display: none;
}
}
.Box {
background: hsl(0,0%,91%);
width: 26px;
height: 24px;
color: hsl(0,0%,47%);
float: left;
}
.Box.Active {
background: hsl(0,0%,36%);
color: white;
}
</style>
<img class="mockup" width="100%" src="znuny.png">
<div class="settings">
<table>
<tr>
<td>
<td><h2>Settings</h2>
<tr>
<td>
<input id="flat" type="checkbox" data-option="flat">
<td>
<label for="flat">Flat Design</label>
<tr>
<td>
<input type="color" id="color" value="#AE99D6" data-option="color">
<td>
<label for="color">Color</label>
<tr>
<td>
<input type="range" id="borderRadius" value="5" min="0" max="20" data-option="borderRadius">
<input type="number" value="5" min="5" max="20" data-option="borderRadius">px
<td>
<label for="borderRadius">Border Radius</label>
<tr>
<td>
<input type="range" id="fontSize" value="12" min="11" max="18" data-option="fontSize">
<input type="number" value="12" min="11" max="18" data-option="fontSize">px
<td>
<label for="fontSize">Font Size</label>
<tr>
<td>
<td><button class="open-zammad-chat">Open Chat</button>
</table>
</div>
<script src="jquery-2.1.4.min.js"></script>
<script src="chat.js"></script>
<script>
var chat = new ZammadChat({
host: 'ws://localhost',
port: 6042,
debug: true,
background: '#494d52',
flat: true,
shown: false
});
$('.settings :input').on({
change: function(){
switch($(this).attr('data-option')){
case "flat":
$('.zammad-chat').toggleClass('zammad-chat--flat', this.checked);
break;
case "color":
setScssVariable('themeColor', this.value);
updateStyle();
break;
case "borderRadius":
setScssVariable('borderRadius', this.value + "px");
updateStyle();
break;
}
},
input: function(){
switch($(this).attr('data-option')){
case "borderRadius":
$('[data-option="borderRadius"]').val(this.value);
setScssVariable('borderRadius', this.value + "px");
updateStyle();
break;
case "fontSize":
$('[data-option="fontSize"]').val(this.value);
setScssVariable('fontSize', this.value + "px");
updateStyle();
break;
}
}
});
</script>

306
test/unit/chat_test.rb Normal file
View file

@ -0,0 +1,306 @@
# encoding: utf-8
require 'test_helper'
class ChatTest < ActiveSupport::TestCase
# create base
groups = Group.all
roles = Role.where( name: %w(Agent Chat) )
agent1 = User.create_or_update(
login: 'ticket-chat-agent1@example.com',
firstname: 'Notification',
lastname: 'Agent1',
email: 'ticket-chat-agent1@example.com',
password: 'agentpw',
active: true,
roles: roles,
groups: groups,
updated_at: '2015-02-05 16:37:00',
updated_by_id: 1,
created_by_id: 1,
)
agent2 = User.create_or_update(
login: 'ticket-chat-agent2@example.com',
firstname: 'Notification',
lastname: 'Agent2',
email: 'ticket-chat-agent2@example.com',
password: 'agentpw',
active: true,
roles: roles,
groups: groups,
updated_at: '2015-02-05 16:38:00',
updated_by_id: 1,
created_by_id: 1,
)
test 'default test' do
Chat.delete_all
Chat::Topic.delete_all
Chat::Session.delete_all
Chat::Message.delete_all
Chat::Agent.delete_all
Setting.set('chat', false)
chat = Chat.create(
name: 'default',
max_queue: 5,
note: '',
active: true,
updated_by_id: 1,
created_by_id: 1,
)
chat_topic = Chat::Topic.create(
chat_id: chat.id,
name: 'default',
updated_by_id: 1,
created_by_id: 1,
)
# check if feature is disabled
assert_equal('chat_disabled', chat.customer_state[:state])
assert_equal('chat_disabled', Chat.agent_state(agent1.id)[:state])
Setting.set('chat', true)
# check customer state
assert_equal('offline', chat.customer_state[:state])
# check agent state
agent_state = Chat.agent_state(agent1.id)
assert_equal(0, agent_state[:waiting_chat_count])
assert_equal(0, agent_state[:running_chat_count])
assert_equal([], agent_state[:active_sessions])
assert_equal(0, agent_state[:seads_available])
assert_equal(0, agent_state[:seads_total])
assert_equal(false, agent_state[:active])
# set agent 1 to active
chat_agent1 = Chat::Agent.create_or_update(
active: true,
concurrent: 4,
updated_by_id: agent1.id,
created_by_id: agent1.id,
)
# check customer state
assert_equal('online', chat.customer_state[:state])
# check agent state
agent_state = Chat.agent_state(agent1.id)
assert_equal(0, agent_state[:waiting_chat_count])
assert_equal(0, agent_state[:running_chat_count])
assert_equal([], agent_state[:active_sessions])
assert_equal(4, agent_state[:seads_available])
assert_equal(4, agent_state[:seads_total])
assert_equal(true, agent_state[:active])
# start session
chat_session1 = Chat::Session.create(
chat_id: chat.id,
user_id: agent1.id,
)
assert(chat_session1.session_id)
# check customer state
assert_equal('online', chat.customer_state[:state])
# check agent state
agent_state = Chat.agent_state(agent1.id)
assert_equal(1, agent_state[:waiting_chat_count])
assert_equal(0, agent_state[:running_chat_count])
assert_equal([], agent_state[:active_sessions])
assert_equal(3, agent_state[:seads_available])
assert_equal(4, agent_state[:seads_total])
assert_equal(true, agent_state[:active])
# activate second agent
chat_agent2 = Chat::Agent.create_or_update(
active: true,
concurrent: 2,
updated_by_id: agent2.id,
created_by_id: agent2.id,
)
# check customer state
assert_equal('online', chat.customer_state[:state])
# check agent1 state
agent_state = Chat.agent_state(agent1.id)
assert_equal(1, agent_state[:waiting_chat_count])
assert_equal(0, agent_state[:running_chat_count])
assert_equal([], agent_state[:active_sessions])
assert_equal(5, agent_state[:seads_available])
assert_equal(6, agent_state[:seads_total])
assert_equal(true, agent_state[:active])
# check agent2 state
agent_state = Chat.agent_state(agent2.id)
assert_equal(1, agent_state[:waiting_chat_count])
assert_equal(0, agent_state[:running_chat_count])
assert_equal([], agent_state[:active_sessions])
assert_equal(5, agent_state[:seads_available])
assert_equal(6, agent_state[:seads_total])
assert_equal(true, agent_state[:active])
# start next chat
chat_session2 = Chat::Session.create(
chat_id: chat.id,
)
# check customer state
assert_equal('online', chat.customer_state[:state])
# check agent1 state
agent_state = Chat.agent_state(agent1.id)
assert_equal(2, agent_state[:waiting_chat_count])
assert_equal(0, agent_state[:running_chat_count])
assert_equal([], agent_state[:active_sessions])
assert_equal(4, agent_state[:seads_available])
assert_equal(6, agent_state[:seads_total])
assert_equal(true, agent_state[:active])
# check agent2 state
agent_state = Chat.agent_state(agent2.id)
assert_equal(2, agent_state[:waiting_chat_count])
assert_equal(0, agent_state[:running_chat_count])
assert_equal([], agent_state[:active_sessions])
assert_equal(4, agent_state[:seads_available])
assert_equal(6, agent_state[:seads_total])
assert_equal(true, agent_state[:active])
# start new chats
chat_session3 = Chat::Session.create(
chat_id: chat.id,
)
chat_session4 = Chat::Session.create(
chat_id: chat.id,
)
chat_session5 = Chat::Session.create(
chat_id: chat.id,
)
chat_session6 = Chat::Session.create(
chat_id: chat.id,
)
# check customer state
assert_equal('no_seats_available', chat.customer_state[:state])
# check agent1 state
agent_state = Chat.agent_state(agent1.id)
assert_equal(6, agent_state[:waiting_chat_count])
assert_equal(0, agent_state[:running_chat_count])
assert_equal([], agent_state[:active_sessions])
assert_equal(0, agent_state[:seads_available])
assert_equal(6, agent_state[:seads_total])
assert_equal(true, agent_state[:active])
# check agent2 state
agent_state = Chat.agent_state(agent2.id)
assert_equal(6, agent_state[:waiting_chat_count])
assert_equal(0, agent_state[:running_chat_count])
assert_equal([], agent_state[:active_sessions])
assert_equal(0, agent_state[:seads_available])
assert_equal(6, agent_state[:seads_total])
assert_equal(true, agent_state[:active])
chat_session6.state = 'running'
chat_session6.save
# check customer state
assert_equal('no_seats_available', chat.customer_state[:state])
assert_equal(0, chat.customer_state[:queue])
# check agent1 state
agent_state = Chat.agent_state(agent1.id)
assert_equal(5, agent_state[:waiting_chat_count])
assert_equal(1, agent_state[:running_chat_count])
assert_equal([], agent_state[:active_sessions])
assert_equal(0, agent_state[:seads_available])
assert_equal(6, agent_state[:seads_total])
assert_equal(true, agent_state[:active])
# check agent2 state
agent_state = Chat.agent_state(agent2.id)
assert_equal(5, agent_state[:waiting_chat_count])
assert_equal(1, agent_state[:running_chat_count])
assert_equal([], agent_state[:active_sessions])
assert_equal(0, agent_state[:seads_available])
assert_equal(6, agent_state[:seads_total])
assert_equal(true, agent_state[:active])
chat_agent2.active = false
chat_agent2.save
# check customer state
assert_equal('no_seats_available', chat.customer_state[:state])
assert_equal(-2, chat.customer_state[:queue])
# check agent1 state
agent_state = Chat.agent_state(agent1.id)
assert_equal(5, agent_state[:waiting_chat_count])
assert_equal(1, agent_state[:running_chat_count])
assert_equal([], agent_state[:active_sessions])
assert_equal(-2, agent_state[:seads_available])
assert_equal(4, agent_state[:seads_total])
assert_equal(true, agent_state[:active])
# check agent2 state
agent_state = Chat.agent_state(agent2.id)
assert_equal(5, agent_state[:waiting_chat_count])
assert_equal(1, agent_state[:running_chat_count])
assert_equal([], agent_state[:active_sessions])
assert_equal(-2, agent_state[:seads_available])
assert_equal(4, agent_state[:seads_total])
assert_equal(false, agent_state[:active])
chat_session6.state = 'closed'
chat_session6.save
# check customer state
assert_equal('no_seats_available', chat.customer_state[:state])
assert_equal(-1, chat.customer_state[:queue])
# check agent1 state
agent_state = Chat.agent_state(agent1.id)
assert_equal(5, agent_state[:waiting_chat_count])
assert_equal(0, agent_state[:running_chat_count])
assert_equal([], agent_state[:active_sessions])
assert_equal(-1, agent_state[:seads_available])
assert_equal(4, agent_state[:seads_total])
assert_equal(true, agent_state[:active])
# check agent2 state
agent_state = Chat.agent_state(agent2.id)
assert_equal(5, agent_state[:waiting_chat_count])
assert_equal(0, agent_state[:running_chat_count])
assert_equal([], agent_state[:active_sessions])
assert_equal(-1, agent_state[:seads_available])
assert_equal(4, agent_state[:seads_total])
assert_equal(false, agent_state[:active])
chat_session5.destroy
chat_session4.destroy
# check customer state
assert_equal('online', chat.customer_state[:state])
# check agent1 state
agent_state = Chat.agent_state(agent1.id)
assert_equal(3, agent_state[:waiting_chat_count])
assert_equal(0, agent_state[:running_chat_count])
assert_equal([], agent_state[:active_sessions])
assert_equal(1, agent_state[:seads_available])
assert_equal(4, agent_state[:seads_total])
assert_equal(true, agent_state[:active])
# check agent2 state
agent_state = Chat.agent_state(agent2.id)
assert_equal(3, agent_state[:waiting_chat_count])
assert_equal(0, agent_state[:running_chat_count])
assert_equal([], agent_state[:active_sessions])
assert_equal(1, agent_state[:seads_available])
assert_equal(4, agent_state[:seads_total])
assert_equal(false, agent_state[:active])
end
end