Improved CTI features.
This commit is contained in:
parent
a746f559cd
commit
c747c0c1f1
6 changed files with 191 additions and 20 deletions
|
@ -1,9 +1,17 @@
|
||||||
class App.CTI extends App.Controller
|
class App.CTI extends App.Controller
|
||||||
|
events:
|
||||||
|
'click .js-check': 'done'
|
||||||
|
|
||||||
constructor: ->
|
constructor: ->
|
||||||
super
|
super
|
||||||
|
|
||||||
|
return if !@isRole('CTI')
|
||||||
|
|
||||||
|
@list = []
|
||||||
@meta =
|
@meta =
|
||||||
active: false
|
active: false
|
||||||
|
counter: 0
|
||||||
|
state: {}
|
||||||
|
|
||||||
preferences = @Session.get('preferences') || {}
|
preferences = @Session.get('preferences') || {}
|
||||||
@meta.active = preferences.cti || false
|
@meta.active = preferences.cti || false
|
||||||
|
@ -14,9 +22,20 @@ class App.CTI extends App.Controller
|
||||||
'cti_event'
|
'cti_event'
|
||||||
(data) =>
|
(data) =>
|
||||||
console.log('cti_event', data)
|
console.log('cti_event', data)
|
||||||
if data.state is 'newCall'
|
if data.direction is 'in'
|
||||||
console.log('notify')
|
if data.state is 'newCall'
|
||||||
@notify(data)
|
if @switch()
|
||||||
|
@notify(data)
|
||||||
|
return if @meta.state[data.id]
|
||||||
|
@meta.state[data.id] = true
|
||||||
|
@meta.counter += 1
|
||||||
|
@updateNavMenu()
|
||||||
|
if data.state is 'answer' || data.state is 'hangup'
|
||||||
|
return if !@meta.state[data.id]
|
||||||
|
delete @meta.state[data.id]
|
||||||
|
@meta.counter -= 1
|
||||||
|
@updateNavMenu()
|
||||||
|
|
||||||
'cti_event'
|
'cti_event'
|
||||||
)
|
)
|
||||||
App.Event.bind(
|
App.Event.bind(
|
||||||
|
@ -26,6 +45,11 @@ class App.CTI extends App.Controller
|
||||||
@render()
|
@render()
|
||||||
'cti_list_push'
|
'cti_list_push'
|
||||||
)
|
)
|
||||||
|
App.Event.bind(
|
||||||
|
'auth'
|
||||||
|
(data) =>
|
||||||
|
@meta.counter = 0
|
||||||
|
)
|
||||||
|
|
||||||
# fetch data, render view
|
# fetch data, render view
|
||||||
load: ->
|
load: ->
|
||||||
|
@ -39,14 +63,11 @@ class App.CTI extends App.Controller
|
||||||
)
|
)
|
||||||
|
|
||||||
notify: (data) ->
|
notify: (data) ->
|
||||||
console.log(data)
|
App.Event.trigger 'notify', {
|
||||||
#return if !
|
type: 'notice'
|
||||||
if data.state is 'newCall' && data.direction is 'in'
|
msg: App.i18n.translateContent('Call from %s for %s', data.from, data.to)
|
||||||
App.Event.trigger 'notify', {
|
timeout: 12000
|
||||||
type: 'notice'
|
}
|
||||||
msg: App.i18n.translateContent('Call from %s for %s', data.from, data.to)
|
|
||||||
timeout: 2500
|
|
||||||
}
|
|
||||||
|
|
||||||
featureActive: =>
|
featureActive: =>
|
||||||
return true
|
return true
|
||||||
|
@ -59,18 +80,48 @@ class App.CTI extends App.Controller
|
||||||
@renderScreenUnauthorized(objectName: 'CTI')
|
@renderScreenUnauthorized(objectName: 'CTI')
|
||||||
return
|
return
|
||||||
|
|
||||||
|
format = (time) ->
|
||||||
|
# Minutes and seconds
|
||||||
|
mins = ~~(time / 60)
|
||||||
|
secs = time % 60
|
||||||
|
|
||||||
|
# Hours, minutes and seconds
|
||||||
|
hrs = ~~(time / 3600)
|
||||||
|
mins = ~~((time % 3600) / 60)
|
||||||
|
secs = time % 60
|
||||||
|
|
||||||
|
# Output like "1:01" or "4:03:59" or "123:03:59"
|
||||||
|
mins = "0#{mins}" if mins < 10
|
||||||
|
secs = "0#{secs}" if secs < 10
|
||||||
|
if hrs > 0
|
||||||
|
return "#{hrs}:#{mins}:#{secs}"
|
||||||
|
"#{mins}:#{secs}"
|
||||||
|
|
||||||
|
for item in @list
|
||||||
|
if item.start && item.end
|
||||||
|
item.duration = format((Date.parse(item.end) - Date.parse(item.start))/1000)
|
||||||
@html App.view('cti/index')(
|
@html App.view('cti/index')(
|
||||||
list: @list
|
list: @list
|
||||||
)
|
)
|
||||||
|
|
||||||
@updateNavMenu()
|
@updateNavMenu()
|
||||||
|
|
||||||
|
done: (e) =>
|
||||||
|
element = $(e.currentTarget)
|
||||||
|
id = element.closest('tr').data('id')
|
||||||
|
done = element.prop('checked')
|
||||||
|
@ajax(
|
||||||
|
type: 'POST'
|
||||||
|
url: "#{@apiPath}/cti/done/#{id}"
|
||||||
|
data: JSON.stringify(done: done)
|
||||||
|
)
|
||||||
|
|
||||||
show: (params) =>
|
show: (params) =>
|
||||||
@title 'CTI', true
|
@title 'CTI', true
|
||||||
@navupdate '#cti'
|
@navupdate '#cti'
|
||||||
|
|
||||||
counter: ->
|
counter: =>
|
||||||
counter = 0
|
@meta.counter
|
||||||
|
|
||||||
switch: (state = undefined) =>
|
switch: (state = undefined) =>
|
||||||
|
|
||||||
|
|
|
@ -5,20 +5,24 @@
|
||||||
<table class="table table-striped table-hover">
|
<table class="table table-striped table-hover">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
|
<th style="width: 28px;"></th>
|
||||||
<th><%- @T('From') %></th>
|
<th><%- @T('From') %></th>
|
||||||
<th><%- @T('To') %></th>
|
<th><%- @T('To') %></th>
|
||||||
<th><%- @T('State') %></th>
|
<th><%- @T('State') %></th>
|
||||||
<th><%- @T('Comment') %></th>
|
<th><%- @T('Comment') %></th>
|
||||||
|
<th><%- @T('Duration') %></th>
|
||||||
<th><%- @T('Time') %></th>
|
<th><%- @T('Time') %></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<% for item in @list: %>
|
<% for item in @list: %>
|
||||||
<tr>
|
<tr <% if item.state is 'hangup' && item.done: %>class="is-inactive"<% end %> data-id="<%- item.id %>">
|
||||||
<td><%= item.from %><% if item.from_comment: %> (<%= item.from_comment %>)<% end %></td>
|
<td><% if item.state is 'hangup': %><input type="checkbox" class="js-check" <% if item.done: %>checked<% end %>><% end %>
|
||||||
<td><%= item.to %><% if item.to_comment: %> (<%= item.to_comment %>)<% end %></td>
|
<td><% if item.from_comment: %><%= item.from_comment %><br><% end %><%= item.from %></td>
|
||||||
|
<td><% if item.to_comment: %><%= item.to_comment %><br><% end %><%= item.to %></td>
|
||||||
<td><%= item.state %></td>
|
<td><%= item.state %></td>
|
||||||
<td><%= item.comment %></td>
|
<td><%= item.comment %></td>
|
||||||
|
<td><%= item.duration %></td>
|
||||||
<td><%- @humanTime(item.created_at) %></td>
|
<td><%- @humanTime(item.created_at) %></td>
|
||||||
</tr>
|
</tr>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
|
@ -12,6 +12,16 @@ class Integration::SipgateController < ApplicationController
|
||||||
render json: list
|
render json: list
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# set caller log to done
|
||||||
|
def done
|
||||||
|
return if !authentication_check
|
||||||
|
return if deny_if_not_role('CTI')
|
||||||
|
log = Cti::Log.find(params['id'])
|
||||||
|
log.done = params['done']
|
||||||
|
log.save
|
||||||
|
render json: {}
|
||||||
|
end
|
||||||
|
|
||||||
# notify about inbound call / block inbound call
|
# notify about inbound call / block inbound call
|
||||||
def in
|
def in
|
||||||
http_log_config facility: 'sipgate.io'
|
http_log_config facility: 'sipgate.io'
|
||||||
|
@ -143,12 +153,23 @@ class Integration::SipgateController < ApplicationController
|
||||||
log = Cti::Log.find_by(call_id: params['callId'])
|
log = Cti::Log.find_by(call_id: params['callId'])
|
||||||
raise "No such call_id #{params['callId']}" if !log
|
raise "No such call_id #{params['callId']}" if !log
|
||||||
log.state = 'answer'
|
log.state = 'answer'
|
||||||
|
log.start = Time.zone.now
|
||||||
|
if user
|
||||||
|
log.to_comment = user
|
||||||
|
end
|
||||||
log.comment = comment
|
log.comment = comment
|
||||||
log.save
|
log.save
|
||||||
elsif params['event'] == 'hangup'
|
elsif params['event'] == 'hangup'
|
||||||
log = Cti::Log.find_by(call_id: params['callId'])
|
log = Cti::Log.find_by(call_id: params['callId'])
|
||||||
raise "No such call_id #{params['callId']}" if !log
|
raise "No such call_id #{params['callId']}" if !log
|
||||||
|
if params['direction'] == 'in' && log.state == 'newCall'
|
||||||
|
log.done = false
|
||||||
|
end
|
||||||
|
if params['direction'] == 'in' && log.to_comment == 'voicemail'
|
||||||
|
log.done = false
|
||||||
|
end
|
||||||
log.state = 'hangup'
|
log.state = 'hangup'
|
||||||
|
log.end = Time.zone.now
|
||||||
log.comment = comment
|
log.comment = comment
|
||||||
log.save
|
log.save
|
||||||
else
|
else
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
Zammad::Application.routes.draw do
|
Zammad::Application.routes.draw do
|
||||||
|
|
||||||
match '/api/v1/cti/log', to: 'integration/sipgate#index', via: :get
|
match '/api/v1/cti/log', to: 'integration/sipgate#index', via: :get
|
||||||
match '/api/v1/sipgate/in', to: 'integration/sipgate#in', via: :post
|
match '/api/v1/cti/done/:id', to: 'integration/sipgate#done', via: :post
|
||||||
match '/api/v1/sipgate/out', to: 'integration/sipgate#out', via: :post
|
match '/api/v1/sipgate/in', to: 'integration/sipgate#in', via: :post
|
||||||
|
match '/api/v1/sipgate/out', to: 'integration/sipgate#out', via: :post
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
7
db/migrate/20160427000001_update_cti_log.rb
Normal file
7
db/migrate/20160427000001_update_cti_log.rb
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
class UpdateCtiLog < ActiveRecord::Migration
|
||||||
|
def up
|
||||||
|
add_column :cti_logs, :start, :timestamp, null: true
|
||||||
|
add_column :cti_logs, :end, :timestamp, null: true
|
||||||
|
add_column :cti_logs, :done, :boolean, null: false, default: true
|
||||||
|
end
|
||||||
|
end
|
|
@ -68,7 +68,7 @@ class SipgateControllerTest < ActionDispatch::IntegrationTest
|
||||||
)
|
)
|
||||||
|
|
||||||
groups = Group.where(name: 'Users')
|
groups = Group.where(name: 'Users')
|
||||||
roles = Role.where(name: 'Agent')
|
roles = Role.where(name: %w(Agent CTI))
|
||||||
agent = User.create_or_update(
|
agent = User.create_or_update(
|
||||||
login: 'cti-agent@example.com',
|
login: 'cti-agent@example.com',
|
||||||
firstname: 'E',
|
firstname: 'E',
|
||||||
|
@ -225,6 +225,7 @@ class SipgateControllerTest < ActionDispatch::IntegrationTest
|
||||||
assert_equal('user 1', log.from_comment)
|
assert_equal('user 1', log.from_comment)
|
||||||
assert_equal(nil, log.comment)
|
assert_equal(nil, log.comment)
|
||||||
assert_equal('newCall', log.state)
|
assert_equal('newCall', log.state)
|
||||||
|
assert_equal(true, log.done)
|
||||||
|
|
||||||
# outbound - I - hangup by agent
|
# outbound - I - hangup by agent
|
||||||
params = 'event=hangup&direction=out&callId=1234567890-1&cause=cancel'
|
params = 'event=hangup&direction=out&callId=1234567890-1&cause=cancel'
|
||||||
|
@ -238,6 +239,7 @@ class SipgateControllerTest < ActionDispatch::IntegrationTest
|
||||||
assert_equal('user 1', log.from_comment)
|
assert_equal('user 1', log.from_comment)
|
||||||
assert_equal('cancel', log.comment)
|
assert_equal('cancel', log.comment)
|
||||||
assert_equal('hangup', log.state)
|
assert_equal('hangup', log.state)
|
||||||
|
assert_equal(true, log.done)
|
||||||
|
|
||||||
# outbound - II - new call
|
# outbound - II - new call
|
||||||
params = 'event=newCall&direction=out&from=4930600000000&to=4912347114711&callId=1234567890-2&user%5B%5D=user+1'
|
params = 'event=newCall&direction=out&from=4930600000000&to=4912347114711&callId=1234567890-2&user%5B%5D=user+1'
|
||||||
|
@ -251,6 +253,7 @@ class SipgateControllerTest < ActionDispatch::IntegrationTest
|
||||||
assert_equal('user 1', log.from_comment)
|
assert_equal('user 1', log.from_comment)
|
||||||
assert_equal(nil, log.comment)
|
assert_equal(nil, log.comment)
|
||||||
assert_equal('newCall', log.state)
|
assert_equal('newCall', log.state)
|
||||||
|
assert_equal(true, log.done)
|
||||||
|
|
||||||
# outbound - II - answer by customer
|
# outbound - II - answer by customer
|
||||||
params = 'event=answer&direction=out&callId=1234567890-2&from=4930600000000&to=4912347114711'
|
params = 'event=answer&direction=out&callId=1234567890-2&from=4930600000000&to=4912347114711'
|
||||||
|
@ -264,6 +267,7 @@ class SipgateControllerTest < ActionDispatch::IntegrationTest
|
||||||
assert_equal('user 1', log.from_comment)
|
assert_equal('user 1', log.from_comment)
|
||||||
assert_equal(nil, log.comment)
|
assert_equal(nil, log.comment)
|
||||||
assert_equal('answer', log.state)
|
assert_equal('answer', log.state)
|
||||||
|
assert_equal(true, log.done)
|
||||||
|
|
||||||
# outbound - II - hangup by customer
|
# outbound - II - hangup by customer
|
||||||
params = 'event=hangup&direction=out&callId=1234567890-2&cause=normalClearing&from=4930600000000&to=4912347114711'
|
params = 'event=hangup&direction=out&callId=1234567890-2&cause=normalClearing&from=4930600000000&to=4912347114711'
|
||||||
|
@ -277,6 +281,7 @@ class SipgateControllerTest < ActionDispatch::IntegrationTest
|
||||||
assert_equal('user 1', log.from_comment)
|
assert_equal('user 1', log.from_comment)
|
||||||
assert_equal('normalClearing', log.comment)
|
assert_equal('normalClearing', log.comment)
|
||||||
assert_equal('hangup', log.state)
|
assert_equal('hangup', log.state)
|
||||||
|
assert_equal(true, log.done)
|
||||||
|
|
||||||
# inbound - I - new call
|
# inbound - I - new call
|
||||||
params = 'event=newCall&direction=in&to=4930600000000&from=4912347114711&callId=1234567890-3&user%5B%5D=user+1'
|
params = 'event=newCall&direction=in&to=4930600000000&from=4912347114711&callId=1234567890-3&user%5B%5D=user+1'
|
||||||
|
@ -290,6 +295,7 @@ class SipgateControllerTest < ActionDispatch::IntegrationTest
|
||||||
assert_equal('user 1', log.to_comment)
|
assert_equal('user 1', log.to_comment)
|
||||||
assert_equal(nil, log.comment)
|
assert_equal(nil, log.comment)
|
||||||
assert_equal('newCall', log.state)
|
assert_equal('newCall', log.state)
|
||||||
|
assert_equal(true, log.done)
|
||||||
|
|
||||||
# inbound - I - answer by customer
|
# inbound - I - answer by customer
|
||||||
params = 'event=answer&direction=in&callId=1234567890-3&to=4930600000000&from=4912347114711'
|
params = 'event=answer&direction=in&callId=1234567890-3&to=4930600000000&from=4912347114711'
|
||||||
|
@ -303,6 +309,7 @@ class SipgateControllerTest < ActionDispatch::IntegrationTest
|
||||||
assert_equal('user 1', log.to_comment)
|
assert_equal('user 1', log.to_comment)
|
||||||
assert_equal(nil, log.comment)
|
assert_equal(nil, log.comment)
|
||||||
assert_equal('answer', log.state)
|
assert_equal('answer', log.state)
|
||||||
|
assert_equal(true, log.done)
|
||||||
|
|
||||||
# inbound - I - hangup by customer
|
# inbound - I - hangup by customer
|
||||||
params = 'event=hangup&direction=in&callId=1234567890-3&cause=normalClearing&to=4930600000000&from=4912347114711'
|
params = 'event=hangup&direction=in&callId=1234567890-3&cause=normalClearing&to=4930600000000&from=4912347114711'
|
||||||
|
@ -316,7 +323,79 @@ class SipgateControllerTest < ActionDispatch::IntegrationTest
|
||||||
assert_equal('user 1', log.to_comment)
|
assert_equal('user 1', log.to_comment)
|
||||||
assert_equal('normalClearing', log.comment)
|
assert_equal('normalClearing', log.comment)
|
||||||
assert_equal('hangup', log.state)
|
assert_equal('hangup', log.state)
|
||||||
|
assert_equal(true, log.done)
|
||||||
|
|
||||||
|
# inbound - II - new call
|
||||||
|
params = 'event=newCall&direction=in&to=4930600000000&from=4912347114711&callId=1234567890-4&user%5B%5D=user+1,user+2'
|
||||||
|
post '/api/v1/sipgate/in', params
|
||||||
|
assert_response(200)
|
||||||
|
log = Cti::Log.find_by(call_id: '1234567890-4')
|
||||||
|
assert(log)
|
||||||
|
assert_equal('4930600000000', log.to)
|
||||||
|
assert_equal('4912347114711', log.from)
|
||||||
|
assert_equal('in', log.direction)
|
||||||
|
assert_equal('user 1,user 2', log.to_comment)
|
||||||
|
assert_equal(nil, log.comment)
|
||||||
|
assert_equal('newCall', log.state)
|
||||||
|
assert_equal(true, log.done)
|
||||||
|
|
||||||
|
# inbound - II - answer by voicemail
|
||||||
|
params = 'event=answer&direction=in&callId=1234567890-4&to=4930600000000&from=4912347114711&user=voicemail'
|
||||||
|
post '/api/v1/sipgate/in', params
|
||||||
|
assert_response(200)
|
||||||
|
log = Cti::Log.find_by(call_id: '1234567890-4')
|
||||||
|
assert(log)
|
||||||
|
assert_equal('4930600000000', log.to)
|
||||||
|
assert_equal('4912347114711', log.from)
|
||||||
|
assert_equal('in', log.direction)
|
||||||
|
assert_equal('voicemail', log.to_comment)
|
||||||
|
assert_equal(nil, log.comment)
|
||||||
|
assert_equal('answer', log.state)
|
||||||
|
assert_equal(true, log.done)
|
||||||
|
|
||||||
|
# inbound - II - hangup by customer
|
||||||
|
params = 'event=hangup&direction=in&callId=1234567890-4&cause=normalClearing&to=4930600000000&from=4912347114711'
|
||||||
|
post '/api/v1/sipgate/in', params
|
||||||
|
assert_response(200)
|
||||||
|
log = Cti::Log.find_by(call_id: '1234567890-4')
|
||||||
|
assert(log)
|
||||||
|
assert_equal('4930600000000', log.to)
|
||||||
|
assert_equal('4912347114711', log.from)
|
||||||
|
assert_equal('in', log.direction)
|
||||||
|
assert_equal('voicemail', log.to_comment)
|
||||||
|
assert_equal('normalClearing', log.comment)
|
||||||
|
assert_equal('hangup', log.state)
|
||||||
|
assert_equal(false, log.done)
|
||||||
|
|
||||||
|
# inbound - III - new call
|
||||||
|
params = 'event=newCall&direction=in&to=4930600000000&from=4912347114711&callId=1234567890-5&user%5B%5D=user+1,user+2'
|
||||||
|
post '/api/v1/sipgate/in', params
|
||||||
|
assert_response(200)
|
||||||
|
log = Cti::Log.find_by(call_id: '1234567890-5')
|
||||||
|
assert(log)
|
||||||
|
assert_equal('4930600000000', log.to)
|
||||||
|
assert_equal('4912347114711', log.from)
|
||||||
|
assert_equal('in', log.direction)
|
||||||
|
assert_equal('user 1,user 2', log.to_comment)
|
||||||
|
assert_equal(nil, log.comment)
|
||||||
|
assert_equal('newCall', log.state)
|
||||||
|
assert_equal(true, log.done)
|
||||||
|
|
||||||
|
# inbound - III - hangup by customer
|
||||||
|
params = 'event=hangup&direction=in&callId=1234567890-5&cause=normalClearing&to=4930600000000&from=4912347114711'
|
||||||
|
post '/api/v1/sipgate/in', params
|
||||||
|
assert_response(200)
|
||||||
|
log = Cti::Log.find_by(call_id: '1234567890-5')
|
||||||
|
assert(log)
|
||||||
|
assert_equal('4930600000000', log.to)
|
||||||
|
assert_equal('4912347114711', log.from)
|
||||||
|
assert_equal('in', log.direction)
|
||||||
|
assert_equal('user 1,user 2', log.to_comment)
|
||||||
|
assert_equal('normalClearing', log.comment)
|
||||||
|
assert_equal('hangup', log.state)
|
||||||
|
assert_equal(false, log.done)
|
||||||
|
|
||||||
|
# get caller list
|
||||||
get '/api/v1/cti/log'
|
get '/api/v1/cti/log'
|
||||||
assert_response(401)
|
assert_response(401)
|
||||||
|
|
||||||
|
@ -326,7 +405,15 @@ class SipgateControllerTest < ActionDispatch::IntegrationTest
|
||||||
assert_response(200)
|
assert_response(200)
|
||||||
result = JSON.parse(@response.body)
|
result = JSON.parse(@response.body)
|
||||||
assert_equal(result.class, Array)
|
assert_equal(result.class, Array)
|
||||||
assert_equal(3, result.count)
|
assert_equal(5, result.count)
|
||||||
|
assert_equal('hangup', result[1]['state'])
|
||||||
|
assert_equal('4930777000000', result[1]['from'])
|
||||||
|
assert_equal('user 1', result[1]['from_comment'])
|
||||||
|
assert_equal('4912347114711', result[1]['to'])
|
||||||
|
assert_equal(nil, result[1]['to_comment'])
|
||||||
|
assert_equal('1234567890-2', result[1]['call_id'])
|
||||||
|
assert_equal('normalClearing', result[1]['comment'])
|
||||||
|
assert_equal('hangup', result[1]['state'])
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue