Implemented feature to transfer chat sessions to another chat topic.

This commit is contained in:
Martin Edenhofer 2019-12-03 07:29:02 +01:00
parent 3c788ca426
commit 133b570fba
15 changed files with 491 additions and 160 deletions

View file

@ -4,11 +4,12 @@ class App.CustomerChat extends App.Controller
'click .js-settings': 'settings' 'click .js-settings': 'settings'
elements: elements:
'.js-acceptChat': 'acceptChatElement' '.js-acceptChat': 'acceptChatElement'
'.js-badgeWaitingCustomers': 'badgeWaitingCustomers' '.js-badgeWaitingCustomers': 'badgeWaitingCustomers'
'.js-totalInfo': 'totalInfo'
'.js-badgeChattingCustomers': 'badgeChattingCustomers' '.js-badgeChattingCustomers': 'badgeChattingCustomers'
'.js-badgeActiveAgents': 'badgeActiveAgents' '.js-badgeActiveAgents': 'badgeActiveAgents'
'.chat-workspace': 'workspace' '.chat-workspace': 'workspace'
sounds: sounds:
chat_new: new Audio('assets/sounds/chat_new.mp3') chat_new: new Audio('assets/sounds/chat_new.mp3')
@ -28,7 +29,9 @@ class App.CustomerChat extends App.Controller
@meta = @meta =
active: false active: false
waiting_chat_count: 0 waiting_chat_count: 0
waiting_chat_count_by_chat: {}
waiting_chat_session_list: [] waiting_chat_session_list: []
waiting_chat_session_list_by_chat: {}
running_chat_count: 0 running_chat_count: 0
running_chat_session_list: [] running_chat_session_list: []
active_agent_count: 0 active_agent_count: 0
@ -99,65 +102,23 @@ class App.CustomerChat extends App.Controller
@html App.view('customer_chat/index')() @html App.view('customer_chat/index')()
chatSessionList = (list) -> chatSessionList: (list) ->
for chat_session in list list = [] if !list
chat = App.Chat.find(chat_session.chat_id) for chat_session in list
chat_session.name = "#{chat.displayName()} [##{chat_session.id}]" chat = App.Chat.find(chat_session.chat_id)
chat_session.geo_data = '' chat_session.name = "#{chat.displayName()} [##{chat_session.id}]"
if chat_session.preferences && chat_session.preferences.geo_ip chat_session.geo_data = ''
if chat_session.preferences.geo_ip.country_name if chat_session.preferences && chat_session.preferences.geo_ip
chat_session.geo_data += chat_session.preferences.geo_ip.country_name if chat_session.preferences.geo_ip.country_name
if chat_session.preferences.geo_ip.city_name chat_session.geo_data += chat_session.preferences.geo_ip.country_name
chat_session.geo_data += " #{chat_session.preferences.geo_ip.city_name}" if chat_session.preferences.geo_ip.city_name
if chat_session.user_id chat_session.geo_data += " #{chat_session.preferences.geo_ip.city_name}"
chat_session.user = App.User.find(chat_session.user_id) if chat_session.user_id
App.view('customer_chat/chat_list')( chat_session.user = App.User.find(chat_session.user_id)
chat_sessions: list App.view('customer_chat/chat_list')(
) chat_sessions: list
@el.find('.js-waitingCustomers .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('Waiting Customers')
content: =>
chatSessionList(@meta.waiting_chat_session_list)
) )
@el.find('.js-chattingCustomers .js-info').popover(
trigger: 'hover'
html: true
animation: false
delay: 0
placement: 'bottom'
container: 'body'
title: ->
App.i18n.translateContent('Chatting Customers')
content: =>
chatSessionList(@meta.running_chat_session_list)
)
@el.find('.js-activeAgents .js-info').popover(
trigger: 'hover'
html: true
animation: false
delay: 0
placement: 'bottom'
container: 'body'
title: ->
App.i18n.translateContent('Active Agents')
content: =>
users = []
for user_id in @meta.active_agent_ids
users.push App.User.find(user_id)
App.view('customer_chat/user_list')(
users: users
)
)
show: (params) => show: (params) =>
@title 'Customer Chat', true @title 'Customer Chat', true
@ -248,15 +209,68 @@ class App.CustomerChat extends App.Controller
@stopPushState() @stopPushState()
@pushState() @pushState()
activeChatTopcis: =>
preferences = @Session.get('preferences')
return [] if !preferences
return [] if !preferences.chat
return [] if !preferences.chat.active
chats = []
for chat in App.Chat.all()
if preferences.chat.active[chat.id] is 'on' || preferences.chat.active[chat.id.toString()] is 'on'
chats.push chat
chats
updateMeta: => updateMeta: =>
$('.popover').remove()
activeChatTopcis = @activeChatTopcis()
@$('.js-header').html(App.view('customer_chat/chat_header')(chats: activeChatTopcis))
@refreshElements()
if @meta.waiting_chat_count && @maxChatWindows > @windowCount() if @meta.waiting_chat_count && @maxChatWindows > @windowCount()
@acceptChatElement.addClass('is-clickable is-blinking')
# activate normal button
@acceptChatElement.not('[data-chat-id]').addClass('is-active pulsate-animation')
# activate specific chat buttons
if activeChatTopcis.length > 1
for chat in activeChatTopcis
if @meta.waiting_chat_count_by_chat[chat.id]
@$(".js-header .js-acceptChat[data-chat-id=#{chat.id}]").addClass('is-active pulsate-animation').attr('disabled', false)
@idleTimeoutStart() @idleTimeoutStart()
else else
@acceptChatElement.removeClass('is-clickable is-blinking') @acceptChatElement.removeClass('is-active pulsate-animation')
@idleTimeoutStop() @idleTimeoutStop()
@badgeWaitingCustomers.text(@meta.waiting_chat_count) if activeChatTopcis.length > 1
for chat in App.Chat.all()
do (chat) =>
@$(".js-header .js-waitingCustomers[data-chat-id=#{chat.id}] .js-badgeWaitingCustomers").text(@meta.waiting_chat_count_by_chat[chat.id])
@el.find(".js-waitingCustomers[data-chat-id=#{chat.id}] .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('Waiting Customers')
content: =>
@chatSessionList(@meta.waiting_chat_session_list_by_chat[chat.id])
)
else
@badgeWaitingCustomers.text(@meta.waiting_chat_count)
@el.find('.js-waitingCustomers .js-totalInfo').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('Waiting Customers')
content: =>
@chatSessionList(@meta.waiting_chat_session_list_by_chat[activeChatTopcis[0].id])
)
@badgeChattingCustomers.text(@meta.running_chat_count) @badgeChattingCustomers.text(@meta.running_chat_count)
@badgeActiveAgents.text(@meta.active_agent_count) @badgeActiveAgents.text(@meta.active_agent_count)
@ -266,6 +280,37 @@ class App.CustomerChat extends App.Controller
@addChat(session) @addChat(session)
@meta.active_sessions = false @meta.active_sessions = false
@el.find('.js-chattingCustomers .js-info').popover(
trigger: 'hover'
html: true
animation: false
delay: 0
placement: 'bottom'
container: 'body'
title: ->
App.i18n.translateContent('Chatting Customers')
content: =>
@chatSessionList(@meta.running_chat_session_list)
)
@el.find('.js-activeAgents .js-info').popover(
trigger: 'hover'
html: true
animation: false
delay: 0
placement: 'bottom'
container: 'body'
title: ->
App.i18n.translateContent('Active Agents')
content: =>
users = []
for user_id in @meta.active_agent_ids
users.push App.User.find(user_id)
App.view('customer_chat/user_list')(
users: users
)
)
@updateNavMenu() @updateNavMenu()
addChat: (session) -> addChat: (session) ->
@ -298,9 +343,10 @@ class App.CustomerChat extends App.Controller
for session_id, chat of @chatWindows for session_id, chat of @chatWindows
chat.trigger('layout-changed') chat.trigger('layout-changed')
acceptChat: => acceptChat: (e) =>
return if @windowCount() >= @maxChatWindows return if @windowCount() >= @maxChatWindows
App.WebSocket.send(event:'chat_session_start') chat_id = $(e.currentTarget).attr('data-chat-id')
App.WebSocket.send(event:'chat_session_start', chat_id: chat_id)
@idleTimeoutStop() @idleTimeoutStop()
settings: (params = {}) -> settings: (params = {}) ->
@ -344,6 +390,7 @@ class ChatWindow extends App.Controller
'click .js-scrollHint': 'onScrollHintClick' 'click .js-scrollHint': 'onScrollHintClick'
'click .js-info': 'toggleMeta' 'click .js-info': 'toggleMeta'
'click .js-createTicket': 'ticketCreate' 'click .js-createTicket': 'ticketCreate'
'click .js-transferChat': 'transfer'
'submit .js-metaForm': 'sendMetaForm' 'submit .js-metaForm': 'sendMetaForm'
elements: elements:
@ -450,6 +497,7 @@ class ChatWindow extends App.Controller
@html App.view('customer_chat/chat_window')( @html App.view('customer_chat/chat_window')(
name: @name name: @name
session: @session session: @session
chats: App.Chat.all()
) )
@el.one('transitionend', @onTransitionend) @el.one('transitionend', @onTransitionend)
@ -766,6 +814,12 @@ class ChatWindow extends App.Controller
else if showHint else if showHint
@showScrollHint() @showScrollHint()
transfer: (e) =>
e.preventDefault()
chat_id = $(e.currentTarget).attr('data-chat-id')
App.WebSocket.send(event:'chat_transfer', chat_id: chat_id, session_id: @session.id)
@close()
ticketCreate: (e) => ticketCreate: (e) =>
e.preventDefault() e.preventDefault()

