Merge branch 'develop' of github.com:martini/zammad into develop
This commit is contained in:
commit
6ef77f627d
32 changed files with 1351 additions and 164 deletions
10
Gemfile.lock
10
Gemfile.lock
|
@ -45,7 +45,7 @@ GEM
|
|||
ast (2.2.0)
|
||||
autoprefixer-rails (6.3.6)
|
||||
execjs
|
||||
biz (1.5.1)
|
||||
biz (1.5.2)
|
||||
clavius (~> 1.0)
|
||||
tzinfo
|
||||
browser (2.0.3)
|
||||
|
@ -129,7 +129,7 @@ GEM
|
|||
inflection (1.0.0)
|
||||
json (1.8.3)
|
||||
jwt (1.5.1)
|
||||
koala (2.2.0)
|
||||
koala (2.3.0)
|
||||
addressable
|
||||
faraday
|
||||
multi_json
|
||||
|
@ -199,7 +199,7 @@ GEM
|
|||
coderay (~> 1.1.0)
|
||||
method_source (~> 0.8.1)
|
||||
slop (~> 3.4)
|
||||
puma (3.3.0)
|
||||
puma (3.4.0)
|
||||
rack (1.6.4)
|
||||
rack-livereload (0.3.16)
|
||||
rack
|
||||
|
@ -269,8 +269,8 @@ GEM
|
|||
simplecov (>= 0.4.1)
|
||||
slack-notifier (1.5.1)
|
||||
slop (3.6.0)
|
||||
spring (1.6.4)
|
||||
sprockets (3.5.2)
|
||||
spring (1.7.1)
|
||||
sprockets (3.6.0)
|
||||
concurrent-ruby (~> 1.0)
|
||||
rack (> 1, < 3)
|
||||
sprockets-rails (3.0.4)
|
||||
|
|
|
@ -0,0 +1,156 @@
|
|||
class Index extends App.ControllerIntegrationBase
|
||||
featureIntegration: 'sipgate_integration'
|
||||
featureName: 'sipgate.io'
|
||||
featureConfig: 'sipgate_config'
|
||||
description: [
|
||||
['This service shows you contacts of incoming calls and a caller list in realtime.']
|
||||
['Also caller id of outbound calls can be changed.']
|
||||
]
|
||||
|
||||
render: =>
|
||||
super
|
||||
new Form(
|
||||
el: @$('.js-form')
|
||||
)
|
||||
|
||||
new App.HttpLog(
|
||||
el: @$('.js-log')
|
||||
facility: 'sipgate.io'
|
||||
)
|
||||
|
||||
class Form extends App.Controller
|
||||
events:
|
||||
'submit form': 'update'
|
||||
'click .js-inboundBlockCallerId .js-add': 'addInboundBlockCallerId'
|
||||
'click .js-outboundRouting .js-add': 'addOutboundRouting'
|
||||
'click .js-inboundBlockCallerId .js-remove': 'removeInboundBlockCallerId'
|
||||
'click .js-outboundRouting .js-remove': 'removeOutboundRouting'
|
||||
|
||||
constructor: ->
|
||||
super
|
||||
|
||||
# check authentication
|
||||
return if !@authenticate()
|
||||
|
||||
@subscribeId = App.Setting.subscribe(@render, initFetch: true, clear: false)
|
||||
|
||||
currentConfig: ->
|
||||
config = App.Setting.get('sipgate_config')
|
||||
if !config.outbound
|
||||
config.outbound = {}
|
||||
if !config.outbound.routing_table
|
||||
config.outbound.routing_table = []
|
||||
if !config.inbound
|
||||
config.inbound = {}
|
||||
if !config.inbound.block_caller_ids
|
||||
config.inbound.block_caller_ids = []
|
||||
config
|
||||
|
||||
setConfig: (value) ->
|
||||
App.Setting.set('sipgate_config', value)
|
||||
|
||||
render: =>
|
||||
@config = @currentConfig()
|
||||
|
||||
@html App.view('integration/sipgate')(
|
||||
config: @config
|
||||
)
|
||||
|
||||
updateCurrentConfig: =>
|
||||
config = @config
|
||||
cleanupInput = @cleanupInput
|
||||
|
||||
# default caller_id
|
||||
default_caller_id = @$('input[name=default_caller_id]').val()
|
||||
config.outbound.default_caller_id = cleanupInput(default_caller_id)
|
||||
|
||||
# routing table
|
||||
config.outbound.routing_table = []
|
||||
@$('.js-outboundRouting .js-row').each(->
|
||||
dest = cleanupInput($(@).find('input[name="dest"]').val())
|
||||
caller_id = cleanupInput($(@).find('input[name="caller_id"]').val())
|
||||
note = $(@).find('input[name="note"]').val()
|
||||
config.outbound.routing_table.push {
|
||||
dest: dest
|
||||
caller_id: caller_id
|
||||
note: note
|
||||
}
|
||||
)
|
||||
|
||||
# blocked caller ids
|
||||
config.inbound.block_caller_ids = []
|
||||
@$('.js-inboundBlockCallerId .js-row').each(->
|
||||
caller_id = $(@).find('input[name="caller_id"]').val()
|
||||
note = $(@).find('input[name="note"]').val()
|
||||
config.inbound.block_caller_ids.push {
|
||||
caller_id: cleanupInput(caller_id)
|
||||
note: note
|
||||
}
|
||||
)
|
||||
|
||||
@config = config
|
||||
|
||||
update: (e) =>
|
||||
e.preventDefault()
|
||||
@updateCurrentConfig()
|
||||
@setConfig(@config)
|
||||
|
||||
cleanupInput: (value) ->
|
||||
return value if !value
|
||||
value.replace(/\s/g, '').trim()
|
||||
|
||||
addInboundBlockCallerId: (e) =>
|
||||
e.preventDefault()
|
||||
@updateCurrentConfig()
|
||||
element = $(e.currentTarget).closest('tr')
|
||||
caller_id = element.find('input[name="caller_id"]').val()
|
||||
note = element.find('input[name="note"]').val()
|
||||
@config.inbound.block_caller_ids.push {
|
||||
caller_id: @cleanupInput(caller_id)
|
||||
note: note
|
||||
}
|
||||
@setConfig(@config)
|
||||
@render()
|
||||
|
||||
addOutboundRouting: (e) =>
|
||||
e.preventDefault()
|
||||
@updateCurrentConfig()
|
||||
element = $(e.currentTarget).closest('tr')
|
||||
dest = @cleanupInput(element.find('input[name="dest"]').val())
|
||||
caller_id = @cleanupInput(element.find('input[name="caller_id"]').val())
|
||||
note = element.find('input[name="note"]').val()
|
||||
@config.outbound.routing_table.push {
|
||||
dest: dest
|
||||
caller_id: caller_id
|
||||
note: note
|
||||
}
|
||||
@setConfig(@config)
|
||||
@render()
|
||||
|
||||
removeInboundBlockCallerId: (e) =>
|
||||
e.preventDefault()
|
||||
@updateCurrentConfig()
|
||||
element = $(e.currentTarget).closest('tr')
|
||||
element.remove()
|
||||
|
||||
removeOutboundRouting: (e) =>
|
||||
e.preventDefault()
|
||||
@updateCurrentConfig()
|
||||
element = $(e.currentTarget).closest('tr')
|
||||
element.remove()
|
||||
|
||||
class State
|
||||
@current: ->
|
||||
App.Setting.get('sipgate_integration')
|
||||
|
||||
App.Config.set(
|
||||
'IntegrationSipgate'
|
||||
{
|
||||
name: 'sipgate.io'
|
||||
target: '#system/integration/sipgate'
|
||||
description: 'VoIP services provide.'
|
||||
controller: Index
|
||||
state: State
|
||||
}
|
||||
'NavBarIntegrations'
|
||||
)
|
114
app/assets/javascripts/app/controllers/cti.coffee
Normal file
114
app/assets/javascripts/app/controllers/cti.coffee
Normal file
|
@ -0,0 +1,114 @@
|
|||
class App.CTI extends App.Controller
|
||||
constructor: ->
|
||||
super
|
||||
|
||||
@meta =
|
||||
active: false
|
||||
|
||||
preferences = @Session.get('preferences') || {}
|
||||
@meta.active = preferences.cti || false
|
||||
|
||||
@load()
|
||||
|
||||
App.Event.bind(
|
||||
'cti_event'
|
||||
(data) =>
|
||||
console.log('cti_event', data)
|
||||
if data.state is 'newCall'
|
||||
console.log('notify')
|
||||
@notify(data)
|
||||
'cti_event'
|
||||
)
|
||||
App.Event.bind(
|
||||
'cti_list_push'
|
||||
(data) =>
|
||||
@list = data
|
||||
@render()
|
||||
'cti_list_push'
|
||||
)
|
||||
|
||||
# fetch data, render view
|
||||
load: ->
|
||||
@ajax(
|
||||
id: 'cti_log'
|
||||
type: 'GET'
|
||||
url: "#{@apiPath}/cti/log"
|
||||
success: (data) =>
|
||||
@list = data
|
||||
@render()
|
||||
)
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
featureActive: =>
|
||||
return true
|
||||
if @Config.get('sipgate_integration')
|
||||
return true
|
||||
false
|
||||
|
||||
render: ->
|
||||
if !@isRole('CTI')
|
||||
@renderScreenUnauthorized(objectName: 'CTI')
|
||||
return
|
||||
|
||||
@html App.view('cti/index')(
|
||||
list: @list
|
||||
)
|
||||
|
||||
@updateNavMenu()
|
||||
|
||||
show: (params) =>
|
||||
@title 'CTI', true
|
||||
@navupdate '#cti'
|
||||
|
||||
counter: ->
|
||||
counter = 0
|
||||
|
||||
switch: (state = undefined) =>
|
||||
|
||||
# read state
|
||||
if state is undefined
|
||||
return @meta.active
|
||||
|
||||
@meta.active = state
|
||||
|
||||
# update user preferences
|
||||
@ajax(
|
||||
id: 'preferences'
|
||||
type: 'PUT'
|
||||
url: "#{@apiPath}/users/preferences"
|
||||
data: JSON.stringify(user: {cti: state})
|
||||
processData: true
|
||||
)
|
||||
|
||||
updateNavMenu: =>
|
||||
delay = ->
|
||||
App.Event.trigger('menu:render')
|
||||
@delay(delay, 200, 'updateNavMenu')
|
||||
|
||||
class CTIRouter extends App.ControllerPermanent
|
||||
constructor: (params) ->
|
||||
super
|
||||
|
||||
# check authentication
|
||||
return if !@authenticate(false, 'CTI')
|
||||
|
||||
App.TaskManager.execute(
|
||||
key: 'CTI'
|
||||
controller: 'CTI'
|
||||
params: {}
|
||||
show: true
|
||||
persistent: true
|
||||
)
|
||||
|
||||
App.Config.set('cti', CTIRouter, 'Routes')
|
||||
App.Config.set('CTI', { controller: 'CTI', authentication: true }, 'permanentTask')
|
||||
App.Config.set('CTI', { prio: 1300, parent: '', name: 'Phone', target: '#cti', key: 'CTI', shown: false, role: ['CTI'], class: 'phone' }, 'NavBar')
|
|
@ -9,22 +9,23 @@ class App.HttpLog extends App.Controller
|
|||
|
||||
fetch: =>
|
||||
@ajax(
|
||||
id: 'http_logs'
|
||||
type: 'GET'
|
||||
url: "#{@apiPath}/http_logs/#{@facility}"
|
||||
id: 'http_logs'
|
||||
type: 'GET'
|
||||
url: "#{@apiPath}/http_logs/#{@facility}"
|
||||
data:
|
||||
limit: @limit || 50
|
||||
processData: true
|
||||
success: (data) =>
|
||||
@records = data
|
||||
@render()
|
||||
if !@records[0] || (data[0] && @records[0] && data[0].updated_at isnt @records[0].updated_at)
|
||||
@records = data
|
||||
@render()
|
||||
@delay(@fetch, 20000)
|
||||
)
|
||||
|
||||
render: =>
|
||||
@html App.view('widget/http_log')(
|
||||
records: @records
|
||||
)
|
||||
#@delay(message, 2000)
|
||||
|
||||
show: (e) =>
|
||||
e.preventDefault()
|
||||
|
|
28
app/assets/javascripts/app/views/cti/index.jst.eco
Normal file
28
app/assets/javascripts/app/views/cti/index.jst.eco
Normal file
|
@ -0,0 +1,28 @@
|
|||
<div class="main flex">
|
||||
<h1><%- @T('Caller log') %></h1>
|
||||
|
||||
<div class="page-content">
|
||||
<table class="table table-striped table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><%- @T('From') %></th>
|
||||
<th><%- @T('To') %></th>
|
||||
<th><%- @T('State') %></th>
|
||||
<th><%- @T('Comment') %></th>
|
||||
<th><%- @T('Time') %></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<% for item in @list: %>
|
||||
<tr>
|
||||
<td><%= item.from %><% if item.from_comment: %> (<%= item.from_comment %>)<% end %></td>
|
||||
<td><%= item.to %><% if item.to_comment: %> (<%= item.to_comment %>)<% end %></td>
|
||||
<td><%= item.state %></td>
|
||||
<td><%= item.comment %></td>
|
||||
<td><%- @humanTime(item.created_at) %></td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
78
app/assets/javascripts/app/views/integration/sipgate.jst.eco
Normal file
78
app/assets/javascripts/app/views/integration/sipgate.jst.eco
Normal file
|
@ -0,0 +1,78 @@
|
|||
<form>
|
||||
|
||||
<h2><%- @T('Inbound') %></h2>
|
||||
|
||||
<p><%- @T('Blocked caller ids based on sender caller id.') %>
|
||||
|
||||
<div class="settings-entry">
|
||||
<table class="settings-list js-inboundBlockCallerId" style="width: 100%;">
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="50%"><%- @T('Caller id to block') %>
|
||||
<th width="40%"><%- @T('Note') %>
|
||||
<th width="10%"><%- @T('Action') %>
|
||||
</thead>
|
||||
<tbody>
|
||||
<% for row in @config.inbound.block_caller_ids: %>
|
||||
<tr class="js-row">
|
||||
<td class="settings-list-control-cell"><input name="caller_id" value="<%= row.caller_id %>" class="form-control form-control--small js-summary">
|
||||
<td class="settings-list-control-cell"><input name="note" value="<%= row.note %>" class="form-control form-control--small js-summary">
|
||||
<td class="settings-list-row-control"><div class="btn btn--text js-remove"><%- @Icon('trash') %> <%- @T('Remove') %></div>
|
||||
<% end %>
|
||||
<tr>
|
||||
<td class="settings-list-control-cell"><input name="caller_id" value="" placeholder="4930609854189" class="form-control form-control--small js-summary">
|
||||
<td class="settings-list-control-cell"><input name="note" value="" placeholder="<%- @Ti('my onw note') %>" class="form-control form-control--small js-summary">
|
||||
<td class="settings-list-row-control"><div class="btn btn--text btn--create js-add"><%- @Icon('plus-small') %> <%- @T('Add') %></div>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<h2><%- @T('Outbound') %></h2>
|
||||
|
||||
<p><%- @T('Set caller id of outbound calls based on destination caller id.') %>
|
||||
|
||||
<div class="settings-entry js-outboundRouting">
|
||||
<table class="settings-list" style="width: 100%;">
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="30%"><%- @T('Destination caller id') %>
|
||||
<th width="30%"><%- @T('Set outbound caller id') %>
|
||||
<th width="30%"><%- @T('Note') %>
|
||||
<th width="10%"><%- @T('Action') %>
|
||||
</thead>
|
||||
<tbody>
|
||||
<% for row in @config.outbound.routing_table: %>
|
||||
<tr class="js-row">
|
||||
<td class="settings-list-control-cell"><input name="dest" value="<%= row.dest %>" class="form-control form-control--small js-summary">
|
||||
<td class="settings-list-control-cell"><input name="caller_id" value="<%= row.caller_id %>" class="form-control form-control--small js-summary">
|
||||
<td class="settings-list-control-cell"><input name="note" value="<%= row.note %>" class="form-control form-control--small js-summary">
|
||||
<td class="settings-list-row-control"><div class="btn btn--text js-remove"><%- @Icon('trash') %> <%- @T('Remove') %></div>
|
||||
<% end %>
|
||||
<tr>
|
||||
<td class="settings-list-control-cell"><input name="dest" value="" placeholder="49* or 3230123456789" class="form-control form-control--small js-summary">
|
||||
<td class="settings-list-control-cell"><input name="caller_id" value="" placeholder="4930609854189" class="form-control form-control--small js-summary">
|
||||
<td class="settings-list-control-cell"><input name="note" value="" placeholder="<%- @Ti('my onw note') %>" class="form-control form-control--small js-summary">
|
||||
<td class="settings-list-row-control"><div class="btn btn--text btn--create js-add"><%- @Icon('plus-small') %> <%- @T('Add') %></div>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<p><%- @T('Default caller id.') %>
|
||||
|
||||
<div class="settings-entry">
|
||||
<table class="settings-list" style="width: 100%;">
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="50%"><%- @T('Default caller id') %>
|
||||
<th width="50%"><%- @T('Note') %>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="settings-list-control-cell"><input name="default_caller_id" value="<%= @config.outbound.default_caller_id %>" placeholder="4930609854189" class="form-control form-control--small js-summary">
|
||||
<td class="settings-list-row-control"><%- @T('Default caller id for outbound calls.') %>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn--primary"><%- @T('Save') %></button>
|
||||
</form>
|
|
@ -1,6 +1,6 @@
|
|||
<hr>
|
||||
|
||||
<%- @T('Recent logs') %>
|
||||
<h2><%- @T('Recent logs') %></h2>
|
||||
<div class="settings-entry">
|
||||
<table class="settings-list" style="width: 100%;">
|
||||
<thead>
|
||||
|
|
|
@ -3734,7 +3734,6 @@ footer {
|
|||
}
|
||||
|
||||
.popover-content {
|
||||
flex: 1;
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
margin-bottom: 0;
|
||||
|
|
|
@ -6,6 +6,7 @@ class ApplicationController < ActionController::Base
|
|||
helper_method :current_user,
|
||||
:authentication_check,
|
||||
:config_frontend,
|
||||
:http_log_config,
|
||||
:role?,
|
||||
:model_create_render,
|
||||
:model_update_render,
|
||||
|
@ -18,7 +19,7 @@ class ApplicationController < ActionController::Base
|
|||
before_action :cors_preflight_check
|
||||
|
||||
after_action :user_device_update, :set_access_control_headers
|
||||
after_action :trigger_events
|
||||
after_action :trigger_events, :http_log
|
||||
|
||||
# For all responses in this controller, return the CORS access control headers.
|
||||
def set_access_control_headers
|
||||
|
@ -47,6 +48,10 @@ class ApplicationController < ActionController::Base
|
|||
false
|
||||
end
|
||||
|
||||
def http_log_config(config)
|
||||
@http_log_support = config
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# execute events
|
||||
|
@ -98,6 +103,60 @@ class ApplicationController < ActionController::Base
|
|||
session[:user_agent] = request.env['HTTP_USER_AGENT']
|
||||
end
|
||||
|
||||
# log http access
|
||||
def http_log
|
||||
return if !@http_log_support
|
||||
|
||||
# request
|
||||
request_data = {
|
||||
content: '',
|
||||
content_type: request.headers['Content-Type'],
|
||||
content_encoding: request.headers['Content-Encoding'],
|
||||
source: request.headers['User-Agent'] || request.headers['Server'],
|
||||
}
|
||||
request.headers.each {|key, value|
|
||||
next if key[0, 5] != 'HTTP_'
|
||||
request_data[:content] += if key == 'HTTP_COOKIE'
|
||||
"#{key}: xxxxx\n"
|
||||
else
|
||||
"#{key}: #{value}\n"
|
||||
end
|
||||
}
|
||||
body = request.body.read
|
||||
if body
|
||||
request_data[:content] += "\n" + body
|
||||
end
|
||||
request_data[:content] = request_data[:content].slice(0, 8000)
|
||||
|
||||
# response
|
||||
response_data = {
|
||||
code: response.status = response.code,
|
||||
content: '',
|
||||
content_type: nil,
|
||||
content_encoding: nil,
|
||||
source: nil,
|
||||
}
|
||||
response.headers.each {|key, value|
|
||||
response_data[:content] += "#{key}: #{value}\n"
|
||||
}
|
||||
body = response.body
|
||||
if body
|
||||
response_data[:content] += "\n" + body
|
||||
end
|
||||
response_data[:content] = response_data[:content].slice(0, 8000)
|
||||
record = {
|
||||
direction: 'in',
|
||||
facility: @http_log_support[:facility],
|
||||
url: url_for(only_path: false, overwrite_params: {}),
|
||||
status: response.status,
|
||||
ip: request.remote_ip,
|
||||
request: request_data,
|
||||
response: response_data,
|
||||
method: request.method,
|
||||
}
|
||||
HttpLog.create(record)
|
||||
end
|
||||
|
||||
# user device recent action update
|
||||
def user_device_update
|
||||
|
||||
|
@ -228,12 +287,11 @@ class ApplicationController < ActionController::Base
|
|||
}
|
||||
end
|
||||
|
||||
def authentication_check(auth_param = {} )
|
||||
def authentication_check(auth_param = {})
|
||||
result = authentication_check_only(auth_param)
|
||||
|
||||
# check if basic_auth fallback is possible
|
||||
if auth_param[:basic_auth_promt] && result[:auth] == false
|
||||
|
||||
return request_http_basic_authentication
|
||||
end
|
||||
|
||||
|
|
178
app/controllers/integration/sipgate_controller.rb
Normal file
178
app/controllers/integration/sipgate_controller.rb
Normal file
|
@ -0,0 +1,178 @@
|
|||
# Copyright (C) 2012-2014 Zammad Foundation, http://zammad-foundation.org/
|
||||
|
||||
require 'builder'
|
||||
|
||||
class Integration::SipgateController < ApplicationController
|
||||
|
||||
# list current caller log
|
||||
def index
|
||||
return if !authentication_check
|
||||
return if deny_if_not_role('CTI')
|
||||
list = Cti::Log.order('created_at DESC').limit(60)
|
||||
render json: list
|
||||
end
|
||||
|
||||
# notify about inbound call / block inbound call
|
||||
def in
|
||||
http_log_config facility: 'sipgate.io'
|
||||
return if !configured?
|
||||
|
||||
if params['event'] == 'newCall'
|
||||
|
||||
config = Setting.get('sipgate_config')
|
||||
config_inbound = config[:inbound] || {}
|
||||
block_caller_ids = config_inbound[:block_caller_ids] || []
|
||||
|
||||
# check if call need to be blocked
|
||||
block_caller_ids.each {|item|
|
||||
next unless item[:caller_id] == params['from']
|
||||
xml = Builder::XmlMarkup.new(indent: 2)
|
||||
xml.instruct!
|
||||
content = xml.Response(onHangup: url, onAnswer: url) do
|
||||
xml.Reject('reason' => 'busy')
|
||||
end
|
||||
|
||||
send_data content, type: 'application/xml; charset=UTF-8;'
|
||||
|
||||
#params['Reject'] = 'busy'
|
||||
params['comment'] = 'reject, busy'
|
||||
if params['user']
|
||||
params['comment'] = "#{params['user']} -> reject, busy"
|
||||
end
|
||||
update_log(params)
|
||||
return true
|
||||
}
|
||||
end
|
||||
|
||||
update_log(params)
|
||||
|
||||
xml = Builder::XmlMarkup.new(indent: 2)
|
||||
xml.instruct!
|
||||
content = xml.Response(onHangup: url, onAnswer: url)
|
||||
send_data content, type: 'application/xml; charset=UTF-8;'
|
||||
end
|
||||
|
||||
# set caller id of outbound call
|
||||
def out
|
||||
http_log_config facility: 'sipgate.io'
|
||||
return if !configured?
|
||||
|
||||
config = Setting.get('sipgate_config')
|
||||
config_outbound = config[:outbound][:routing_table]
|
||||
default_caller_id = config[:outbound][:default_caller_id]
|
||||
|
||||
xml = Builder::XmlMarkup.new(indent: 2)
|
||||
xml.instruct!
|
||||
|
||||
# set callerId
|
||||
content = nil
|
||||
to = params[:to]
|
||||
from = nil
|
||||
if to
|
||||
config_outbound.each {|row|
|
||||
dest = row[:dest].gsub(/\*/, '.+?')
|
||||
next if to !~ /^#{dest}$/
|
||||
from = row[:caller_id]
|
||||
content = xml.Response(onHangup: url, onAnswer: url) do
|
||||
xml.Dial(callerId: from) { xml.Number(params[:to]) }
|
||||
end
|
||||
break
|
||||
}
|
||||
if !content && default_caller_id
|
||||
from = default_caller_id
|
||||
content = xml.Response(onHangup: url, onAnswer: url) do
|
||||
xml.Dial(callerId: default_caller_id) { xml.Number(params[:to]) }
|
||||
end
|
||||
end
|
||||
else
|
||||
content = xml.Response(onHangup: url, onAnswer: url)
|
||||
end
|
||||
|
||||
send_data content, type: 'application/xml; charset=UTF-8;'
|
||||
if from
|
||||
params['from'] = from
|
||||
end
|
||||
update_log(params)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def configured?
|
||||
if !Setting.get('sipgate_integration')
|
||||
xml_error('Feature is disable, please contact your admin to enable it!')
|
||||
return false
|
||||
end
|
||||
config = Setting.get('sipgate_config')
|
||||
if !config || !config[:inbound] || !config[:outbound]
|
||||
xml_error('Feature not configured, please contact your admin!')
|
||||
return false
|
||||
end
|
||||
true
|
||||
end
|
||||
|
||||
def update_log(params)
|
||||
|
||||
user = params['user']
|
||||
if params['user'] && params['user'].class == Array
|
||||
user = params['user'].join(', ')
|
||||
end
|
||||
from_comment = nil
|
||||
to_comment = nil
|
||||
if params['direction'] == 'in'
|
||||
to_comment = user
|
||||
else
|
||||
from_comment = user
|
||||
end
|
||||
comment = nil
|
||||
if params['cause']
|
||||
comment = params['cause']
|
||||
end
|
||||
|
||||
if params['event'] == 'newCall'
|
||||
Cti::Log.create(
|
||||
direction: params['direction'],
|
||||
from: params['from'],
|
||||
from_comment: from_comment,
|
||||
to: params['to'],
|
||||
to_comment: to_comment,
|
||||
call_id: params['callId'],
|
||||
comment: comment,
|
||||
state: params['event'],
|
||||
)
|
||||
elsif params['event'] == 'answer'
|
||||
log = Cti::Log.find_by(call_id: params['callId'])
|
||||
raise "No such call_id #{params['callId']}" if !log
|
||||
log.state = 'answer'
|
||||
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
|
||||
log.state = 'hangup'
|
||||
log.comment = comment
|
||||
log.save
|
||||
else
|
||||
raise "Unknown event #{params['event']}"
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
def xml_error(error)
|
||||
xml = Builder::XmlMarkup.new(indent: 2)
|
||||
xml.instruct!
|
||||
content = xml.Response() do
|
||||
xml.Error(error)
|
||||
end
|
||||
send_data content, type: 'application/xml; charset=UTF-8;', status: 422
|
||||
end
|
||||
|
||||
def base_url
|
||||
http_type = Setting.get('http_type')
|
||||
fqdn = Setting.get('fqdn')
|
||||
"#{http_type}://#{fqdn}/api/v1/sipgate"
|
||||
end
|
||||
|
||||
def url
|
||||
"#{base_url}/#{params['direction']}"
|
||||
end
|
||||
end
|
|
@ -85,7 +85,7 @@ class UsersController < ApplicationController
|
|||
group_ids = []
|
||||
role_ids = []
|
||||
if count <= 2
|
||||
Role.where(name: [ Z_ROLENAME_ADMIN, 'Agent', 'Chat']).each { |role|
|
||||
Role.where(name: [ Z_ROLENAME_ADMIN, 'Agent', 'Chat', 'CTI']).each { |role|
|
||||
role_ids.push role.id
|
||||
}
|
||||
Group.all().each { |group|
|
||||
|
|
|
@ -490,6 +490,7 @@ retrns
|
|||
Observer::Transaction.commit
|
||||
|
||||
# run postmaster post filter
|
||||
filters = {}
|
||||
Setting.where(area: 'Postmaster::PostFilter').order(:name).each {|setting|
|
||||
filters[setting.name] = Kernel.const_get(Setting.get(setting.name))
|
||||
}
|
||||
|
|
78
app/models/cti/log.rb
Normal file
78
app/models/cti/log.rb
Normal file
|
@ -0,0 +1,78 @@
|
|||
module Cti
|
||||
class Log < ApplicationModel
|
||||
self.table_name = 'cti_logs'
|
||||
|
||||
after_create :push_event, :push_caller_list
|
||||
after_update :push_event, :push_caller_list
|
||||
after_destroy :push_event, :push_caller_list
|
||||
|
||||
=begin
|
||||
|
||||
Cti::Log.create(
|
||||
direction: 'in',
|
||||
from: '007',
|
||||
from_comment: '',
|
||||
to: '008',
|
||||
to_comment: '',
|
||||
call_id: '1',
|
||||
comment: '',
|
||||
state: 'newCall',
|
||||
)
|
||||
|
||||
Cti::Log.create(
|
||||
direction: 'in',
|
||||
from: '007',
|
||||
from_comment: '',
|
||||
to: '008',
|
||||
to_comment: '',
|
||||
call_id: '2',
|
||||
comment: '',
|
||||
state: 'answer',
|
||||
)
|
||||
|
||||
Cti::Log.create(
|
||||
direction: 'in',
|
||||
from: '009',
|
||||
from_comment: '',
|
||||
to: '010',
|
||||
to_comment: '',
|
||||
call_id: '3',
|
||||
comment: '',
|
||||
state: 'hangup',
|
||||
)
|
||||
|
||||
=end
|
||||
|
||||
def push_event
|
||||
users = User.of_role('CTI')
|
||||
users.each {|user|
|
||||
|
||||
# send notify about event
|
||||
Sessions.send_to(
|
||||
user.id,
|
||||
{
|
||||
event: 'cti_event',
|
||||
data: self,
|
||||
},
|
||||
)
|
||||
}
|
||||
end
|
||||
|
||||
def push_caller_list
|
||||
list = Cti::Log.order('created_at DESC').limit(60)
|
||||
|
||||
users = User.of_role('CTI')
|
||||
users.each {|user|
|
||||
|
||||
# send notify on create/update/delete
|
||||
Sessions.send_to(
|
||||
user.id,
|
||||
{
|
||||
event: 'cti_list_push',
|
||||
data: list,
|
||||
},
|
||||
)
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,33 +0,0 @@
|
|||
# Copyright (C) 2012-2014 Zammad Foundation, http://zammad-foundation.org/
|
||||
|
||||
require 'signature_detection'
|
||||
|
||||
class Observer::Ticket::Article::SignatureDetection < ActiveRecord::Observer
|
||||
observe 'ticket::_article'
|
||||
|
||||
def before_create(record)
|
||||
|
||||
# return if we run import mode
|
||||
return if Setting.get('import_mode')
|
||||
|
||||
# if sender is not customer, do not change anything
|
||||
sender = Ticket::Article::Sender.lookup(id: record.sender_id)
|
||||
return if !sender
|
||||
return if sender['name'] != 'Customer'
|
||||
|
||||
# set email attributes
|
||||
type = Ticket::Article::Type.lookup(id: record.type_id)
|
||||
return if type['name'] != 'email'
|
||||
|
||||
# add queue job to update current signature of user id
|
||||
Delayed::Job.enqueue(Observer::Ticket::Article::SignatureDetection::BackgroundJob.new(record.created_by_id))
|
||||
|
||||
# user
|
||||
user = User.lookup(id: record.created_by_id)
|
||||
return if !user
|
||||
return if !user.preferences
|
||||
return if !user.preferences[:signature_detection]
|
||||
|
||||
record.preferences[:signature_detection] = SignatureDetection.find_signature_line(user.preferences[:signature_detection], record.body)
|
||||
end
|
||||
end
|
|
@ -1,9 +0,0 @@
|
|||
class Observer::Ticket::Article::SignatureDetection::BackgroundJob
|
||||
def initialize(id)
|
||||
@user_id = id
|
||||
end
|
||||
|
||||
def perform
|
||||
SignatureDetection.rebuild_user(@user_id)
|
||||
end
|
||||
end
|
|
@ -1,7 +1,7 @@
|
|||
# Copyright (C) 2012-2014 Zammad Foundation, http://zammad-foundation.org/
|
||||
|
||||
class Observer::Transaction < ActiveRecord::Observer
|
||||
observe :ticket, 'ticket::_article'
|
||||
observe :ticket, 'ticket::_article', :user, :organization
|
||||
|
||||
def self.commit(params = {})
|
||||
|
||||
|
@ -28,10 +28,10 @@ class Observer::Transaction < ActiveRecord::Observer
|
|||
|
||||
# get uniq objects
|
||||
list_objects = get_uniq_changes(list)
|
||||
list_objects.each {|_id, item|
|
||||
|
||||
# send background job
|
||||
Delayed::Job.enqueue(Transaction::BackgroundJob.new(item, params))
|
||||
list_objects.each {|_object, objects|
|
||||
objects.each {|_id, item|
|
||||
Delayed::Job.enqueue(Transaction::BackgroundJob.new(item, params))
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
|
@ -40,33 +40,37 @@ class Observer::Transaction < ActiveRecord::Observer
|
|||
result = get_uniq_changes(events)
|
||||
|
||||
result = {
|
||||
1 => {
|
||||
object: 'Ticket',
|
||||
type: 'create',
|
||||
ticket_id: 123,
|
||||
article_id: 123,
|
||||
},
|
||||
9 => {
|
||||
object: 'Ticket',
|
||||
type: 'update',
|
||||
ticket_id: 123,
|
||||
changes: {
|
||||
attribute1: [before, now],
|
||||
attribute2: [before, now],
|
||||
}
|
||||
'Ticket' =>
|
||||
1 => {
|
||||
object: 'Ticket',
|
||||
type: 'create',
|
||||
object_id: 123,
|
||||
article_id: 123,
|
||||
},
|
||||
9 => {
|
||||
object: 'Ticket',
|
||||
type: 'update',
|
||||
object_id: 123,
|
||||
changes: {
|
||||
attribute1: [before, now],
|
||||
attribute2: [before, now],
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
result = {
|
||||
9 => {
|
||||
object: 'Ticket',
|
||||
type: 'update',
|
||||
ticket_id: 123,
|
||||
article_id: 123,
|
||||
changes: {
|
||||
attribute1: [before, now],
|
||||
attribute2: [before, now],
|
||||
}
|
||||
'Ticket' =>
|
||||
9 => {
|
||||
object: 'Ticket',
|
||||
type: 'update',
|
||||
object_id: 123,
|
||||
article_id: 123,
|
||||
changes: {
|
||||
attribute1: [before, now],
|
||||
attribute2: [before, now],
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -76,57 +80,59 @@ class Observer::Transaction < ActiveRecord::Observer
|
|||
list_objects = {}
|
||||
events.each { |event|
|
||||
|
||||
# get current state of objects
|
||||
if event[:name] == 'Ticket::Article'
|
||||
# simulate article create as ticket update
|
||||
article = nil
|
||||
if event[:object] == 'Ticket::Article'
|
||||
article = Ticket::Article.lookup(id: event[:id])
|
||||
|
||||
# next if article is already deleted
|
||||
next if !article
|
||||
next if event[:type] == 'update'
|
||||
|
||||
ticket = article.ticket
|
||||
if !list_objects[ticket.id]
|
||||
list_objects[ticket.id] = {}
|
||||
# set new event infos
|
||||
ticket = Ticket.lookup(id: article.ticket_id)
|
||||
event[:object] = 'Ticket'
|
||||
event[:id] = ticket.id
|
||||
event[:type] = 'update'
|
||||
event[:changes] = nil
|
||||
end
|
||||
|
||||
# get current state of objects
|
||||
object = Kernel.const_get(event[:object]).lookup(id: event[:id])
|
||||
|
||||
# next if object is already deleted
|
||||
next if !object
|
||||
|
||||
if !list_objects[event[:object]]
|
||||
list_objects[event[:object]] = {}
|
||||
end
|
||||
if !list_objects[event[:object]][object.id]
|
||||
list_objects[event[:object]][object.id] = {}
|
||||
end
|
||||
store = list_objects[event[:object]][object.id]
|
||||
store[:object] = event[:object]
|
||||
store[:object_id] = object.id
|
||||
|
||||
if !store[:type] || store[:type] == 'update'
|
||||
store[:type] = event[:type]
|
||||
end
|
||||
|
||||
# merge changes
|
||||
if event[:changes]
|
||||
if !store[:changes]
|
||||
store[:changes] = event[:changes]
|
||||
else
|
||||
event[:changes].each {|key, value|
|
||||
if !store[:changes][key]
|
||||
store[:changes][key] = value
|
||||
else
|
||||
store[:changes][key][1] = value[1]
|
||||
end
|
||||
}
|
||||
end
|
||||
list_objects[ticket.id][:object] = 'Ticket'
|
||||
list_objects[ticket.id][:article_id] = article.id
|
||||
list_objects[ticket.id][:ticket_id] = ticket.id
|
||||
end
|
||||
|
||||
if !list_objects[ticket.id][:type]
|
||||
list_objects[ticket.id][:type] = 'update'
|
||||
end
|
||||
|
||||
elsif event[:name] == 'Ticket'
|
||||
ticket = Ticket.lookup(id: event[:id])
|
||||
|
||||
# next if ticket is already deleted
|
||||
next if !ticket
|
||||
|
||||
if !list_objects[ticket.id]
|
||||
list_objects[ticket.id] = {}
|
||||
end
|
||||
list_objects[ticket.id][:object] = 'Ticket'
|
||||
list_objects[ticket.id][:ticket_id] = ticket.id
|
||||
|
||||
if !list_objects[ticket.id][:type] || list_objects[ticket.id][:type] == 'update'
|
||||
list_objects[ticket.id][:type] = event[:type]
|
||||
end
|
||||
|
||||
# merge changes
|
||||
if event[:changes]
|
||||
if !list_objects[ticket.id][:changes]
|
||||
list_objects[ticket.id][:changes] = event[:changes]
|
||||
else
|
||||
event[:changes].each {|key, value|
|
||||
if !list_objects[ticket.id][:changes][key]
|
||||
list_objects[ticket.id][:changes][key] = value
|
||||
else
|
||||
list_objects[ticket.id][:changes][key][1] = value[1]
|
||||
end
|
||||
}
|
||||
end
|
||||
end
|
||||
else
|
||||
raise "unknown object for integration #{event[:name]}"
|
||||
# remember article id if exists
|
||||
if article
|
||||
store[:article_id] = article.id
|
||||
end
|
||||
}
|
||||
list_objects
|
||||
|
@ -138,7 +144,7 @@ class Observer::Transaction < ActiveRecord::Observer
|
|||
return if Setting.get('import_mode')
|
||||
|
||||
e = {
|
||||
name: record.class.name,
|
||||
object: record.class.name,
|
||||
type: 'create',
|
||||
data: record,
|
||||
id: record.id,
|
||||
|
@ -151,9 +157,6 @@ class Observer::Transaction < ActiveRecord::Observer
|
|||
# return if we run import mode
|
||||
return if Setting.get('import_mode')
|
||||
|
||||
# ignore updates on articles / we just want send integrations on ticket updates
|
||||
return if record.class.name == 'Ticket::Article'
|
||||
|
||||
# ignore certain attributes
|
||||
real_changes = {}
|
||||
record.changes.each {|key, value|
|
||||
|
@ -173,7 +176,7 @@ class Observer::Transaction < ActiveRecord::Observer
|
|||
return if real_changes.empty?
|
||||
|
||||
e = {
|
||||
name: record.class.name,
|
||||
object: record.class.name,
|
||||
type: 'update',
|
||||
data: record,
|
||||
changes: real_changes,
|
||||
|
|
|
@ -183,7 +183,7 @@ returns
|
|||
Transaction::BackgroundJob.run(
|
||||
object: 'Ticket',
|
||||
type: 'reminder_reached',
|
||||
ticket_id: ticket.id,
|
||||
object_id: ticket.id,
|
||||
article_id: ticket.articles.last.id,
|
||||
)
|
||||
|
||||
|
@ -223,7 +223,7 @@ returns
|
|||
Transaction::BackgroundJob.run(
|
||||
object: 'Ticket',
|
||||
type: 'escalation',
|
||||
ticket_id: ticket.id,
|
||||
object_id: ticket.id,
|
||||
article_id: ticket.articles.last.id,
|
||||
)
|
||||
result.push ticket
|
||||
|
@ -234,7 +234,7 @@ returns
|
|||
Transaction::BackgroundJob.run(
|
||||
object: 'Ticket',
|
||||
type: 'escalation_warning',
|
||||
ticket_id: ticket.id,
|
||||
object_id: ticket.id,
|
||||
article_id: ticket.articles.last.id,
|
||||
)
|
||||
result.push ticket
|
||||
|
|
|
@ -6,7 +6,7 @@ class Transaction::Notification
|
|||
{
|
||||
object: 'Ticket',
|
||||
type: 'update',
|
||||
ticket_id: 123,
|
||||
object_id: 123,
|
||||
via_web: true,
|
||||
changes: {
|
||||
'attribute1' => [before, now],
|
||||
|
@ -21,9 +21,15 @@ class Transaction::Notification
|
|||
end
|
||||
|
||||
def perform
|
||||
|
||||
# return if we run import mode
|
||||
return if Setting.get('import_mode')
|
||||
|
||||
return if @item[:object] != 'Ticket'
|
||||
|
||||
return if @params[:disable_notification]
|
||||
|
||||
ticket = Ticket.find(@item[:ticket_id])
|
||||
ticket = Ticket.find(@item[:object_id])
|
||||
if @item[:article_id]
|
||||
article = Ticket::Article.find(@item[:article_id])
|
||||
end
|
||||
|
|
58
app/models/transaction/signature_detection.rb
Normal file
58
app/models/transaction/signature_detection.rb
Normal file
|
@ -0,0 +1,58 @@
|
|||
# Copyright (C) 2012-2014 Zammad Foundation, http://zammad-foundation.org/
|
||||
require 'signature_detection'
|
||||
|
||||
class Transaction::SignatureDetection
|
||||
|
||||
=begin
|
||||
{
|
||||
object: 'Ticket',
|
||||
type: 'update',
|
||||
object_id: 123,
|
||||
via_web: true,
|
||||
changes: {
|
||||
'attribute1' => [before, now],
|
||||
'attribute2' => [before, now],
|
||||
}
|
||||
},
|
||||
=end
|
||||
|
||||
def initialize(item, params = {})
|
||||
@item = item
|
||||
@params = params
|
||||
end
|
||||
|
||||
def perform
|
||||
|
||||
# return if we run import mode
|
||||
return if Setting.get('import_mode')
|
||||
|
||||
return if @item[:type] != 'create'
|
||||
return if @item[:object] != 'Ticket'
|
||||
|
||||
ticket = Ticket.lookup(id: @item[:object_id])
|
||||
return if !ticket
|
||||
article = ticket.articles.first
|
||||
return if !article
|
||||
|
||||
# if sender is not customer, do not change anything
|
||||
sender = Ticket::Article::Sender.lookup(id: article.sender_id)
|
||||
return if !sender
|
||||
return if sender['name'] != 'Customer'
|
||||
|
||||
# set email attributes
|
||||
type = Ticket::Article::Type.lookup(id: article.type_id)
|
||||
return if type['name'] != 'email'
|
||||
|
||||
# add queue job to update current signature of user id
|
||||
SignatureDetection.rebuild_user(article.created_by_id)
|
||||
|
||||
# user
|
||||
user = User.lookup(id: article.created_by_id)
|
||||
return if !user
|
||||
return if !user.preferences
|
||||
return if !user.preferences[:signature_detection]
|
||||
article.preferences[:signature_detection] = SignatureDetection.find_signature_line(user.preferences[:signature_detection], article.body)
|
||||
article.save
|
||||
end
|
||||
|
||||
end
|
|
@ -6,14 +6,14 @@ class Transaction::Slack
|
|||
backend = Transaction::Slack.new(
|
||||
object: 'Ticket',
|
||||
type: 'create',
|
||||
ticket_id: 1,
|
||||
object_id: 1,
|
||||
)
|
||||
backend.perform
|
||||
|
||||
{
|
||||
object: 'Ticket',
|
||||
type: 'update',
|
||||
ticket_id: 123,
|
||||
object_id: 123,
|
||||
via_web: true,
|
||||
changes: {
|
||||
'attribute1' => [before, now],
|
||||
|
@ -27,6 +27,10 @@ backend.perform
|
|||
end
|
||||
|
||||
def perform
|
||||
|
||||
# return if we run import mode
|
||||
return if Setting.get('import_mode')
|
||||
|
||||
return if @item[:object] != 'Ticket'
|
||||
return if !Setting.get('slack_integration')
|
||||
|
||||
|
@ -34,7 +38,7 @@ backend.perform
|
|||
return if !config
|
||||
return if !config['items']
|
||||
|
||||
ticket = Ticket.find(@item[:ticket_id])
|
||||
ticket = Ticket.find(@item[:object_id])
|
||||
if @item[:article_id]
|
||||
article = Ticket::Article.find(@item[:article_id])
|
||||
end
|
||||
|
|
|
@ -30,7 +30,6 @@ module Zammad
|
|||
'observer::_ticket::_article::_communicate_email',
|
||||
'observer::_ticket::_article::_communicate_facebook',
|
||||
'observer::_ticket::_article::_communicate_twitter',
|
||||
'observer::_ticket::_article::_signature_detection',
|
||||
'observer::_ticket::_reset_new_state',
|
||||
'observer::_ticket::_escalation_calculation',
|
||||
'observer::_ticket::_ref_object_touch',
|
||||
|
|
|
@ -2,7 +2,7 @@ Zammad::Application.routes.draw do
|
|||
api_path = Rails.configuration.api_path
|
||||
|
||||
match api_path + '/http_logs', to: 'http_logs#index', via: :get
|
||||
match api_path + '/http_logs/:facility', to: 'http_logs#index', via: :get
|
||||
match api_path + '/http_logs/:facility', to: 'http_logs#index', via: :get, constraints: { facility: /.*/ }
|
||||
match api_path + '/http_logs', to: 'http_logs#create', via: :post
|
||||
|
||||
end
|
||||
|
|
7
config/routes/integration_sipgate.rb
Normal file
7
config/routes/integration_sipgate.rb
Normal file
|
@ -0,0 +1,7 @@
|
|||
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
|
||||
|
||||
end
|
37
db/migrate/20160421000001_add_sipgate_integration.rb
Normal file
37
db/migrate/20160421000001_add_sipgate_integration.rb
Normal file
|
@ -0,0 +1,37 @@
|
|||
class AddSipgateIntegration < ActiveRecord::Migration
|
||||
def up
|
||||
Setting.create_if_not_exists(
|
||||
title: 'sipgate.io integration',
|
||||
name: 'sipgate_integration',
|
||||
area: 'Integration::Switch',
|
||||
description: 'Define if sipgate.io (http://www.sipgate.io) is enabled or not.',
|
||||
options: {
|
||||
form: [
|
||||
{
|
||||
display: '',
|
||||
null: true,
|
||||
name: 'sipgate_integration',
|
||||
tag: 'boolean',
|
||||
options: {
|
||||
true => 'yes',
|
||||
false => 'no',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
state: false,
|
||||
preferences: { prio: 1 },
|
||||
frontend: false
|
||||
)
|
||||
Setting.create_if_not_exists(
|
||||
title: 'sipgate.io config',
|
||||
name: 'sipgate_config',
|
||||
area: 'Integration::Sipgate',
|
||||
description: 'Define the sipgate.io config.',
|
||||
options: {},
|
||||
state: {},
|
||||
frontend: false,
|
||||
preferences: { prio: 2 },
|
||||
)
|
||||
end
|
||||
end
|
13
db/migrate/20160422000001_update_signature_detection.rb
Normal file
13
db/migrate/20160422000001_update_signature_detection.rb
Normal file
|
@ -0,0 +1,13 @@
|
|||
class UpdateSignatureDetection < ActiveRecord::Migration
|
||||
def up
|
||||
Setting.create_if_not_exists(
|
||||
title: 'Define transaction backend.',
|
||||
name: '1000_signature_detection',
|
||||
area: 'Transaction::Backend',
|
||||
description: 'Define the transaction backend to detect customers signature in email.',
|
||||
options: {},
|
||||
state: 'Transaction::SignatureDetection',
|
||||
frontend: false
|
||||
)
|
||||
end
|
||||
end
|
29
db/migrate/20160422000003_create_cti_log.rb
Normal file
29
db/migrate/20160422000003_create_cti_log.rb
Normal file
|
@ -0,0 +1,29 @@
|
|||
class CreateCtiLog < ActiveRecord::Migration
|
||||
def up
|
||||
create_table :cti_logs do |t|
|
||||
t.string :direction, limit: 20, null: false
|
||||
t.string :state, limit: 20, null: false
|
||||
t.string :from, limit: 100, null: false
|
||||
t.string :from_comment, limit: 250, null: true
|
||||
t.string :to, limit: 100, null: false
|
||||
t.string :to_comment, limit: 250, null: true
|
||||
t.string :call_id, limit: 250, null: false
|
||||
t.string :comment, limit: 500, null: true
|
||||
t.timestamps null: false
|
||||
end
|
||||
add_index :cti_logs, [:call_id], unique: true
|
||||
add_index :cti_logs, [:direction]
|
||||
add_index :cti_logs, [:from]
|
||||
|
||||
# return if it's a new setup
|
||||
return if !Setting.find_by(name: 'system_init_done')
|
||||
|
||||
Role.create_if_not_exists(
|
||||
name: 'CTI',
|
||||
note: 'Access to CTI feature.',
|
||||
updated_by_id: 1,
|
||||
created_by_id: 1
|
||||
)
|
||||
|
||||
end
|
||||
end
|
49
db/seeds.rb
49
db/seeds.rb
|
@ -1778,6 +1778,15 @@ Setting.create_if_not_exists(
|
|||
state: 'Transaction::Notification',
|
||||
frontend: false
|
||||
)
|
||||
Setting.create_if_not_exists(
|
||||
title: 'Define transaction backend.',
|
||||
name: '1000_signature_detection',
|
||||
area: 'Transaction::Backend',
|
||||
description: 'Define the transaction backend to detect customers signature in email.',
|
||||
options: {},
|
||||
state: 'Transaction::SignatureDetection',
|
||||
frontend: false
|
||||
)
|
||||
Setting.create_if_not_exists(
|
||||
title: 'Define transaction backend.',
|
||||
name: '6000_slack_webhook',
|
||||
|
@ -1822,6 +1831,39 @@ Setting.create_if_not_exists(
|
|||
frontend: false,
|
||||
preferences: { prio: 2 },
|
||||
)
|
||||
Setting.create_if_not_exists(
|
||||
title: 'sipgate.io integration',
|
||||
name: 'sipgate_integration',
|
||||
area: 'Integration::Switch',
|
||||
description: 'Define if sipgate.io (http://www.sipgate.io) is enabled or not.',
|
||||
options: {
|
||||
form: [
|
||||
{
|
||||
display: '',
|
||||
null: true,
|
||||
name: 'sipgate_integration',
|
||||
tag: 'boolean',
|
||||
options: {
|
||||
true => 'yes',
|
||||
false => 'no',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
state: false,
|
||||
preferences: { prio: 1 },
|
||||
frontend: false
|
||||
)
|
||||
Setting.create_if_not_exists(
|
||||
title: 'sipgate.io config',
|
||||
name: 'sipgate_config',
|
||||
area: 'Integration::Sipgate',
|
||||
description: 'Define the sipgate.io config.',
|
||||
options: {},
|
||||
state: {},
|
||||
frontend: false,
|
||||
preferences: { prio: 2 },
|
||||
)
|
||||
|
||||
signature = Signature.create_if_not_exists(
|
||||
id: 1,
|
||||
|
@ -1873,6 +1915,13 @@ Role.create_if_not_exists(
|
|||
updated_by_id: 1,
|
||||
created_by_id: 1
|
||||
)
|
||||
Role.create_if_not_exists(
|
||||
id: 6,
|
||||
name: 'CTI',
|
||||
note: 'Access to CTI feature.',
|
||||
updated_by_id: 1,
|
||||
created_by_id: 1
|
||||
)
|
||||
|
||||
Group.create_if_not_exists(
|
||||
id: 1,
|
||||
|
|
|
@ -131,7 +131,6 @@ returns
|
|||
=end
|
||||
|
||||
def self.by_user_id(user_id)
|
||||
|
||||
type = Ticket::Article::Type.lookup(name: 'email')
|
||||
sender = Ticket::Article::Sender.lookup(name: 'Customer')
|
||||
article_bodies = []
|
||||
|
@ -142,7 +141,7 @@ returns
|
|||
article_bodies.push article.body
|
||||
}
|
||||
|
||||
find_signature( article_bodies )
|
||||
find_signature(article_bodies)
|
||||
end
|
||||
|
||||
=begin
|
||||
|
@ -167,7 +166,7 @@ returns
|
|||
|
||||
=begin
|
||||
|
||||
rebuild signature for user
|
||||
rebuild signature detection for user
|
||||
|
||||
SignatureDetection.rebuild_user(user_id)
|
||||
|
||||
|
|
333
test/integration/sipgate_controller_test.rb
Normal file
333
test/integration/sipgate_controller_test.rb
Normal file
|
@ -0,0 +1,333 @@
|
|||
# encoding: utf-8
|
||||
require 'test_helper'
|
||||
require 'rexml/document'
|
||||
|
||||
class SipgateControllerTest < ActionDispatch::IntegrationTest
|
||||
setup do
|
||||
|
||||
Cti::Log.destroy_all
|
||||
|
||||
Setting.create_or_update(
|
||||
title: 'sipgate.io integration',
|
||||
name: 'sipgate_integration',
|
||||
area: 'Integration::Switch',
|
||||
description: 'Define if sipgate.io (http://www.sipgate.io) is enabled or not.',
|
||||
options: {
|
||||
form: [
|
||||
{
|
||||
display: '',
|
||||
null: true,
|
||||
name: 'sipgate_integration',
|
||||
tag: 'boolean',
|
||||
options: {
|
||||
true => 'yes',
|
||||
false => 'no',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
state: true,
|
||||
preferences: { prio: 1 },
|
||||
frontend: false
|
||||
)
|
||||
Setting.create_or_update(
|
||||
title: 'sipgate.io config',
|
||||
name: 'sipgate_config',
|
||||
area: 'Integration::Sipgate',
|
||||
description: 'Define the sipgate.io config.',
|
||||
options: {},
|
||||
state: {
|
||||
outbound: {
|
||||
routing_table: [
|
||||
{
|
||||
dest: '41*',
|
||||
caller_id: '41715880339000',
|
||||
},
|
||||
{
|
||||
dest: '491714000000',
|
||||
caller_id: '41715880339000',
|
||||
},
|
||||
],
|
||||
default_caller_id: '4930777000000',
|
||||
},
|
||||
inbound: {
|
||||
block_caller_ids: [
|
||||
{
|
||||
caller_id: '491715000000',
|
||||
note: 'some note',
|
||||
}
|
||||
],
|
||||
notify_user_ids: {
|
||||
2 => true,
|
||||
4 => false,
|
||||
},
|
||||
}
|
||||
},
|
||||
frontend: false,
|
||||
preferences: { prio: 2 },
|
||||
)
|
||||
|
||||
groups = Group.where(name: 'Users')
|
||||
roles = Role.where(name: 'Agent')
|
||||
agent = User.create_or_update(
|
||||
login: 'cti-agent@example.com',
|
||||
firstname: 'E',
|
||||
lastname: 'S',
|
||||
email: 'cti-agent@example.com',
|
||||
password: 'agentpw',
|
||||
active: true,
|
||||
roles: roles,
|
||||
groups: groups,
|
||||
updated_by_id: 1,
|
||||
created_by_id: 1,
|
||||
)
|
||||
|
||||
end
|
||||
|
||||
test 'basic call' do
|
||||
|
||||
# inbound - I
|
||||
params = 'event=newCall&direction=in&from=4912347114711&to=4930600000000&callId=4991155921769858278-1&user%5B%5D=user+1&user%5B%5D=user+2'
|
||||
post '/api/v1/sipgate/in', params
|
||||
assert_response(200)
|
||||
on_hangup = nil
|
||||
on_answer = nil
|
||||
content = @response.body
|
||||
response = REXML::Document.new(content)
|
||||
response.elements.each('Response') do |element|
|
||||
on_hangup = element.attributes['onHangup']
|
||||
on_answer = element.attributes['onAnswer']
|
||||
end
|
||||
assert_equal('http://zammad.example.com/api/v1/sipgate/in', on_hangup)
|
||||
assert_equal('http://zammad.example.com/api/v1/sipgate/in', on_answer)
|
||||
|
||||
# inbound - II - block caller
|
||||
params = 'event=newCall&direction=in&from=491715000000&to=4930600000000&callId=4991155921769858278-2&user%5B%5D=user+1&user%5B%5D=user+2'
|
||||
post '/api/v1/sipgate/in', params
|
||||
assert_response(200)
|
||||
on_hangup = nil
|
||||
on_answer = nil
|
||||
content = @response.body
|
||||
response = REXML::Document.new(content)
|
||||
response.elements.each('Response') do |element|
|
||||
on_hangup = element.attributes['onHangup']
|
||||
on_answer = element.attributes['onAnswer']
|
||||
end
|
||||
assert_equal('http://zammad.example.com/api/v1/sipgate/in', on_hangup)
|
||||
assert_equal('http://zammad.example.com/api/v1/sipgate/in', on_answer)
|
||||
reason = nil
|
||||
response.elements.each('Response/Reject') do |element|
|
||||
reason = element.attributes['reason']
|
||||
end
|
||||
assert_equal('busy', reason)
|
||||
|
||||
# outbound - I - set default_caller_id
|
||||
params = 'event=newCall&direction=out&from=4930600000000&to=4912347114711&callId=8621106404543334274-3&user%5B%5D=user+1'
|
||||
post '/api/v1/sipgate/out', params
|
||||
assert_response(200)
|
||||
on_hangup = nil
|
||||
on_answer = nil
|
||||
caller_id = nil
|
||||
number_to_dail = nil
|
||||
content = @response.body
|
||||
response = REXML::Document.new(content)
|
||||
response.elements.each('Response') do |element|
|
||||
on_hangup = element.attributes['onHangup']
|
||||
on_answer = element.attributes['onAnswer']
|
||||
end
|
||||
response.elements.each('Response/Dial') do |element|
|
||||
caller_id = element.attributes['callerId']
|
||||
end
|
||||
response.elements.each('Response/Dial/Number') do |element|
|
||||
number_to_dail = element.text
|
||||
end
|
||||
assert_equal('4930777000000', caller_id)
|
||||
assert_equal('4912347114711', number_to_dail)
|
||||
assert_equal('http://zammad.example.com/api/v1/sipgate/out', on_hangup)
|
||||
assert_equal('http://zammad.example.com/api/v1/sipgate/out', on_answer)
|
||||
|
||||
# outbound - II - set caller_id based on routing_table by explicite number
|
||||
params = 'event=newCall&direction=out&from=4930600000000&to=491714000000&callId=8621106404543334274-4&user%5B%5D=user+1'
|
||||
post '/api/v1/sipgate/out', params
|
||||
assert_response(200)
|
||||
on_hangup = nil
|
||||
on_answer = nil
|
||||
caller_id = nil
|
||||
number_to_dail = nil
|
||||
content = @response.body
|
||||
response = REXML::Document.new(content)
|
||||
response.elements.each('Response') do |element|
|
||||
on_hangup = element.attributes['onHangup']
|
||||
on_answer = element.attributes['onAnswer']
|
||||
end
|
||||
response.elements.each('Response/Dial') do |element|
|
||||
caller_id = element.attributes['callerId']
|
||||
end
|
||||
response.elements.each('Response/Dial/Number') do |element|
|
||||
number_to_dail = element.text
|
||||
end
|
||||
assert_equal('41715880339000', caller_id)
|
||||
assert_equal('491714000000', number_to_dail)
|
||||
assert_equal('http://zammad.example.com/api/v1/sipgate/out', on_hangup)
|
||||
assert_equal('http://zammad.example.com/api/v1/sipgate/out', on_answer)
|
||||
|
||||
# outbound - III - set caller_id based on routing_table by 41*
|
||||
params = 'event=newCall&direction=out&from=4930600000000&to=4147110000000&callId=8621106404543334274-5&user%5B%5D=user+1'
|
||||
post '/api/v1/sipgate/out', params
|
||||
assert_response(200)
|
||||
on_hangup = nil
|
||||
on_answer = nil
|
||||
caller_id = nil
|
||||
number_to_dail = nil
|
||||
content = @response.body
|
||||
response = REXML::Document.new(content)
|
||||
response.elements.each('Response') do |element|
|
||||
on_hangup = element.attributes['onHangup']
|
||||
on_answer = element.attributes['onAnswer']
|
||||
end
|
||||
response.elements.each('Response/Dial') do |element|
|
||||
caller_id = element.attributes['callerId']
|
||||
end
|
||||
response.elements.each('Response/Dial/Number') do |element|
|
||||
number_to_dail = element.text
|
||||
end
|
||||
assert_equal('41715880339000', caller_id)
|
||||
assert_equal('4147110000000', number_to_dail)
|
||||
assert_equal('http://zammad.example.com/api/v1/sipgate/out', on_hangup)
|
||||
assert_equal('http://zammad.example.com/api/v1/sipgate/out', on_answer)
|
||||
|
||||
# no config
|
||||
Setting.set('sipgate_config', {})
|
||||
params = 'event=newCall&direction=in&from=4912347114711&to=4930600000000&callId=4991155921769858278-6&user%5B%5D=user+1&user%5B%5D=user+2'
|
||||
post '/api/v1/sipgate/in', params
|
||||
assert_response(422)
|
||||
error = nil
|
||||
content = @response.body
|
||||
response = REXML::Document.new(content)
|
||||
response.elements.each('Response/Error') do |element|
|
||||
error = element.text
|
||||
end
|
||||
assert_equal('Feature not configured, please contact your admin!', error)
|
||||
|
||||
end
|
||||
|
||||
test 'log call' do
|
||||
|
||||
# outbound - I - new call
|
||||
params = 'event=newCall&direction=out&from=4930600000000&to=4912347114711&callId=1234567890-1&user%5B%5D=user+1'
|
||||
post '/api/v1/sipgate/out', params
|
||||
assert_response(200)
|
||||
log = Cti::Log.find_by(call_id: '1234567890-1')
|
||||
assert(log)
|
||||
assert_equal('4930777000000', log.from)
|
||||
assert_equal('4912347114711', log.to)
|
||||
assert_equal('out', log.direction)
|
||||
assert_equal('user 1', log.from_comment)
|
||||
assert_equal(nil, log.comment)
|
||||
assert_equal('newCall', log.state)
|
||||
|
||||
# outbound - I - hangup by agent
|
||||
params = 'event=hangup&direction=out&callId=1234567890-1&cause=cancel'
|
||||
post '/api/v1/sipgate/out', params
|
||||
assert_response(200)
|
||||
log = Cti::Log.find_by(call_id: '1234567890-1')
|
||||
assert(log)
|
||||
assert_equal('4930777000000', log.from)
|
||||
assert_equal('4912347114711', log.to)
|
||||
assert_equal('out', log.direction)
|
||||
assert_equal('user 1', log.from_comment)
|
||||
assert_equal('cancel', log.comment)
|
||||
assert_equal('hangup', log.state)
|
||||
|
||||
# outbound - II - new call
|
||||
params = 'event=newCall&direction=out&from=4930600000000&to=4912347114711&callId=1234567890-2&user%5B%5D=user+1'
|
||||
post '/api/v1/sipgate/out', params
|
||||
assert_response(200)
|
||||
log = Cti::Log.find_by(call_id: '1234567890-2')
|
||||
assert(log)
|
||||
assert_equal('4930777000000', log.from)
|
||||
assert_equal('4912347114711', log.to)
|
||||
assert_equal('out', log.direction)
|
||||
assert_equal('user 1', log.from_comment)
|
||||
assert_equal(nil, log.comment)
|
||||
assert_equal('newCall', log.state)
|
||||
|
||||
# outbound - II - answer by customer
|
||||
params = 'event=answer&direction=out&callId=1234567890-2&from=4930600000000&to=4912347114711'
|
||||
post '/api/v1/sipgate/out', params
|
||||
assert_response(200)
|
||||
log = Cti::Log.find_by(call_id: '1234567890-2')
|
||||
assert(log)
|
||||
assert_equal('4930777000000', log.from)
|
||||
assert_equal('4912347114711', log.to)
|
||||
assert_equal('out', log.direction)
|
||||
assert_equal('user 1', log.from_comment)
|
||||
assert_equal(nil, log.comment)
|
||||
assert_equal('answer', log.state)
|
||||
|
||||
# outbound - II - hangup by customer
|
||||
params = 'event=hangup&direction=out&callId=1234567890-2&cause=normalClearing&from=4930600000000&to=4912347114711'
|
||||
post '/api/v1/sipgate/out', params
|
||||
assert_response(200)
|
||||
log = Cti::Log.find_by(call_id: '1234567890-2')
|
||||
assert(log)
|
||||
assert_equal('4930777000000', log.from)
|
||||
assert_equal('4912347114711', log.to)
|
||||
assert_equal('out', log.direction)
|
||||
assert_equal('user 1', log.from_comment)
|
||||
assert_equal('normalClearing', log.comment)
|
||||
assert_equal('hangup', log.state)
|
||||
|
||||
# inbound - I - new call
|
||||
params = 'event=newCall&direction=in&to=4930600000000&from=4912347114711&callId=1234567890-3&user%5B%5D=user+1'
|
||||
post '/api/v1/sipgate/in', params
|
||||
assert_response(200)
|
||||
log = Cti::Log.find_by(call_id: '1234567890-3')
|
||||
assert(log)
|
||||
assert_equal('4930600000000', log.to)
|
||||
assert_equal('4912347114711', log.from)
|
||||
assert_equal('in', log.direction)
|
||||
assert_equal('user 1', log.to_comment)
|
||||
assert_equal(nil, log.comment)
|
||||
assert_equal('newCall', log.state)
|
||||
|
||||
# inbound - I - answer by customer
|
||||
params = 'event=answer&direction=in&callId=1234567890-3&to=4930600000000&from=4912347114711'
|
||||
post '/api/v1/sipgate/in', params
|
||||
assert_response(200)
|
||||
log = Cti::Log.find_by(call_id: '1234567890-3')
|
||||
assert(log)
|
||||
assert_equal('4930600000000', log.to)
|
||||
assert_equal('4912347114711', log.from)
|
||||
assert_equal('in', log.direction)
|
||||
assert_equal('user 1', log.to_comment)
|
||||
assert_equal(nil, log.comment)
|
||||
assert_equal('answer', log.state)
|
||||
|
||||
# inbound - I - hangup by customer
|
||||
params = 'event=hangup&direction=in&callId=1234567890-3&cause=normalClearing&to=4930600000000&from=4912347114711'
|
||||
post '/api/v1/sipgate/in', params
|
||||
assert_response(200)
|
||||
log = Cti::Log.find_by(call_id: '1234567890-3')
|
||||
assert(log)
|
||||
assert_equal('4930600000000', log.to)
|
||||
assert_equal('4912347114711', log.from)
|
||||
assert_equal('in', log.direction)
|
||||
assert_equal('user 1', log.to_comment)
|
||||
assert_equal('normalClearing', log.comment)
|
||||
assert_equal('hangup', log.state)
|
||||
|
||||
get '/api/v1/cti/log'
|
||||
assert_response(401)
|
||||
|
||||
headers = { 'ACCEPT' => 'application/json', 'CONTENT_TYPE' => 'application/json' }
|
||||
credentials = ActionController::HttpAuthentication::Basic.encode_credentials('cti-agent@example.com', 'agentpw')
|
||||
get '/api/v1/cti/log', {}, headers.merge('Authorization' => credentials)
|
||||
assert_response(200)
|
||||
result = JSON.parse(@response.body)
|
||||
assert_equal(result.class, Array)
|
||||
assert_equal(3, result.count)
|
||||
|
||||
end
|
||||
|
||||
end
|
|
@ -46,7 +46,7 @@ class ZendeskImportTest < ActiveSupport::TestCase
|
|||
test 'check counts' do
|
||||
assert_equal( 143, User.count, 'users' )
|
||||
assert_equal( 3, Group.count, 'groups' )
|
||||
assert_equal( 5, Role.count, 'roles' )
|
||||
assert_equal( 6, Role.count, 'roles' )
|
||||
assert_equal( 2, Organization.count, 'organizations' )
|
||||
assert_equal( 143, Ticket.count, 'tickets' )
|
||||
assert_equal( 151, Ticket::Article.count, 'ticket articles' )
|
||||
|
|
|
@ -73,6 +73,7 @@ class EmailSignaturDetectionTest < ActiveSupport::TestCase
|
|||
ticket1, article1, user1, mail = Channel::EmailParser.new.process({}, raw_email)
|
||||
assert(ticket1)
|
||||
assert(article1)
|
||||
Delayed::Worker.new.work_off
|
||||
|
||||
# process email II
|
||||
file = File.open("#{Rails.root}/test/fixtures/email_signature_detection/client_a_2.txt", 'rb')
|
||||
|
@ -80,8 +81,6 @@ class EmailSignaturDetectionTest < ActiveSupport::TestCase
|
|||
ticket2, article2, user2, mail = Channel::EmailParser.new.process({}, raw_email)
|
||||
assert(ticket2)
|
||||
assert(article2)
|
||||
|
||||
# process background jobs (user signature detection & article signature detection)
|
||||
Delayed::Worker.new.work_off
|
||||
|
||||
# check if user2 has a signature_detection value
|
||||
|
@ -94,8 +93,10 @@ class EmailSignaturDetectionTest < ActiveSupport::TestCase
|
|||
ticket3, article3, user3, mail = Channel::EmailParser.new.process({}, raw_email)
|
||||
assert(ticket3)
|
||||
assert(article3)
|
||||
Delayed::Worker.new.work_off
|
||||
|
||||
# check if article3 has a signature_detection value
|
||||
article3 = Ticket::Article.find(article3.id)
|
||||
assert_equal(article3.preferences[:signature_detection], 6)
|
||||
|
||||
# relbuild all
|
||||
|
|
|
@ -904,11 +904,11 @@ class TicketNotificationTest < ActiveSupport::TestCase
|
|||
list = EventBuffer.list('transaction')
|
||||
list_objects = Observer::Transaction.get_uniq_changes(list)
|
||||
|
||||
assert_equal('some notification event test 1', list_objects[ticket1.id][:changes]['title'][0])
|
||||
assert_equal('some notification event test 1 - #2', list_objects[ticket1.id][:changes]['title'][1])
|
||||
assert_not(list_objects[ticket1.id][:changes]['priority'])
|
||||
assert_equal(2, list_objects[ticket1.id][:changes]['priority_id'][0])
|
||||
assert_equal(3, list_objects[ticket1.id][:changes]['priority_id'][1])
|
||||
assert_equal('some notification event test 1', list_objects['Ticket'][ticket1.id][:changes]['title'][0])
|
||||
assert_equal('some notification event test 1 - #2', list_objects['Ticket'][ticket1.id][:changes]['title'][1])
|
||||
assert_not(list_objects['Ticket'][ticket1.id][:changes]['priority'])
|
||||
assert_equal(2, list_objects['Ticket'][ticket1.id][:changes]['priority_id'][0])
|
||||
assert_equal(3, list_objects['Ticket'][ticket1.id][:changes]['priority_id'][1])
|
||||
|
||||
# update ticket attributes
|
||||
ticket1.title = "#{ticket1.title} - #3"
|
||||
|
@ -918,11 +918,11 @@ class TicketNotificationTest < ActiveSupport::TestCase
|
|||
list = EventBuffer.list('transaction')
|
||||
list_objects = Observer::Transaction.get_uniq_changes(list)
|
||||
|
||||
assert_equal('some notification event test 1', list_objects[ticket1.id][:changes]['title'][0])
|
||||
assert_equal('some notification event test 1 - #2 - #3', list_objects[ticket1.id][:changes]['title'][1])
|
||||
assert_not(list_objects[ticket1.id][:changes]['priority'])
|
||||
assert_equal(2, list_objects[ticket1.id][:changes]['priority_id'][0])
|
||||
assert_equal(1, list_objects[ticket1.id][:changes]['priority_id'][1])
|
||||
assert_equal('some notification event test 1', list_objects['Ticket'][ticket1.id][:changes]['title'][0])
|
||||
assert_equal('some notification event test 1 - #2 - #3', list_objects['Ticket'][ticket1.id][:changes]['title'][1])
|
||||
assert_not(list_objects['Ticket'][ticket1.id][:changes]['priority'])
|
||||
assert_equal(2, list_objects['Ticket'][ticket1.id][:changes]['priority_id'][0])
|
||||
assert_equal(1, list_objects['Ticket'][ticket1.id][:changes]['priority_id'][1])
|
||||
|
||||
end
|
||||
|
||||
|
|
Loading…
Reference in a new issue