Added browser tests for customer chat.

This commit is contained in:
Martin Edenhofer 2015-12-05 20:41:14 +01:00
parent 0cb6e34870
commit 7831f1c1f8
18 changed files with 1418 additions and 566 deletions

View file

@ -24,7 +24,7 @@ class App.ChannelChat extends App.Controller
'.js-code': 'code'
'.js-palette': 'palette'
'.js-color': 'colorField'
'.js-chatSetting': 'chatSetting'
'.js-chatSetting input': 'chatSetting'
apiOptions: [
{
@ -279,6 +279,8 @@ class App.ChannelChat extends App.Controller
setting.state_current = { value: value }
setting.save()
@Config.set('chat', value)
delay = -> App.Event.trigger('ui:rerender')
@delay(delay, 200)
updateParams: =>
quote = (value) ->

View file

@ -7,7 +7,7 @@ class App.ChannelForm extends App.Controller
elements:
'.js-paramsBlock': 'paramsBlock'
'.js-formSetting': 'formSetting'
'.js-formSetting input': 'formSetting'
constructor: ->
super

View file

@ -11,14 +11,6 @@ class App.CustomerChat extends App.Controller
constructor: ->
super
# access check
if !@isRole('Chat')
@renderScreenUnauthorized(objectName: 'Chat')
return
if !@Config.get('chat')
@renderScreenError(detail: 'Feature disabled!')
return
@chatWindows = {}
@maxChatWindows = 4
preferences = @Session.get('preferences')
@ -49,7 +41,6 @@ class App.CustomerChat extends App.Controller
# add new chat window
@bind('chat_session_start', (data) =>
console.log('chat_session_start', data)
if data.session
@addChat(data.session)
)
@ -78,6 +69,13 @@ class App.CustomerChat extends App.Controller
)
render: ->
if !@isRole('Chat')
@renderScreenUnauthorized(objectName: 'Chat')
return
if !@Config.get('chat')
@renderScreenError(detail: 'Feature disabled!')
return
@html App.view('customer_chat/index')()
show: (params) =>

View file

@ -82,7 +82,7 @@ class App.Navigation extends App.ControllerWidgetPermanent
)
# bind on switch changes and execute it on controller
@$('.js-menu .js-switch').bind('change', (e) ->
@$('.js-menu .js-switch input').bind('change', (e) ->
val = $(e.target).prop('checked')
key = $(e.target).closest('.menu-item').data('key')
return if !key

View file

@ -8,8 +8,8 @@
<h2><%- @T('Enable') %>/<%- @T('Disable') %></h2>
<form>
<div class="zammad-switch">
<input name="chat" type="checkbox" id="setting-chat" class="js-chatSetting" <% if @chatSetting: %>checked<% end %>>
<div class="zammad-switch js-chatSetting">
<input name="chat" type="checkbox" id="setting-chat" <% if @chatSetting: %>checked<% end %>>
<label for="setting-chat"></label>
</div>
</form>

View file

@ -8,8 +8,8 @@
<h2><%- @T('Enable') %>/<%- @T('Disable') %></h2>
<form>
<div class="zammad-switch">
<input name="form_ticket_create" type="checkbox" id="setting-form" class="js-formSetting" <% if @formSetting: %>checked<% end %>>
<div class="zammad-switch js-formSetting">
<input name="form_ticket_create" type="checkbox" id="setting-form" <% if @formSetting: %>checked<% end %>>
<label for="setting-form"></label>
</div>
</form>

View file

@ -30,8 +30,8 @@
<span class="counter badge badge--big"><%= item.counter %></span>
<% end %>
<% if item.switch isnt undefined: %>
<span class="zammad-switch zammad-switch--dark zammad-switch--small zammad-switch--green">
<input type="checkbox" id="<%- item.class %>-switch" class="js-switch" <% if item.switch: %>checked<% end %>>
<span class="zammad-switch zammad-switch--dark zammad-switch--small zammad-switch--green js-switch">
<input type="checkbox" id="<%- item.class %>-switch" <% if item.switch: %>checked<% end %>>
<label for="<%- item.class %>-switch"></label>
</span>
<% end %>

View file

@ -18,6 +18,18 @@ class Chat::Session < ApplicationModel
preferences[:participants]
end
def recipients_active?
return true if !preferences
return true if !preferences[:participants]
count = 0
preferences[:participants].each {|client_id|
next if !Sessions.session_exists?(client_id)
count += 1
}
return true if count >= 2
false
end
def send_to_recipients(message, ignore_client_id = nil)
preferences[:participants].each {|local_client_id|
next if local_client_id == ignore_client_id

View file

@ -0,0 +1,36 @@
# encoding: utf-8
class Observer::Chat::Leave::BackgroundJob
def initialize(chat_session_id, client_id, session)
@chat_session_id = chat_session_id
@client_id = client_id
@session = session
end
def perform
# check if customer has permanently left the conversation
chat_session = Chat::Session.find_by(id: @chat_session_id)
return if !chat_session
return if chat_session.recipients_active?
chat_session.state = 'closed'
chat_session.save
realname = 'Anonymous'
if @session && @session['id']
realname = User.lookup(id: @session['id']).fullname
end
# notifiy participients
message = {
event: 'chat_session_left',
data: {
realname: realname,
session_id: chat_session.session_id,
},
}
chat_session.send_to_recipients(message, @client_id)
end
end

View file

@ -16,7 +16,11 @@ class Setting < ApplicationModel
@@current = {} # rubocop:disable Style/ClassVars
@@change_id = nil # rubocop:disable Style/ClassVars
@@lookup_at = nil # rubocop:disable Style/ClassVars
@@lookup_timeout = 2.minutes # rubocop:disable Style/ClassVars
if ENV['ZAMMAD_SETTING_TTL']
@@lookup_timeout = ENV['ZAMMAD_SETTING_TTL'].to_i # rubocop:disable Style/ClassVars
else
@@lookup_timeout = 2.minutes # rubocop:disable Style/ClassVars
end
=begin

View file

@ -5,8 +5,135 @@ do($ = window.jQuery, window) ->
scriptHost = myScript.src.match('.*://([^:/]*).*')[1]
# Define the plugin class
class ZammadChat
class Base
defaults:
debug: false
constructor: (options) ->
@options = $.extend {}, @defaults, options
@log = new Log(debug: @options.debug, logPrefix: @options.logPrefix || @logPrefix)
class Log
defaults:
debug: false
constructor: (options) ->
@options = $.extend {}, @defaults, options
debug: (items...) =>
return if !@options.debug && level is 'debug'
@log('debug', items)
notice: (items...) =>
@log('notice', items)
error: (items...) =>
@log('error', items)
return if !@options.debug && level is 'debug'
items.unshift(level)
console.log.apply console, string
log: (level, items) =>
items.unshift('||')
items.unshift(level)
items.unshift(@options.logPrefix)
console.log.apply console, items
return if !@options.debug
logString = ''
for item in items
logString += ' '
if typeof item is 'object'
logString += JSON.stringify(item)
else if item && item.toString
logString += item.toString()
else
logString += item
$('.js-chatLogDisplay').prepend('<div>' + logString + '</div>')
class Timeout extends Base
timeoutStartedAt: null
logPrefix: 'timeout'
defaults:
debug: false
timeout: 4
timeoutIntervallCheck: 0.5
constructor: (options) ->
super(options)
start: =>
@stop()
timeoutStartedAt = new Date
check = =>
timeLeft = new Date - new Date(timeoutStartedAt.getTime() + @options.timeout * 1000 * 60)
@log.debug "Timeout check for #{@options.timeout} minutes (left #{timeLeft/1000} sec.)"#, new Date
return if timeLeft < 0
@stop()
@options.callback()
@log.debug "Start timeout in #{@options.timeout} minutes"#, new Date
@intervallId = setInterval(check, @options.timeoutIntervallCheck * 1000 * 60)
stop: =>
return if !@intervallId
@log.debug "Stop timeout of #{@options.timeout} minutes at"#, new Date
clearInterval(@intervallId)
class Io extends Base
logPrefix: 'io'
constructor: (options) ->
super(options)
set: (params) =>
for key, value of params
@options[key] = value
detectHost: ->
protocol = 'ws://'
if window.location.protocol is 'https:'
protocol = 'wss://'
@options.host = "#{ protocol }#{ scriptHost }/ws"
connect: =>
@detectHost() if !@options.host
@log.debug "Connecting to #{@options.host}"
@ws = new window.WebSocket("#{@options.host}")
@ws.onopen = (e) =>
@log.debug 'on open', e
@options.onOpen(e)
@ws.onmessage = (e) =>
pipes = JSON.parse(e.data)
@log.debug 'on message', e.data
if @options.onMessage
@options.onMessage(pipes)
@ws.onclose = (e) =>
@log.debug 'close websocket connection'
if @options.onClose
@options.onClose(e)
@ws.onerror = (e) =>
@log.debug 'onerror', e
if @options.onError
@options.onError(e)
close: =>
@ws.close()
reconnect: =>
@ws.close()
@connect()
send: (event, data = {}) =>
@log.debug 'send', event, data
msg = JSON.stringify
event: event
data: data
@ws.send msg
class ZammadChat extends Base
defaults:
chatId: undefined
show: true
@ -21,9 +148,14 @@ do($ = window.jQuery, window) ->
buttonClass: 'open-zammad-chat'
inactiveClass: 'is-inactive'
title: '<strong>Chat</strong> with us!'
idleTimeout: 4
inactiveTimeout: 20
idleTimeout: 8
idleTimeoutIntervallCheck: 0.5
inactiveTimeout: 8
inactiveTimeoutIntervallCheck: 0.5
waitingListTimeout: 4
waitingListTimeoutIntervallCheck: 0.5
logPrefix: 'chat'
_messageCount: 0
isOpen: true
blinkOnlineInterval: null
@ -35,7 +167,6 @@ do($ = window.jQuery, window) ->
isTyping: false
state: 'offline'
initialQueueDelay: 10000
wsReconnectEnable: true
translations:
de:
'<strong>Chat</strong> with us!': '<strong>Chat</strong> mit uns!'
@ -57,22 +188,17 @@ do($ = window.jQuery, window) ->
T: (string, items...) =>
if @options.lang && @options.lang isnt 'en'
if !@translations[@options.lang]
@log 'notice', "Translation '#{@options.lang}' needed!"
@log.notice "Translation '#{@options.lang}' needed!"
else
translations = @translations[@options.lang]
if !translations[string]
@log 'notice', "Translation needed for '#{string}'"
@log.notice "Translation needed for '#{string}'"
string = translations[string] || string
if items
for item in items
string = string.replace(/%s/, item)
string
log: (level, string...) =>
return if !@options.debug && level is 'debug'
string.unshift(level)
console.log.apply console, string
view: (name) =>
return (options) =>
if !options
@ -86,19 +212,20 @@ do($ = window.jQuery, window) ->
constructor: (options) ->
@options = $.extend {}, @defaults, options
super(@options)
# check prerequisites
if !$
@state = 'unsupported'
@log 'notice', 'Chat: no jquery found!'
@log.notice 'Chat: no jquery found!'
return
if !window.WebSocket or !sessionStorage
@state = 'unsupported'
@log 'notice', 'Chat: Browser not supported!'
@log.notice 'Chat: Browser not supported!'
return
if !@options.chatId
@state = 'unsupported'
@log 'error', 'Chat: need chatId as option!'
@log.error 'Chat: need chatId as option!'
return
# detect language
@ -106,8 +233,19 @@ do($ = window.jQuery, window) ->
@options.lang = $('html').attr('lang')
if @options.lang
@options.lang = @options.lang.replace(/-.+?$/, '') # replace "-xx" of xx-xx
@log 'debug', "lang: #{@options.lang}"
@log.debug "lang: #{@options.lang}"
@loadCss()
@io = new Io(@options)
@io.set(
onOpen: @render
onClose: @hide
onMessage: @onWebSocketMessage
)
@io.connect()
render: =>
@el = $(@view('chat')(
title: @options.title
))
@ -124,10 +262,20 @@ do($ = window.jQuery, window) ->
@input.on
keydown: @checkForEnter
input: @onInput
$(window).on('beforeunload', =>
@onLeaveTemporary()
)
@setAgentOnlineState 'online'
@wsConnect()
@log.debug 'widget rendered'
@loadCss()
@startTimeoutObservers()
@idleTimeout.start()
# get current chat status
@sessionId = sessionStorage.getItem('sessionId')
@send 'chat_status_customer',
session_id: @sessionId
checkForEnter: (event) =>
if not event.shiftKey and event.keyCode is 13
@ -136,22 +284,16 @@ do($ = window.jQuery, window) ->
send: (event, data = {}) =>
data.chat_id = @options.chatId
@log 'debug', 'ws:send', event, data
pipe = JSON.stringify
event: event
data: data
@ws.send pipe
onWebSocketMessage: (e) =>
pipes = JSON.parse( e.data )
@io.send(event, data)
onWebSocketMessage: (pipes) =>
for pipe in pipes
@log 'debug', 'ws:onmessage', pipe
@log.debug 'ws:onmessage', pipe
switch pipe.event
when 'chat_error'
@log 'notice', pipe.data
@log.notice pipe.data
if pipe.data && pipe.data.state is 'chat_disabled'
@wsClose()
@destroy(hide: true)
when 'chat_session_message'
return if pipe.data.self_written
@receiveMessage pipe.data
@ -171,38 +313,35 @@ do($ = window.jQuery, window) ->
when 'online'
@sessionId = undefined
@onReady()
@log 'debug', 'Zammad Chat: ready'
when 'offline'
@onError 'Zammad Chat: No agent online'
@state = 'off'
@hide()
@wsClose()
@destroy(hide: true)
when 'chat_disabled'
@onError 'Zammad Chat: Chat is disabled'
@state = 'off'
@hide()
@wsClose()
@destroy(hide: true)
when 'no_seats_available'
@onError "Zammad Chat: Too many clients in queue. Clients in queue: #{pipe.data.queue}"
@state = 'off'
@hide()
@wsClose()
@destroy(hide: true)
when 'reconnect'
@log 'debug', 'old messages', pipe.data.session
@log.debug 'old messages', pipe.data.session
@reopenSession pipe.data
onReady: =>
@log.debug 'widget ready for use'
$(".#{ @options.buttonClass }").click(@open).removeClass(@inactiveClass)
if @options.show
@show()
onError: (message) =>
@log 'debug', message
@log.debug message
$(".#{ @options.buttonClass }").hide()
reopenSession: (data) =>
@inactiveTimeoutStart()
@inactiveTimeout.start()
unfinishedMessage = sessionStorage.getItem 'unfinished_message'
@ -246,7 +385,7 @@ do($ = window.jQuery, window) ->
@isTyping = new Date()
@send 'chat_session_typing',
session_id: @sessionId
@inactiveTimeoutStart()
@inactiveTimeout.start()
onSubmit: (event) =>
event.preventDefault()
@ -256,7 +395,7 @@ do($ = window.jQuery, window) ->
message = @input.val()
return if !message
@inactiveTimeoutStart()
@inactiveTimeout.start()
sessionStorage.removeItem 'unfinished_message'
@ -286,7 +425,7 @@ do($ = window.jQuery, window) ->
session_id: @sessionId
receiveMessage: (data) =>
@inactiveTimeoutStart()
@inactiveTimeout.start()
# hide writing indicator
@onAgentTypingEnd()
@ -305,6 +444,7 @@ do($ = window.jQuery, window) ->
@scrollToBottom()
open: =>
@log.debug 'open widget'
if @isOpen
@show()
@ -322,12 +462,14 @@ do($ = window.jQuery, window) ->
@isOpen = true
if !@sessionId
@sessionInit()
@send('chat_session_init')
onOpenAnimationEnd: =>
@idleTimeoutStop()
@idleTimeout.stop()
close: (event) =>
@log.debug 'close widget'
return @state if @state is 'off' or @state is 'unsupported'
event.stopPropagation() if event
@ -339,7 +481,8 @@ do($ = window.jQuery, window) ->
session_id: @sessionId
# stop timer
@inactiveTimeoutStop()
@inactiveTimeout.stop()
@waitingListTimeout.stop()
# delete input store
sessionStorage.removeItem 'unfinished_message'
@ -360,14 +503,20 @@ do($ = window.jQuery, window) ->
onCloseAnimationEnd: =>
@el.removeClass('zammad-chat-is-visible')
@disconnect()
@showLoader()
@el.find('.zammad-chat-welcome').removeClass('zammad-chat-is-hidden')
@el.find('.zammad-chat-agent').addClass('zammad-chat-is-hidden')
@el.find('.zammad-chat-agent-status').addClass('zammad-chat-is-hidden')
@isOpen = false
# restart connection
@onWebSocketOpen()
@io.reconnect()
hide: ->
@el.removeClass('zammad-chat-is-shown')
if @el
@el.removeClass('zammad-chat-is-shown')
show: ->
return @state if @state is 'off' or @state is 'unsupported'
@ -397,6 +546,9 @@ do($ = window.jQuery, window) ->
# delay initial queue position, show connecting first
show = =>
@onQueue data
console.log('onQueueScreen')
@waitingListTimeout.start()
if @initialQueueDelay && !@onInitialQueueDelayId
@onInitialQueueDelayId = setTimeout(show, @initialQueueDelay)
return
@ -409,7 +561,7 @@ do($ = window.jQuery, window) ->
show()
onQueue: (data) =>
@log 'notice', 'onQueue', data.position
@log.notice 'onQueue', data.position
@inQueue = true
@el.find('.zammad-chat-body').html @view('waiting')
@ -432,6 +584,11 @@ do($ = window.jQuery, window) ->
onAgentTypingEnd: =>
@el.find('.zammad-chat-message--typing').remove()
onLeaveTemporary: =>
return if !@sessionId
@send 'chat_session_leave_temporary',
session_id: @sessionId
maybeAddTimestamp: ->
timestamp = Date.now()
@ -470,59 +627,38 @@ do($ = window.jQuery, window) ->
scrollToBottom: ->
@el.find('.zammad-chat-body').scrollTop($('.zammad-chat-body').prop('scrollHeight'))
sessionInit: ->
@send('chat_session_init')
detectHost: ->
protocol = 'ws://'
if window.location.protocol is 'https:'
protocol = 'wss://'
@options.host = "#{ protocol }#{ scriptHost }/ws"
wsConnect: =>
@detectHost() if !@options.host
destroy: (params = {}) =>
@log.debug 'destroy widget'
console.log('el', @el)
if params.hide
if @el
@el.remove()
@wsReconnectStop()
@io.close()
@log 'debug', "Connecting to #{@options.host}"
@ws = new window.WebSocket("#{@options.host}")
@ws.onopen = @onWebSocketOpen
@ws.onmessage = @onWebSocketMessage
@ws.onclose = (e) =>
@log 'debug', 'close websocket connection'
if @wsReconnectEnable
@reconnect()
@ws.onerror = (e) =>
@log 'debug', 'ws:onerror', e
wsClose: =>
@wsReconnectEnable = false
@ws.close()
wsReconnect: =>
wsReconnectStart: =>
@wsReconnectStop()
if @reconnectDelayId
clearTimeout(@reconnectDelayId)
@reconnectDelayId = setTimeout(@wsConnect, 5000)
@reconnectDelayId = setTimeout(@io.connect(), 5000)
onWebSocketOpen: =>
@idleTimeoutStart()
@sessionId = sessionStorage.getItem('sessionId')
@log 'debug', 'ws connected'
@send 'chat_status_customer',
session_id: @sessionId
@setAgentOnlineState 'online'
wsReconnectStop: =>
if @reconnectDelayId
clearTimeout(@reconnectDelayId)
reconnect: =>
# set status to connecting
@log 'notice', 'reconnecting'
@log.notice 'reconnecting'
@disableInput()
@lastAddedType = 'status'
@setAgentOnlineState 'connecting'
@addStatus @T('Connection lost')
@wsReconnect()
onConnectionReestablished: =>
# set status back to online
@ -534,13 +670,7 @@ do($ = window.jQuery, window) ->
@addStatus @T('Chat closed by %s', data.realname)
@disableInput()
@setAgentOnlineState 'offline'
@inactiveTimeoutStop()
disconnect: ->
@showLoader()
@el.find('.zammad-chat-welcome').removeClass('zammad-chat-is-hidden')
@el.find('.zammad-chat-agent').addClass('zammad-chat-is-hidden')
@el.find('.zammad-chat-agent-status').addClass('zammad-chat-is-hidden')
@inactiveTimeout.stop()
setSessionId: (id) =>
@sessionId = id
@ -573,8 +703,12 @@ do($ = window.jQuery, window) ->
@setAgentOnlineState 'online'
showTimeout: ->
@el.find('.zammad-chat-body').html @view('timeout')
@waitingListTimeout.stop()
@idleTimeout.stop()
@inactiveTimeout.start()
showCustomerTimeout: ->
@el.find('.zammad-chat-body').html @view('customer_timeout')
agent: @agent.name
delay: @options.inactiveTimeout
@close()
@ -582,6 +716,14 @@ do($ = window.jQuery, window) ->
location.reload()
@el.find('.js-restart').click reload
showWaitingListTimeout: ->
@el.find('.zammad-chat-body').html @view('waiting_list_timeout')
delay: @options.watingListTimeout
@close()
reload = ->
location.reload()
@el.find('.js-restart').click reload
showLoader: ->
@el.find('.zammad-chat-body').html @view('loader')()
@ -603,42 +745,47 @@ do($ = window.jQuery, window) ->
.replace(/\/ws/i, '')
url += '/assets/chat/chat.css'
@log 'debug', "load css from '#{url}'"
@log.debug "load css from '#{url}'"
styles = "@import url('#{url}');"
newSS = document.createElement('link')
newSS.rel = 'stylesheet'
newSS.href = 'data:text/css,' + escape(styles)
document.getElementsByTagName('head')[0].appendChild(newSS)
inactiveTimeoutStart: =>
@inactiveTimeoutStop()
delay = =>
@log 'debug', "Inactive timeout of #{@options.inactiveTimeout} minutes, show timeout screen.", new Date
@state = 'off'
@setAgentOnlineState 'offline'
@showTimeout()
@wsClose()
@log 'debug', "Start inactive timeout in #{@options.inactiveTimeout} minutes", new Date
@inactiveTimeoutStopDelayId = setTimeout(delay, @options.inactiveTimeout * 1000 * 60)
inactiveTimeoutStop: =>
return if !@inactiveTimeoutStopDelayId
@log 'debug', "Stop inactive timeout of #{@options.inactiveTimeout} minutes at", new Date
clearTimeout(@inactiveTimeoutStopDelayId)
idleTimeoutStart: =>
@idleTimeoutStop()
delay = =>
@log 'debug', "Idle timeout of #{@options.idleTimeout} minutes, hide widget", new Date
@state = 'off'
@hide()
@wsClose()
@log 'debug', "Start idle timeout in #{@options.idleTimeout} minutes", new Date
@idleTimeoutStopDelayId = setTimeout(delay, @options.idleTimeout * 1000 * 60)
idleTimeoutStop: =>
return if !@idleTimeoutStopDelayId
@log 'debug', "Stop idle timeout of #{@options.idleTimeout} minutes at", new Date
clearTimeout(@idleTimeoutStopDelayId)
startTimeoutObservers: =>
@idleTimeout = new Timeout(
logPrefix: 'idleTimeout'
debug: @options.debug
timeout: @options.idleTimeout
timeoutIntervallCheck: @options.idleTimeoutIntervallCheck
callback: =>
@log.debug 'Idle timeout reached, hide widget', new Date
@state = 'off'
@destroy(hide: true)
)
@inactiveTimeout = new Timeout(
logPrefix: 'inactiveTimeout'
debug: @options.debug
timeout: @options.inactiveTimeout
timeoutIntervallCheck: @options.inactiveTimeoutIntervallCheck
callback: =>
@log.debug 'Inactive timeout reached, show timeout screen.', new Date
@state = 'off'
@setAgentOnlineState 'offline'
@showCustomerTimeout()
@destroy(hide:false)
)
@waitingListTimeout = new Timeout(
logPrefix: 'waitingListTimeout'
debug: @options.debug
timeout: @options.waitingListTimeout
timeoutIntervallCheck: @options.waitingListTimeoutIntervallCheck
callback: =>
@log.debug 'Waiting list timeout reached, show timeout screen.', new Date
@state = 'off'
@setAgentOnlineState 'offline'
@showWaitingListTimeout()
@destroy(hide:false)
)
window.ZammadChat = ZammadChat

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,7 @@
<div class="zammad-chat-modal">
<div class="zammad-chat-modal-text">
<%- @T('We are sorry, it takes longer as expected to get an empty slot. Please try again later or send us an email. Thank you!') %>
<br>
<div class="zammad-chat-button js-restart"<%= " style='background: #{ @background }'" if @background %>><%- @T('Start new conversation') %></div>
</div>
</div>

View file

@ -25,6 +25,7 @@
padding: 10px;
border-radius: 5px;
box-shadow: 0 3px 10px rgba(0,0,0,.3);
width: 500px;
}
.settings input {
@ -39,6 +40,12 @@
padding-right: 0;
}
table td.log {
text-align: left;
padding-right: 0;
word-break: break-all;
}
td {
padding: 5px;
}
@ -73,8 +80,8 @@
<div class="settings">
<table>
<tr>
<td>
<td><h2>Settings</h2>
<td>
<tr>
<td>
<input id="flat" type="checkbox" data-option="flat">
@ -100,6 +107,11 @@
<tr>
<td>
<td><button class="open-zammad-chat">Open Chat</button>
<tr>
<td class="log"><h2>Log</h2>
<td>
<tr>
<td colspan="2" class="log js-chatLogDisplay">
</table>
</div>
@ -113,7 +125,13 @@
debug: true,
background: '#494d52',
flat: true,
shown: false
shown: false,
idleTimeout: 1,
idleTimeoutIntervallCheck: 0.5,
inactiveTimeout: 2,
inactiveTimeoutIntervallCheck: 0.5,
waitingListTimeout: 1.2,
waitingListTimeoutIntervallCheck: 0.5,
});
$('.settings :input').on({

View file

@ -257,6 +257,7 @@ EventMachine.run {
}
elsif data['event']
log 'notice', "execute event '#{data['event']}'", client_id
message = Sessions::Event.run(data['event'], data, @clients[client_id][:session], client_id)
if message
websocket_send(client_id, message)

View file

@ -1,128 +1,428 @@
# encoding: utf-8
# rubocop:disable all
require 'browser_test_helper'
class ChatTest < TestCase
def test_websocket
return # TODO: temp disable
message = 'message 1äöüß ' + rand(99_999_999_999_999_999).to_s
tests = [
{
name: 'start',
instance1: browser_instance,
instance2: browser_instance,
instance1_username: 'master@example.com',
instance1_password: 'test',
instance2_username: 'agent1@example.com',
instance2_password: 'test',
url: browser_url,
action: [
{
where: :instance1,
execute: 'check',
css: '#login',
result: false,
},
{
where: :instance2,
execute: 'check',
css: '#login',
result: false,
},
{
execute: 'wait',
value: 1,
},
{
where: :instance1,
execute: 'click',
css: '#chat_toogle',
},
{
execute: 'wait',
value: 8,
},
{
where: :instance1,
execute: 'click',
css: '#chat_toogle',
},
{
execute: 'wait',
value: 4,
},
{
where: :instance2,
execute: 'click',
css: '#chat_toogle',
},
{
where: :instance1,
execute: 'click',
css: '#chat_toogle',
},
{
execute: 'wait',
value: 2,
},
{
where: :instance1,
execute: 'set',
css: 'input[name="chat_message"]',
value: message,
},
{
where: :instance1,
execute: 'send_key',
css: 'input[name="chat_message"]',
value: :enter,
},
{
execute: 'wait',
value: 6,
},
{
where: :instance1,
execute: 'match',
css: '#chat_log_container',
value: message,
match_result: true,
},
{
where: :instance2,
execute: 'match',
css: '#chat_log_container',
value: message,
match_result: true,
},
{
where: :instance1,
execute: 'navigate',
to: browser_url,
},
{
execute: 'wait',
value: 8,
},
{
where: :instance1,
execute: 'click',
css: '#chat_toogle',
},
{
execute: 'wait',
value: 8,
},
{
where: :instance1,
execute: 'match',
css: '#chat_log_container',
value: message,
match_result: true,
},
],
},
]
browser_double_test(tests)
def test_basic
agent = browser_instance
login(
browser: agent,
username: 'master@example.com',
password: 'test',
url: browser_url,
)
tasks_close_all(
browser: agent,
)
# disable chat
click(
browser: agent,
css: 'a[href="#manage"]',
)
click(
browser: agent,
css: 'a[href="#channels/chat"]',
)
switch(
browser: agent,
css: '#content .js-chatSetting',
type: 'off',
)
sleep 25 # wait for rerendering
click(
browser: agent,
css: 'a[href="#customer_chat"]',
)
match(
browser: agent,
css: '.active.content',
value: 'disabled',
)
customer = browser_instance
location(
browser: customer,
url: "#{browser_url}/assets/chat/znuny.html",
)
sleep 4
exists_not(
browser: customer,
css: '.zammad-chat',
)
match(
browser: customer,
css: '.settings',
value: '{"state":"chat_disabled"}',
)
click(
browser: agent,
css: 'a[href="#manage"]',
)
click(
browser: agent,
css: 'a[href="#channels/chat"]',
)
switch(
browser: agent,
css: '#content .js-chatSetting',
type: 'on',
)
sleep 15 # wait for rerendering
switch(
browser: agent,
css: '#navigation .js-switch',
type: 'off',
)
click(
browser: agent,
css: 'a[href="#customer_chat"]',
wait: 2,
)
match_not(
browser: agent,
css: '.active.content',
value: 'disabled',
)
reload(
browser: customer,
)
sleep 4
exists_not(
browser: customer,
css: '.zammad-chat',
)
match_not(
browser: customer,
css: '.settings',
value: '{"state":"chat_disabled"}',
)
match(
browser: customer,
css: '.settings',
value: '{"event":"chat_status_customer","data":{"state":"offline"}}',
)
click(
browser: agent,
css: 'a[href="#customer_chat"]',
)
switch(
browser: agent,
css: '#navigation .js-switch',
type: 'on',
)
reload(
browser: customer,
)
watch_for(
browser: customer,
css: '.zammad-chat',
timeout: 5,
)
match_not(
browser: customer,
css: '.settings',
value: '{"state":"chat_disabled"}',
)
match_not(
browser: customer,
css: '.settings',
value: '{"event":"chat_status_customer","data":{"state":"offline"}}',
)
match(
browser: customer,
css: '.settings',
value: '"data":{"state":"online"}',
)
# init chat
click(
browser: customer,
css: '.js-chat-open',
)
exists(
browser: customer,
css: '.zammad-chat-is-shown',
)
watch_for(
browser: customer,
css: '.zammad-chat',
value: '(waiting|warte)',
)
watch_for(
browser: agent,
css: '.js-chatMenuItem .counter',
value: '1',
)
click(
browser: customer,
css: '.js-chat-close',
)
watch_for_disappear(
browser: customer,
css: '.zammad-chat',
value: '(waiting|warte)',
)
watch_for_disappear(
browser: agent,
css: '.js-chatMenuItem .counter',
)
end
def test_basic_usecase1
agent = browser_instance
login(
browser: agent,
username: 'master@example.com',
password: 'test',
url: browser_url,
)
tasks_close_all(
browser: agent,
)
click(
browser: agent,
css: 'a[href="#customer_chat"]',
)
agent.find_elements( { css: '.active .chat-window .js-close' } ).each(&:click)
customer = browser_instance
location(
browser: customer,
url: "#{browser_url}/assets/chat/znuny.html",
)
watch_for(
browser: customer,
css: '.zammad-chat',
timeout: 5,
)
click(
browser: customer,
css: '.js-chat-open',
)
exists(
browser: customer,
css: '.zammad-chat-is-shown',
)
watch_for(
browser: customer,
css: '.zammad-chat',
value: '(waiting|warte)',
)
click(
browser: agent,
css: '.active .js-acceptChat',
)
sleep 2
exists_not(
browser: agent,
css: '.active .chat-window .chat-status.is-modified',
)
set(
browser: agent,
css: '.active .chat-window .js-customerChatInput',
value: 'my name is me',
)
click(
browser: agent,
css: '.active .chat-window .js-send',
)
watch_for(
browser: customer,
css: '.zammad-chat .zammad-chat-agent-status',
value: 'online',
)
watch_for(
browser: customer,
css: '.zammad-chat',
value: 'my name is me',
)
set(
browser: customer,
css: '.zammad-chat .zammad-chat-input',
value: 'my name is customer',
)
click(
browser: customer,
css: '.zammad-chat .zammad-chat-send',
)
watch_for(
browser: agent,
css: '.active .chat-window',
value: 'my name is customer',
)
exists(
browser: agent,
css: '.active .chat-window .chat-status.is-modified',
)
click(
browser: agent,
css: '.active .chat-window .js-customerChatInput',
)
exists_not(
browser: agent,
css: '.active .chat-window .chat-status.is-modified',
)
click(
browser: customer,
css: '.js-chat-close',
)
watch_for(
browser: agent,
css: '.active .chat-window',
value: 'has left the conversation',
)
end
def test_basic_usecase2
agent = browser_instance
login(
browser: agent,
username: 'master@example.com',
password: 'test',
url: browser_url,
)
tasks_close_all(
browser: agent,
)
click(
browser: agent,
css: 'a[href="#customer_chat"]',
)
agent.find_elements( { css: '.active .chat-window .js-close' } ).each(&:click)
customer = browser_instance
location(
browser: customer,
url: "#{browser_url}/assets/chat/znuny.html",
)
watch_for(
browser: customer,
css: '.zammad-chat',
timeout: 5,
)
click(
browser: customer,
css: '.js-chat-open',
)
exists(
browser: customer,
css: '.zammad-chat-is-shown',
)
watch_for(
browser: customer,
css: '.zammad-chat',
value: '(waiting|warte)',
)
click(
browser: agent,
css: '.active .js-acceptChat',
)
sleep 2
exists_not(
browser: agent,
css: '.active .chat-window .chat-status.is-modified',
)
watch_for(
browser: customer,
css: '.zammad-chat .zammad-chat-agent-status',
value: 'online',
)
set(
browser: customer,
css: '.zammad-chat .zammad-chat-input',
value: 'my name is customer',
)
click(
browser: customer,
css: '.zammad-chat .zammad-chat-send',
)
watch_for(
browser: agent,
css: '.active .chat-window',
value: 'my name is customer',
)
exists(
browser: agent,
css: '.active .chat-window .chat-status.is-modified',
)
set(
browser: agent,
css: '.active .chat-window .js-customerChatInput',
value: 'my name is me',
)
exists_not(
browser: agent,
css: '.active .chat-window .chat-status.is-modified',
)
click(
browser: agent,
css: '.active .chat-window .js-send',
)
watch_for(
browser: customer,
css: '.zammad-chat',
value: 'my name is me',
)
click(
browser: agent,
css: '.active .chat-window .js-close',
)
watch_for(
browser: customer,
css: '.zammad-chat .zammad-chat-agent-status',
value: 'offline',
)
watch_for(
browser: customer,
css: '.zammad-chat',
value: 'Chat closed by',
)
end
def test_timeouts
customer = browser_instance
location(
browser: customer,
url: "#{browser_url}/assets/chat/znuny.html",
)
watch_for(
browser: customer,
css: '.zammad-chat',
timeout: 5,
)
watch_for_disappear(
browser: customer,
css: '.zammad-chat',
timeout: 75,
)
reload(
browser: customer,
)
exists(
browser: customer,
css: '.zammad-chat',
)
click(
browser: customer,
css: '.js-chat-open',
)
watch_for(
browser: customer,
css: '.zammad-chat',
value: '(waiting|warte)',
timeout: 35,
)
watch_for(
browser: customer,
css: '.zammad-chat',
value: '(takes longer|dauert länger)',
timeout: 90,
)
end
end