View file

@ -0,0 +1,40 @@
<div class="status-fields">
<% if @chats.length > 1: %>
<div class="buttonDropdown dropdown">
<div class="status-field status-field--spacer js-acceptChat">
<span class="badge js-badgeWaitingCustomers"></span> <%- @T('Waiting Customers') %>
</div>
<div class="status-field status-field--arrow" data-toggle="dropdown">
<%- @Icon('arrow-down') %>
</div>
<ul class="dropdown-menu" role="menu">
<% for chat in @chats: %>
<li class="js-waitingCustomers js-acceptChat" disabled data-chat-id="<%= chat.id %>" role="menuitem">
<span class="badge js-badgeWaitingCustomers"></span> <%- @T('Waiting in %s', chat.name) %> <span class="flex-spacer"></span>
<div class="status-badge js-info">
<div class="info-badge"><%- @Icon('info') %></div>
</div>
<% end %>
</ul>
</div>
<% else: %>
<div class="status-field js-acceptChat js-waitingCustomers">
<span class="badge js-badgeWaitingCustomers"></span> <%- @T('Waiting Customers') %>
<div class="status-badge js-totalInfo">
<div class="info-badge"><%- @Icon('info') %></div>
</div>
</div>
<% end %>
<div class="status-field js-chattingCustomers">
<span class="badge js-badgeChattingCustomers"></span> <%- @T('Chatting Customers') %>
<div class="status-badge js-info">
<div class="info-badge"><%- @Icon('info') %></div>
</div>
</div>
<div class="status-field js-activeAgents">
<span class="badge js-badgeActiveAgents"></span> <%- @T('Active Agents') %>
<div class="status-badge js-info">
<div class="info-badge"><%- @Icon('info') %></div>
</div>
</div>
</div>

