From 86c2df69ad83840c4a4930541ecd8bcff1badb72 Mon Sep 17 00:00:00 2001 From: Martin Edenhofer Date: Thu, 18 Oct 2018 11:42:37 +0200 Subject: [PATCH] Fixed issue #2292 - CTI Log: duration_talking_time is incorrect and after update initialized_at is a string (not timestamp) --- .../javascripts/app/controllers/cti.coffee | 19 +----- app/assets/javascripts/app/index.coffee | 17 +++++ .../app/views/cti/caller_log.jst.eco | 8 ++- app/models/cti/log.rb | 60 ++++++++++++++--- db/migrate/20181017000001_cti_generic_api2.rb | 39 +++++++++++ spec/requests/integration/cti_spec.rb | 65 +++++++++++++++++-- 6 files changed, 174 insertions(+), 34 deletions(-) create mode 100644 db/migrate/20181017000001_cti_generic_api2.rb diff --git a/app/assets/javascripts/app/controllers/cti.coffee b/app/assets/javascripts/app/controllers/cti.coffee index 02ebe3387..8bc773f72 100644 --- a/app/assets/javascripts/app/controllers/cti.coffee +++ b/app/assets/javascripts/app/controllers/cti.coffee @@ -121,20 +121,6 @@ class App.CTI extends App.Controller @renderCallerLog() renderCallerLog: -> - format = (time) -> - - # Hours, minutes and seconds - hrs = ~~parseInt((time / 3600)) - mins = ~~parseInt(((time % 3600) / 60)) - secs = parseInt(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 item.status_class = '' item.disabled = true @@ -157,15 +143,12 @@ class App.CTI extends App.Controller if item.comment item.state_human += ", #{item.comment}" - if item.start_at && item.end_at - item.duration = format((Date.parse(item.end_at) - Date.parse(item.start_at))/1000) - diff_in_min = ((Date.now() - Date.parse(item.created_at)) / 1000) / 60 if diff_in_min > 1 item.disabled = false @removePopovers() - @callerLog.html( App.view('cti/caller_log')(list: @list)) + @callerLog.html(App.view('cti/caller_log')(list: @list)) @renderPopovers() @updateNavMenu() diff --git a/app/assets/javascripts/app/index.coffee b/app/assets/javascripts/app/index.coffee index 5395640dd..807944c39 100644 --- a/app/assets/javascripts/app/index.coffee +++ b/app/assets/javascripts/app/index.coffee @@ -43,6 +43,23 @@ class App extends Spine.Controller decimal: (data, positions = 2) -> App.Utils.decimal(data, positions) + # define time_duration / mm:ss / hh:mm:ss format helper + time_duration: (time) -> + return '' if !time + return '' if isNaN(parseInt(time)) + + # Hours, minutes and seconds + hrs = ~~parseInt((time / 3600)) + mins = ~~parseInt(((time % 3600) / 60)) + secs = parseInt(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}" + # define mask helper # mask an value like 'a***********yz' M: (item, start = 1, end = 2) -> diff --git a/app/assets/javascripts/app/views/cti/caller_log.jst.eco b/app/assets/javascripts/app/views/cti/caller_log.jst.eco index 7a962111c..8f70744e7 100644 --- a/app/assets/javascripts/app/views/cti/caller_log.jst.eco +++ b/app/assets/javascripts/app/views/cti/caller_log.jst.eco @@ -4,8 +4,10 @@ <%- @T('From') %> <%- @T('To') %> + <%- @T('Status') %> - <%- @T('Duration') %> + <%- @T('Waiting') %> + <%- @T('Duration') %> <%- @T('Time') %> @@ -83,12 +85,14 @@ <% end %> <% end %> + <% if item.state_human: %> <%- @Icon('status', "#{item.status_class} inline") %> <%- @T(item.state_human) %> <% end %> - <%= item.duration %> + <%= @time_duration(item.duration_waiting_time) %> + <%= @time_duration(item.duration_talking_time) %> <%- @humanTime(item.created_at) %> <% end %> diff --git a/app/models/cti/log.rb b/app/models/cti/log.rb index 09abedd58..41e6e7174 100644 --- a/app/models/cti/log.rb +++ b/app/models/cti/log.rb @@ -68,7 +68,8 @@ example data, can be used for demo user_id: 2, } ] - } + }, + created_at: Time.zone.now, ) Cti::Log.create!( @@ -91,7 +92,8 @@ example data, can be used for demo user_id: 2, } ] - } + }, + created_at: Time.zone.now - 20.seconds, ) Cti::Log.create!( @@ -114,7 +116,11 @@ example data, can be used for demo user_id: 2, } ] - } + }, + initialized_at: Time.zone.now - 20.seconds, + start_at: Time.zone.now - 30.seconds, + duration_waiting_time: 20, + created_at: Time.zone.now - 20.seconds, ) Cti::Log.create!( @@ -139,7 +145,13 @@ example data, can be used for demo user_id: 2, } ] - } + }, + initialized_at: Time.zone.now - 80.seconds, + start_at: Time.zone.now - 45.seconds, + end_at: Time.zone.now, + duration_waiting_time: 35, + duration_talking_time: 45, + created_at: Time.zone.now - 80.seconds, ) Cti::Log.create!( @@ -164,7 +176,13 @@ example data, can be used for demo user_id: 2, } ] - } + }, + initialized_at: Time.zone.now - 5.minutes, + start_at: Time.zone.now - 3.minutes, + end_at: Time.zone.now - 20.seconds, + duration_waiting_time: 120, + duration_talking_time: 160, + created_at: Time.zone.now - 5.minutes, ) Cti::Log.create!( @@ -189,7 +207,13 @@ example data, can be used for demo user_id: 2, } ] - } + }, + initialized_at: Time.zone.now - 60.minutes, + start_at: Time.zone.now - 59.minutes, + end_at: Time.zone.now - 2.minutes, + duration_waiting_time: 60, + duration_talking_time: 3420, + created_at: Time.zone.now - 60.minutes, ) Cti::Log.create!( @@ -214,7 +238,13 @@ example data, can be used for demo user_id: 2, } ] - } + }, + initialized_at: Time.zone.now - 240.minutes, + start_at: Time.zone.now - 235.minutes, + end_at: Time.zone.now - 222.minutes, + duration_waiting_time: 300, + duration_talking_time: 1080, + created_at: Time.zone.now - 240.minutes, ) Cti::Log.create!( @@ -226,7 +256,13 @@ example data, can be used for demo state: 'hangup', start_at: Time.zone.now - 20.seconds, end_at: Time.zone.now, - preferences: {} + preferences: {}, + initialized_at: Time.zone.now - 1440.minutes, + start_at: Time.zone.now - 1430.minutes, + end_at: Time.zone.now - 1429.minutes, + duration_waiting_time: 600, + duration_talking_time: 660, + created_at: Time.zone.now - 1440.minutes, ) =end @@ -307,7 +343,11 @@ Cti::Log.process( preferences = nil done = true if params['direction'] == 'in' - to_comment = user + if user.present? + to_comment = user + elsif queue.present? + to_comment = queue + end from_comment, preferences = CallerId.get_comment_preferences(params['from'], 'from') else from_comment = user @@ -367,7 +407,7 @@ Cti::Log.process( log.state = 'hangup' log.end_at = Time.zone.now if log.start_at - log.duration_talking_time = log.start_at.to_i - log.end_at.to_i + log.duration_talking_time = log.end_at.to_i - log.start_at.to_i elsif !log.duration_waiting_time && log.initialized_at log.duration_waiting_time = log.end_at.to_i - log.initialized_at.to_i end diff --git a/db/migrate/20181017000001_cti_generic_api2.rb b/db/migrate/20181017000001_cti_generic_api2.rb new file mode 100644 index 000000000..634d9c275 --- /dev/null +++ b/db/migrate/20181017000001_cti_generic_api2.rb @@ -0,0 +1,39 @@ +class CtiGenericApi2 < ActiveRecord::Migration[5.1] + def up + + # return if it's a new setup + return if !Setting.find_by(name: 'system_init_done') + return if !column_exists?(:cti_logs, :initialized_at) + return if !column_exists?(:cti_logs, :initialized_at_cleanup) + + add_column :cti_logs, :initialized_at_cleanup, :timestamp, limit: 3, null: true + Cti::Log.connection.schema_cache.clear! + Cti::Log.reset_column_information + + # clenaup table records + Cti::Log.order(created_at: :desc).limit(2000).each do |log| + if log.initialized_at + begin + initialized_at = Time.zone.parse(log.initialized_at) + log.update_column(:initialized_at_cleanup, initialized_at) # rubocop:disable Rails/SkipsModelValidations + if initialized_at && log.start_at + log.update_column(:duration_waiting_time, log.start_at.to_i - initialized_at.to_i) # rubocop:disable Rails/SkipsModelValidations + end + rescue => e + logger.error e + end + end + if log.end_at && log.start_at + log.update_column(:duration_talking_time, log.end_at.to_i - log.start_at.to_i) # rubocop:disable Rails/SkipsModelValidations + end + end + + remove_column(:cti_logs, :initialized_at) + Cti::Log.connection.schema_cache.clear! + Cti::Log.reset_column_information + + rename_column :cti_logs, :initialized_at_cleanup, :initialized_at + Cti::Log.connection.schema_cache.clear! + Cti::Log.reset_column_information + end +end diff --git a/spec/requests/integration/cti_spec.rb b/spec/requests/integration/cti_spec.rb index 5c41cc3b1..f9df2d09d 100644 --- a/spec/requests/integration/cti_spec.rb +++ b/spec/requests/integration/cti_spec.rb @@ -162,6 +162,8 @@ RSpec.describe 'Integration CTI', type: :request do expect(log.duration_waiting_time).to be_nil expect(log.duration_talking_time).to be_nil + travel 2.seconds + # outbound - I - hangup by agent params = 'event=hangup&direction=out&call_id=1234567890-1&cause=cancel' post "/api/v1/cti/#{token}", params: params @@ -180,7 +182,7 @@ RSpec.describe 'Integration CTI', type: :request do expect(log.initialized_at).to be_truthy expect(log.start_at).to be_nil expect(log.end_at).to be_truthy - expect(log.duration_waiting_time).to be_truthy + expect(log.duration_waiting_time).to eq(2) expect(log.duration_talking_time).to be_nil # outbound - II - new call @@ -204,6 +206,8 @@ RSpec.describe 'Integration CTI', type: :request do expect(log.duration_waiting_time).to be_nil expect(log.duration_talking_time).to be_nil + travel 2.seconds + # outbound - II - answer by customer params = 'event=answer&direction=out&call_id=1234567890-2&from=4930600000000&to=4912347114711' post "/api/v1/cti/#{token}", params: params @@ -222,9 +226,11 @@ RSpec.describe 'Integration CTI', type: :request do expect(log.initialized_at).to be_truthy expect(log.start_at).to be_truthy expect(log.end_at).to be_nil - expect(log.duration_waiting_time).to be_truthy + expect(log.duration_waiting_time).to eq(2) expect(log.duration_talking_time).to be_nil + travel 2.seconds + # outbound - II - hangup by customer params = 'event=hangup&direction=out&call_id=1234567890-2&cause=normalClearing&from=4930600000000&to=4912347114711' post "/api/v1/cti/#{token}", params: params @@ -243,8 +249,8 @@ RSpec.describe 'Integration CTI', type: :request do expect(log.initialized_at).to be_truthy expect(log.start_at).to be_truthy expect(log.end_at).to be_truthy - expect(log.duration_waiting_time).to be_truthy - expect(log.duration_talking_time).to be_truthy + expect(log.duration_waiting_time).to eq(2) + expect(log.duration_talking_time).to eq(2) # inbound - I - new call params = 'event=newCall&direction=in&to=4930600000000&from=4912347114711&call_id=1234567890-3&user%5B%5D=user+1' @@ -488,5 +494,56 @@ RSpec.describe 'Integration CTI', type: :request do expect(json_response['list'][5]['state']).to eq('hangup') expect(json_response['list'][6]['call_id']).to eq('1234567890-1') end + + it 'does queue param tests' do + token = Setting.get('cti_token') + + # inbound - queue & user + params = 'event=newCall&direction=in&to=4930600000000&from=anonymous&call_id=1234567890-1&user%5B%5D=user+1,user+2&queue=some_queue_name' + post "/api/v1/cti/#{token}", params: params + expect(response).to have_http_status(200) + log = Cti::Log.find_by(call_id: '1234567890-1') + expect(log).to be_truthy + expect(log.to).to eq('4930600000000') + expect(log.from).to eq('anonymous') + expect(log.direction).to eq('in') + expect(log.to_comment).to eq('user 1,user 2') + expect(log.from_comment).to be_nil + expect(log.preferences['to']).to be_falsey + expect(log.preferences['from']).to be_falsey + expect(log.comment).to be_nil + expect(log.queue).to eq('some_queue_name') + expect(log.state).to eq('newCall') + expect(log.done).to eq(false) + expect(log.initialized_at).to be_truthy + expect(log.start_at).to be_nil + expect(log.end_at).to be_nil + expect(log.duration_waiting_time).to be_nil + expect(log.duration_talking_time).to be_nil + + # inbound - queue & no user + params = 'event=newCall&direction=in&to=4930600000000&from=anonymous&call_id=1234567890-2&user%5B%5D=&queue=some_queue_name' + post "/api/v1/cti/#{token}", params: params + expect(response).to have_http_status(200) + log = Cti::Log.find_by(call_id: '1234567890-2') + expect(log).to be_truthy + expect(log.to).to eq('4930600000000') + expect(log.from).to eq('anonymous') + expect(log.direction).to eq('in') + expect(log.to_comment).to eq('some_queue_name') + expect(log.from_comment).to be_nil + expect(log.preferences['to']).to be_falsey + expect(log.preferences['from']).to be_falsey + expect(log.comment).to be_nil + expect(log.queue).to eq('some_queue_name') + expect(log.state).to eq('newCall') + expect(log.done).to eq(false) + expect(log.initialized_at).to be_truthy + expect(log.start_at).to be_nil + expect(log.end_at).to be_nil + expect(log.duration_waiting_time).to be_nil + expect(log.duration_talking_time).to be_nil + + end end end