From c747c0c1f14cee544bb78f4cb1de71a3096b586b Mon Sep 17 00:00:00 2001 From: Martin Edenhofer Date: Wed, 27 Apr 2016 13:37:13 +0200 Subject: [PATCH] Improved CTI features. --- .../javascripts/app/controllers/cti.coffee | 77 +++++++++++++--- .../javascripts/app/views/cti/index.jst.eco | 10 +- .../integration/sipgate_controller.rb | 21 +++++ config/routes/integration_sipgate.rb | 5 +- db/migrate/20160427000001_update_cti_log.rb | 7 ++ test/integration/sipgate_controller_test.rb | 91 ++++++++++++++++++- 6 files changed, 191 insertions(+), 20 deletions(-) create mode 100644 db/migrate/20160427000001_update_cti_log.rb diff --git a/app/assets/javascripts/app/controllers/cti.coffee b/app/assets/javascripts/app/controllers/cti.coffee index 08b09ec27..cd8ebf3a4 100644 --- a/app/assets/javascripts/app/controllers/cti.coffee +++ b/app/assets/javascripts/app/controllers/cti.coffee @@ -1,9 +1,17 @@ class App.CTI extends App.Controller + events: + 'click .js-check': 'done' + constructor: -> super + return if !@isRole('CTI') + + @list = [] @meta = active: false + counter: 0 + state: {} preferences = @Session.get('preferences') || {} @meta.active = preferences.cti || false @@ -14,9 +22,20 @@ class App.CTI extends App.Controller 'cti_event' (data) => console.log('cti_event', data) - if data.state is 'newCall' - console.log('notify') - @notify(data) + if data.direction is 'in' + if data.state is 'newCall' + 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' ) App.Event.bind( @@ -26,6 +45,11 @@ class App.CTI extends App.Controller @render() 'cti_list_push' ) + App.Event.bind( + 'auth' + (data) => + @meta.counter = 0 + ) # fetch data, render view load: -> @@ -39,14 +63,11 @@ class App.CTI extends App.Controller ) notify: (data) -> - console.log(data) - #return if ! - if data.state is 'newCall' && data.direction is 'in' - App.Event.trigger 'notify', { - type: 'notice' - msg: App.i18n.translateContent('Call from %s for %s', data.from, data.to) - timeout: 2500 - } + App.Event.trigger 'notify', { + type: 'notice' + msg: App.i18n.translateContent('Call from %s for %s', data.from, data.to) + timeout: 12000 + } featureActive: => return true @@ -59,18 +80,48 @@ class App.CTI extends App.Controller @renderScreenUnauthorized(objectName: 'CTI') 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')( list: @list ) @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) => @title 'CTI', true @navupdate '#cti' - counter: -> - counter = 0 + counter: => + @meta.counter switch: (state = undefined) => diff --git a/app/assets/javascripts/app/views/cti/index.jst.eco b/app/assets/javascripts/app/views/cti/index.jst.eco index 7aba99b57..313bb3b05 100644 --- a/app/assets/javascripts/app/views/cti/index.jst.eco +++ b/app/assets/javascripts/app/views/cti/index.jst.eco @@ -5,20 +5,24 @@ + + <% for item in @list: %> - - - + class="is-inactive"<% end %> data-id="<%- item.id %>"> + + + <% end %> diff --git a/app/controllers/integration/sipgate_controller.rb b/app/controllers/integration/sipgate_controller.rb index 0cbf6f518..6e2e9d51e 100644 --- a/app/controllers/integration/sipgate_controller.rb +++ b/app/controllers/integration/sipgate_controller.rb @@ -12,6 +12,16 @@ class Integration::SipgateController < ApplicationController render json: list 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 def in http_log_config facility: 'sipgate.io' @@ -143,12 +153,23 @@ class Integration::SipgateController < ApplicationController log = Cti::Log.find_by(call_id: params['callId']) raise "No such call_id #{params['callId']}" if !log log.state = 'answer' + log.start = Time.zone.now + if user + log.to_comment = user + end log.comment = comment log.save elsif params['event'] == 'hangup' log = Cti::Log.find_by(call_id: params['callId']) 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.end = Time.zone.now log.comment = comment log.save else diff --git a/config/routes/integration_sipgate.rb b/config/routes/integration_sipgate.rb index d569898d2..f6594554b 100644 --- a/config/routes/integration_sipgate.rb +++ b/config/routes/integration_sipgate.rb @@ -1,7 +1,8 @@ Zammad::Application.routes.draw do 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/sipgate/out', to: 'integration/sipgate#out', via: :post + match '/api/v1/cti/done/:id', to: 'integration/sipgate#done', 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 diff --git a/db/migrate/20160427000001_update_cti_log.rb b/db/migrate/20160427000001_update_cti_log.rb new file mode 100644 index 000000000..85c3d5eb9 --- /dev/null +++ b/db/migrate/20160427000001_update_cti_log.rb @@ -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 diff --git a/test/integration/sipgate_controller_test.rb b/test/integration/sipgate_controller_test.rb index 98fb66ecc..a48fdeb0f 100644 --- a/test/integration/sipgate_controller_test.rb +++ b/test/integration/sipgate_controller_test.rb @@ -68,7 +68,7 @@ class SipgateControllerTest < ActionDispatch::IntegrationTest ) groups = Group.where(name: 'Users') - roles = Role.where(name: 'Agent') + roles = Role.where(name: %w(Agent CTI)) agent = User.create_or_update( login: 'cti-agent@example.com', firstname: 'E', @@ -225,6 +225,7 @@ class SipgateControllerTest < ActionDispatch::IntegrationTest assert_equal('user 1', log.from_comment) assert_equal(nil, log.comment) assert_equal('newCall', log.state) + assert_equal(true, log.done) # outbound - I - hangup by agent 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('cancel', log.comment) assert_equal('hangup', log.state) + assert_equal(true, log.done) # outbound - II - new call 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(nil, log.comment) assert_equal('newCall', log.state) + assert_equal(true, log.done) # outbound - II - answer by customer 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(nil, log.comment) assert_equal('answer', log.state) + assert_equal(true, log.done) # outbound - II - hangup by customer 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('normalClearing', log.comment) assert_equal('hangup', log.state) + assert_equal(true, log.done) # inbound - I - new call 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(nil, log.comment) assert_equal('newCall', log.state) + assert_equal(true, log.done) # inbound - I - answer by customer 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(nil, log.comment) assert_equal('answer', log.state) + assert_equal(true, log.done) # inbound - I - hangup by customer 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('normalClearing', log.comment) 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' assert_response(401) @@ -326,7 +405,15 @@ class SipgateControllerTest < ActionDispatch::IntegrationTest assert_response(200) result = JSON.parse(@response.body) 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
<%- @T('From') %> <%- @T('To') %> <%- @T('State') %> <%- @T('Comment') %><%- @T('Duration') %> <%- @T('Time') %>
<%= item.from %><% if item.from_comment: %> (<%= item.from_comment %>)<% end %><%= item.to %><% if item.to_comment: %> (<%= item.to_comment %>)<% end %>
<% if item.state is 'hangup': %>checked<% end %>><% end %> + <% if item.from_comment: %><%= item.from_comment %>
<% end %><%= item.from %>
<% if item.to_comment: %><%= item.to_comment %>
<% end %><%= item.to %>
<%= item.state %> <%= item.comment %><%= item.duration %> <%- @humanTime(item.created_at) %>