View file

@ -24,6 +24,15 @@
<div class="chat-body js-body"></div> <div class="chat-body js-body"></div>
<div class="chat-body js-meta hidden"> <div class="chat-body js-meta hidden">
<% if @session: %> <% if @session: %>
<%- @T('Transfer conversation to another chat:') %>
<ul>
<% for chat in @chats: %>
<% if @session.chat_id isnt chat.id: %>
<li><a data-chat-id="<%= chat.id %>" class="js-transferChat" href="#"><%- chat.name %></a>
<% end %>
<% end %>
</ul>
<hr>
<ul> <ul>
<li><%- @T('Created at') %>: <%- @Ttimestamp(@session.created_at) %></li> <li><%- @T('Created at') %>: <%- @Ttimestamp(@session.created_at) %></li>
<% if @session && @session.preferences: %> <% if @session && @session.preferences: %>

View file

@ -3,28 +3,7 @@
<div class="page-header-title"> <div class="page-header-title">
<h1><%- @T('Customer Chat') %></h1> <h1><%- @T('Customer Chat') %></h1>
</div> </div>
<div class="page-header-center"> <div class="page-header-center js-header"></div>
<div class="status-fields">
<div class="status-field js-acceptChat js-waitingCustomers">
<span class="badge js-badgeWaitingCustomers"></span> <%- @T('Waiting Customers') %>
<div class="status-badge js-info">
<div class="info-badge"><%- @Icon('info') %></div>
</div>
</div>
<div class="status-field js-chattingCustomers">
<span class="badge js-badgeChattingCustomers"></span> <%- @T('Chatting Customers') %>
<div class="status-badge js-info">
<div class="info-badge"><%- @Icon('info') %></div>
</div>
</div>
<div class="status-field js-activeAgents">
<span class="badge js-badgeActiveAgents"></span> <%- @T('Active Agents') %>
<div class="status-badge js-info">
<div class="info-badge"><%- @Icon('info') %></div>
</div>
</div>
</div>
</div>
<div class="page-header-meta"> <div class="page-header-meta">
<div class="btn btn--action js-settings"><%- @T('Settings') %></div> <div class="btn btn--action js-settings"><%- @T('Settings') %></div>
</div> </div>

View file

@ -29,11 +29,11 @@
<%- @Icon('arrow-down', 'dropdown-arrow') %> <%- @Icon('arrow-down', 'dropdown-arrow') %>
</div> </div>
</div> </div>
<table class="settings-list"> <table class="settings-list settings-list--stretch">
<thead> <thead>
<tr> <tr>
<th><%- @T('Topic') %> <th><%- @T('Topic') %>
<th width="100%"><%- @T('Greeting') %> (<%- @T('Separate multiple values by ;') %>) <th><%- @T('Greeting') %> (<%- @T('Separate multiple values by ;') %>)
<th><%- @T('Enabled') %> <th><%- @T('Enabled') %>
</th></tr> </th></tr>
</thead> </thead>

View file

@ -889,6 +889,23 @@ pre code.hljs {
.status-fields { .status-fields {
display: flex; display: flex;
max-width: 100%;
.dropdown li {
display: flex;
align-items: center;
padding: 0 10px;
}
.dropdown-menu .status-badge {
margin-left: 10px;
}
.dropdown-menu .badge {
margin-top: -2px;
margin-right: 7px;
background: hsla(0,0%,0%,.5);
}
} }
.status-field { .status-field {
@ -896,10 +913,15 @@ pre code.hljs {
border: 1px solid hsl(0,0%,90%); border: 1px solid hsl(0,0%,90%);
display: flex; display: flex;
height: 34px; height: 34px;
flex-shrink: 1;
min-width: 20px;
padding: 5px 0;
align-items: center; align-items: center;
line-height: 35px; white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
&.is-clickable { &.is-active {
background: hsl(203,65%,55%); background: hsl(203,65%,55%);
color: white; color: white;
border-color: hsl(203,65%,45%); border-color: hsl(203,65%,45%);
@ -908,10 +930,6 @@ pre code.hljs {
@extend %clickable; @extend %clickable;
} }
&.is-blinking {
animation: pulsate 667ms ease-in-out infinite alternate;
}
&:not(:last-child):not(:only-child) { &:not(:last-child):not(:only-child) {
@include bidi-style(border-right-width, 0, border-left-width, 1px); @include bidi-style(border-right-width, 0, border-left-width, 1px);
} }
@ -919,6 +937,12 @@ pre code.hljs {
&:first-child { &:first-child {
border-radius: 5px 0 0 5px; border-radius: 5px 0 0 5px;
@include rtl(border-radius, 0 5px 5px 0); @include rtl(border-radius, 0 5px 5px 0);
.dropdown.open & {
border-radius: 5px 0 0 0;
@include rtl(border-radius, 0 5px 0 0);
}
} }
&:last-child { &:last-child {
@ -931,7 +955,7 @@ pre code.hljs {
} }
.badge { .badge {
@include bidi-style(margin, 0 5px 0 10px, margin, 0 10px 0 5px); @include bidi-style(margin, 0 7px 0 10px, margin, 0 10px 0 7px);
background: hsla(210,50%,10%,.24); background: hsla(210,50%,10%,.24);
} }
@ -942,6 +966,22 @@ pre code.hljs {
justify-content: center; justify-content: center;
align-self: stretch; align-self: stretch;
} }
&--arrow {
@extend %clickable;
border-left: none;
width: 34px;
justify-content: center;
.icon {
fill: currentColor;
}
}
&--spacer {
padding-left: 16px;
padding-right: 2px;
}
} }
.status-badge { .status-badge {
@ -2945,6 +2985,10 @@ ol.tabs li {
.tab { .tab {
flex: none; flex: none;
&:nth-last-child(2) {
@include bidi-style(border-right-width, 0, border-right-width, 1px);
}
} }
} }
@ -7386,6 +7430,12 @@ footer {
.dropdown-menu > li[disabled] { .dropdown-menu > li[disabled] {
opacity: 0.33; opacity: 0.33;
&:hover,
&.is-active {
background: none;
cursor: default;
}
} }
.dropdown-menu > li > a { .dropdown-menu > li > a {
@ -7404,7 +7454,7 @@ footer {
display: block; display: block;
} }
.dropdown-menu .badge { .dropdown-menu .badge--text {
@include bidi-style(padding-left, 10px, padding-right, 0); @include bidi-style(padding-left, 10px, padding-right, 0);
} }

View file

@ -201,6 +201,7 @@ returns
end end
running_chat_session_list_local = running_chat_session_list(chat_ids) running_chat_session_list_local = running_chat_session_list(chat_ids)
running_chat_session_list_local.each do |session| running_chat_session_list_local.each do |session|
next if !session['user_id'] next if !session['user_id']
@ -211,16 +212,18 @@ returns
end end
{ {
waiting_chat_count: waiting_chat_count(chat_ids), waiting_chat_count: waiting_chat_count(chat_ids),
waiting_chat_session_list: waiting_chat_session_list(chat_ids), waiting_chat_count_by_chat: waiting_chat_count_by_chat(chat_ids),
running_chat_count: running_chat_count(chat_ids), waiting_chat_session_list: waiting_chat_session_list(chat_ids),
running_chat_session_list: running_chat_session_list_local, waiting_chat_session_list_by_chat: waiting_chat_session_list_by_chat(chat_ids),
active_agent_count: active_agent_count(chat_ids), running_chat_count: running_chat_count(chat_ids),
active_agent_ids: active_agent_ids, running_chat_session_list: running_chat_session_list_local,
seads_available: seads_available(chat_ids), active_agent_count: active_agent_count(chat_ids),
seads_total: seads_total(chat_ids), active_agent_ids: active_agent_ids,
active: Chat::Agent.state(user_id), seads_available: seads_available(chat_ids),
assets: assets, seads_total: seads_total(chat_ids),
active: Chat::Agent.state(user_id),
assets: assets,
} }
end end
@ -305,6 +308,14 @@ returns
Chat::Session.where(state: ['waiting'], chat_id: chat_ids).count Chat::Session.where(state: ['waiting'], chat_id: chat_ids).count
end end
def self.waiting_chat_count_by_chat(chat_ids)
list = {}
Chat.where(active: true, id: chat_ids).pluck(:id).each do |chat_id|
list[chat_id] = Chat::Session.where(chat_id: chat_id, state: ['waiting']).count
end
list
end
def self.waiting_chat_session_list(chat_ids) def self.waiting_chat_session_list(chat_ids)
sessions = [] sessions = []
Chat::Session.where(state: ['waiting'], chat_id: chat_ids).each do |session| Chat::Session.where(state: ['waiting'], chat_id: chat_ids).each do |session|
@ -313,6 +324,17 @@ returns
sessions sessions
end end
def self.waiting_chat_session_list_by_chat(chat_ids)
sessions = {}
Chat.where(active: true, id: chat_ids).pluck(:id).each do |chat_id|
Chat::Session.where(chat_id: chat_id, state: ['waiting']).each do |session|
sessions[chat_id] ||= []
sessions[chat_id].push session.attributes
end
end
sessions
end
=begin =begin
get count running sessions in given chats get count running sessions in given chats

View file

@ -22,7 +22,11 @@ return is sent as message back to peer
# find first in waiting list # find first in waiting list
chat_user = User.lookup(id: @session['id']) chat_user = User.lookup(id: @session['id'])
chat_ids = Chat.agent_active_chat_ids(chat_user) chat_ids = Chat.agent_active_chat_ids(chat_user)
chat_session = Chat::Session.where(state: 'waiting', chat_id: chat_ids).order(created_at: :asc).first chat_session = if @payload['chat_id']
Chat::Session.where(state: 'waiting', chat_id: @payload['chat_id']).order(created_at: :asc).first
else
Chat::Session.where(state: 'waiting', chat_id: chat_ids).order(created_at: :asc).first
end
if !chat_session if !chat_session
return { return {
event: 'chat_session_start', event: 'chat_session_start',
@ -37,25 +41,32 @@ return is sent as message back to peer
chat_session.preferences[:participants] = chat_session.add_recipient(@client_id) chat_session.preferences[:participants] = chat_session.add_recipient(@client_id)
chat_session.save chat_session.save
# send chat_session_init to client session_attributes = chat_session.attributes
user = chat_session.agent_user session_attributes['messages'] = []
data = { Chat::Message.where(chat_session_id: chat_session.id).order(created_at: :asc).each do |message|
event: 'chat_session_start', session_attributes['messages'].push message.attributes
data: { end
state: 'ok',
agent: user, # send chat_session_init to customer client
session_id: chat_session.session_id, if session_attributes['messages'].blank?
chat_id: chat_session.chat_id, user = chat_session.agent_user
}, data = {
} event: 'chat_session_start',
# send to customer data: {
chat_session.send_to_recipients(data, @client_id) state: 'ok',
agent: user,
session_id: chat_session.session_id,
chat_id: chat_session.chat_id,
},
}
chat_session.send_to_recipients(data, @client_id)
end
# send to agent # send to agent
data = { data = {
event: 'chat_session_start', event: 'chat_session_start',
data: { data: {
session: chat_session.attributes, session: session_attributes,
}, },
} }
Sessions.send(@client_id, data) Sessions.send(@client_id, data)

View file

@ -0,0 +1,39 @@
class Sessions::Event::ChatTransfer < Sessions::Event::ChatBase
def run
return super if super
return if !permission_check('chat.agent', 'chat')
# find chat session
chat_session = Chat::Session.find_by(id: @payload['session_id'])
if !chat_session
return {
event: 'chat_session_start',
data: {
state: 'failed',
message: 'No session available.',
},
}
end
chat_ids_to_notify = [chat_session.chat_id, @payload['chat_id']]
chat_session.chat_id = @payload['chat_id']
chat_session.state = 'waiting'
chat_session.save
# send state update with sessions to agents
Chat.broadcast_agent_state_update(chat_ids_to_notify)
# send transfer message to client
message = {
event: 'chat_session_notice',
data: {
session_id: chat_session.session_id,
message: 'Conversation transfered into other chat. Please stay tuned.',
},
}
chat_session.send_to_recipients(message, @client_id)
nil
end
end

View file

@ -753,6 +753,8 @@ do($ = window.jQuery, window) ->
@onSessionClosed pipe.data @onSessionClosed pipe.data
when 'chat_session_left' when 'chat_session_left'
@onSessionClosed pipe.data @onSessionClosed pipe.data
when 'chat_session_notice'
@addStatus @T(pipe.data.message)
when 'chat_status_customer' when 'chat_status_customer'
switch pipe.data.state switch pipe.data.state
when 'online' when 'online'

View file

@ -1032,6 +1032,9 @@ var bind = function(fn, me){ return function(){ return fn.apply(me, arguments);
case 'chat_session_left': case 'chat_session_left':
this.onSessionClosed(pipe.data); this.onSessionClosed(pipe.data);
break; break;
case 'chat_session_notice':
this.addStatus(this.T(pipe.data.message));
break;
case 'chat_status_customer': case 'chat_status_customer':
switch (pipe.data.state) { switch (pipe.data.state) {
case 'online': case 'online':

File diff suppressed because one or more lines are too long

View file

@ -1858,42 +1858,40 @@ figcaption {
} }
--> -->
<script> <script>
var chat = new ZammadChat({ function getSearchParameters() {
chatId: 1, var prmstr = window.location.search.substr(1);
host: 'ws://localhost:6042', return prmstr != null && prmstr != '' ? transformToAssocArray(prmstr) : {};
cssUrl: 'http://localhost:5000/assets/chat/chat.css', }
debug: true function transformToAssocArray( prmstr ) {
}); var params = {};
var prmarr = prmstr.split('&');
for ( var i = 0; i < prmarr.length; i++) {
var tmparr = prmarr[i].split('=');
params[tmparr[0]] = tmparr[1];
}
return params;
}
var hostname = window.location.hostname;
var port = window.location.port;
var params = getSearchParameters();
var host = 'ws://'+ (location.host || 'localhost').split(':')[0] +':6042'
if (params['port']) {
host = 'ws://' + hostname + ':' + params['port']
}
cssUrl = 'http://' + hostname + ':' + port + '/assets/chat/chat.css'
$('.settings :input').on({ var chat = new ZammadChat({
change: function(){ chatId: 2,
switch($(this).attr('data-option')){ host: host,
case "flat": cssUrl: cssUrl,
$('.zammad-chat').toggleClass('zammad-chat--flat', this.checked); debug: true,
break; background: '#494d52',
case "color": flat: true,
setScssVariable('themeColor', this.value); idleTimeout: 1,
updateStyle(); idleTimeoutIntervallCheck: 0.5,
break; inactiveTimeout: 2,
case "borderRadius": inactiveTimeoutIntervallCheck: 0.5,
setScssVariable('borderRadius', this.value + "px"); waitingListTimeout: 1.2,
updateStyle(); waitingListTimeoutIntervallCheck: 0.5,
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> </script>

View file

@ -47,6 +47,12 @@ RSpec.describe Sessions::Event::ChatSessionStart do
session: { 'id' => agent.id }, session: { 'id' => agent.id },
) )
end end
let(:chat_message_history) do
Chat::Message.create!(
chat_session_id: chat_session.id,
content: 'some message',
)
end
before do before do
Setting.set('chat', true) Setting.set('chat', true)
@ -186,4 +192,37 @@ RSpec.describe Sessions::Event::ChatSessionStart do
end end
end end
context 'when starting a chat session as agent with transfered conversation' do
it 'send out chat_session_start to customer and agent with already created messages' do
chat_message_history
expect(subject_as_agent.run).to eq(nil)
messages_to_customer = Sessions.queue('customer_session_id')
expect(messages_to_customer.count).to eq(0)
messages_to_agent = Sessions.queue(client_id)
expect(messages_to_agent.count).to eq(1)
expect(messages_to_agent[0]).to include(
'event' => 'chat_session_start',
'data' => hash_including(
'session' => hash_including(
'user_id' => agent.id,
'state' => 'running',
'preferences' => hash_including(
'participants' => ['customer_session_id', client_id]
),
'messages' => array_including(
hash_including(
'content' => 'some message',
),
),
'id' => chat_session.id,
'chat_id' => chat_session.chat_id,
'session_id' => chat_session.session_id,
'name' => nil,
)
)
)
end
end
end end

View file

@ -0,0 +1,84 @@
require 'rails_helper'
RSpec.describe Sessions::Event::ChatTransfer do
let(:client_id) { rand(123_456_789) }
let(:chat) { Chat.first }
let(:chat_transfer_into) { Chat.create!(name: 'chat 2', updated_by_id: 1, created_by_id: 1) }
let(:chat_session) do
Sessions.create('customer_session_id', { 'id' => customer.id }, {})
Sessions.queue('customer_session_id')
Chat::Session.create(
chat_id: chat.id,
user_id: nil,
preferences: { participants: ['customer_session_id'] },
state: 'running',
)
end
let!(:agent) do
create(:agent_user, preferences: { chat: { active: { chat.id.to_s => 'on' } } })
end
let!(:customer) { create(:customer_user) }
let(:subject_as_agent) do
Sessions.create(client_id, { 'id' => agent.id }, {})
Sessions.queue(client_id)
described_class.new(
payload: { 'data' => { 'session_id' => chat_session.session_id }, 'session_id' => chat_session.id, 'chat_id' => chat_transfer_into.id },
user_id: agent.id,
client_id: client_id,
clients: {},
session: { 'id' => agent.id },
)
end
before do
Setting.set('chat', true)
end
context 'when transfering a chat session as customer' do
let(:subject_as_customer) do
Sessions.create(client_id, { 'id' => customer.id }, {})
Sessions.queue(client_id)
described_class.new(
payload: { 'data' => { 'session_id' => chat_session.session_id }, 'chat_id' => chat_transfer_into.id },
user_id: customer.id,
client_id: client_id,
clients: {},
session: { 'id' => customer.id },
)
end
context 'without chat.agent permissions' do
it 'send out no_permission event to user' do
expect(subject_as_customer.run).to eq(nil)
messages = Sessions.queue(client_id)
expect(messages.count).to eq(1)
expect(messages).to eq([
'event' => 'chat_error',
'data' => {
'state' => 'no_permission'
}
])
end
end
end
context 'when transfering a chat session as agent' do
it 'send out chat_session_notice to customer and agent and set chat session to waiting' do
expect(subject_as_agent.run).to eq(nil)
messages_to_customer = Sessions.queue('customer_session_id')
expect(messages_to_customer.count).to eq(1)
expect(messages_to_customer[0]).to eq(
'event' => 'chat_session_notice',
'data' => {
'message' => 'Conversation transfered into other chat. Please stay tuned.',
'session_id' => chat_session.session_id,
},
)
messages_to_agent = Sessions.queue(client_id)
expect(messages_to_agent.count).to eq(0)
end
end
end