Merge branch 'develop' of github.com:martini/zammad into develop

This commit is contained in:
rkaldung 2015-02-12 20:22:16 +01:00
commit 591c537325
96 changed files with 2198 additions and 676 deletions

View file

@ -206,14 +206,18 @@ class App.Controller extends Spine.Controller
update = => update = =>
ui = @ ui = @
$('.humanTimeFromNow').each( -> $('.humanTimeFromNow').each( ->
item = $(this)
# console.log('rewrite frontendTimeUpdate', this, $(this).hasClass('escalation')) # console.log('rewrite frontendTimeUpdate', this, $(this).hasClass('escalation'))
timestamp = $(this).data('time') ui.frontendTimeUpdateItem(item)
time = ui.humanTime( timestamp, $(this).hasClass('escalation') )
$(this).attr( 'data-tooltip', App.i18n.translateTimestamp(timestamp) )
$(this).html( time )
) )
App.Interval.set( update, 30000, 'frontendTimeUpdate', 'ui' ) App.Interval.set( update, 30000, 'frontendTimeUpdate', 'ui' )
frontendTimeUpdateItem: (item) =>
timestamp = item.data('time')
time = @humanTime( timestamp, item.hasClass('escalation') )
item.attr( 'data-tooltip', App.i18n.translateTimestamp(timestamp) )
item.html( time )
ticketPopups: (position = 'right') -> ticketPopups: (position = 'right') ->
# open ticket in new task if curent user agent # open ticket in new task if curent user agent
@ -241,12 +245,17 @@ class App.Controller extends Spine.Controller
ticket = App.Ticket.fullLocal( ticket_id ) ticket = App.Ticket.fullLocal( ticket_id )
App.Utils.htmlEscape( ticket.title ) App.Utils.htmlEscape( ticket.title )
content: -> content: ->
ticket_id = $(@).data('id') ticket_id = $(@).data('id')
ticket = App.Ticket.fullLocal( ticket_id ) ticket = App.Ticket.fullLocal( ticket_id )
ticket.humanTime = ui.humanTime(ticket.created_at) html = App.view('popover/ticket')(
App.view('popover/ticket')(
ticket: ticket ticket: ticket
) )
html = $( html )
html.find('.humanTimeFromNow').each( ->
item = $(this)
ui.frontendTimeUpdateItem(item)
)
html
) )
ticketPopupsDestroy: => ticketPopupsDestroy: =>
@ -369,7 +378,7 @@ class App.Controller extends Spine.Controller
userTicketPopups: (params) -> userTicketPopups: (params) ->
show = (data, tickets) => show = (data, ticket_list) =>
if !data.position if !data.position
data.position = 'left' data.position = 'left'
@ -377,7 +386,7 @@ class App.Controller extends Spine.Controller
@userTicketPopupsDestroy() @userTicketPopupsDestroy()
# show user popup # show user popup
controller = @ ui = @
@userTicketPopupsList = @el.find(data.selector).popover( @userTicketPopupsList = @el.find(data.selector).popover(
trigger: 'hover' trigger: 'hover'
container: 'body' container: 'body'
@ -390,16 +399,21 @@ class App.Controller extends Spine.Controller
content: -> content: ->
type = $(@).filter('[data-type]').data('type') type = $(@).filter('[data-type]').data('type')
data = tickets[type] || [] tickets = []
if ticket_list[type]
# set human time for ticket_id in ticket_list[type]
for ticket in data tickets.push App.Ticket.fullLocal( ticket_id )
ticket.humanTime = controller.humanTime(ticket.created_at)
# insert data # insert data
App.view('popover/user_ticket_list')( html = App.view('popover/user_ticket_list')(
tickets: data, tickets: tickets
) )
html = $( html )
html.find('.humanTimeFromNow').each( ->
item = $(this)
ui.frontendTimeUpdateItem(item)
)
html
) )
fetch = (params) => fetch = (params) =>
@ -411,14 +425,18 @@ class App.Controller extends Spine.Controller
} }
processData: true, processData: true,
success: (data, status, xhr) => success: (data, status, xhr) =>
App.Store.write( "user-ticket-popover::#{params.user_id}", data.tickets ) App.Store.write( "user-ticket-popover::#{params.user_id}", data )
show( params, data.tickets )
# load assets
App.Collection.loadAssets( data.assets )
show( params, { open: data.ticket_ids_open, closed: data.ticket_ids_closed } )
) )
# get data # get data
tickets = App.Store.get( "user-ticket-popover::#{params.user_id}" ) data = App.Store.get( "user-ticket-popover::#{params.user_id}" )
if tickets if data
show( params, tickets ) show( params, { open: data.ticket_ids_open, closed: data.ticket_ids_closed } )
@delay( @delay(
=> =>
fetch(params) fetch(params)
@ -556,10 +574,10 @@ class App.ControllerModal extends App.Controller
@el.addClass('modal--local') @el.addClass('modal--local')
@el.modal @el.modal
keyboard: @keyboard keyboard: @keyboard
show: true show: true
backdrop: @backdrop backdrop: @backdrop
container: @container container: @container
.on .on
'show.bs.modal': @onShow 'show.bs.modal': @onShow
'shown.bs.modal': @onShown 'shown.bs.modal': @onShown

View file

@ -175,7 +175,7 @@ class App.ControllerForm extends App.Controller
### ###
formGenItem: (attribute_config, classname, form, attribute_count ) -> formGenItem: (attribute_config, classname, form, attribute_count ) ->
attribute = clone( attribute_config ) attribute = clone( attribute_config, true )
# create item id # create item id
attribute.id = classname + '_' + attribute.name attribute.id = classname + '_' + attribute.name
@ -260,8 +260,8 @@ class App.ControllerForm extends App.Controller
# build options list # build options list
if _.isEmpty(attribute.options) if _.isEmpty(attribute.options)
attribute.options = [ attribute.options = [
{ name: 'active', value: true } { name: 'yes', value: true }
{ name: 'inactive', value: false } { name: 'no', value: false }
] ]
# set data type # set data type
@ -276,6 +276,29 @@ class App.ControllerForm extends App.Controller
# return item # return item
item = $( App.view('generic/select')( attribute: attribute ) ) item = $( App.view('generic/select')( attribute: attribute ) )
else if attribute.tag is 'active'
# active attribute is always required
attribute.null = false
# build options list
attribute.options = [
{ name: 'active', value: true }
{ name: 'inactive', value: false }
]
# set data type
if attribute.name
attribute.name = '{boolean}' + attribute.name
# finde selected item of list
for record in attribute.options
if record.value is attribute.value
record.selected = 'selected'
# return item
item = $( App.view('generic/select')( attribute: attribute ) )
# select # select
else if attribute.tag is 'select' else if attribute.tag is 'select'
item = $( App.view('generic/select')( attribute: attribute ) ) item = $( App.view('generic/select')( attribute: attribute ) )
@ -320,12 +343,13 @@ class App.ControllerForm extends App.Controller
number number
if !reset && (year isnt '' && month isnt '' && day isnt '') if !reset && (year isnt '' && month isnt '' && day isnt '')
time = new Date( Date.parse( "#{year}-#{format(month)}-#{format(day)}T00:00:00Z" ) ) time = new Date( Date.parse( "#{year}-#{format(month)}-#{format(day)}T00:00:00Z" ) )
time.setMinutes( time.getMinutes() + diff + time.getTimezoneOffset() )
else else
time = new Date() time = new Date()
#time.setMinutes( time.getMinutes() + diff + time.getTimezoneOffset() ) time.setMinutes( time.getMinutes() + diff )
item.closest('.form-group').find("[name=\"{date}#{name}___day\"]").val( time.getUTCDate() ) item.closest('.form-group').find("[name=\"{date}#{name}___day\"]").val( time.getDate() )
item.closest('.form-group').find("[name=\"{date}#{name}___month\"]").val( time.getUTCMonth()+1 ) item.closest('.form-group').find("[name=\"{date}#{name}___month\"]").val( time.getMonth()+1 )
item.closest('.form-group').find("[name=\"{date}#{name}___year\"]").val( time.getUTCFullYear() ) item.closest('.form-group').find("[name=\"{date}#{name}___year\"]").val( time.getFullYear() )
item.find('.js-today').bind('click', (e) -> item.find('.js-today').bind('click', (e) ->
e.preventDefault() e.preventDefault()
@ -462,9 +486,10 @@ class App.ControllerForm extends App.Controller
number number
if !reset && (year isnt '' && month isnt '' && day isnt '' && hour isnt '' && day isnt '') if !reset && (year isnt '' && month isnt '' && day isnt '' && hour isnt '' && day isnt '')
time = new Date( Date.parse( "#{year}-#{format(month)}-#{format(day)}T#{format(hour)}:#{format(minute)}:00Z" ) ) time = new Date( Date.parse( "#{year}-#{format(month)}-#{format(day)}T#{format(hour)}:#{format(minute)}:00Z" ) )
time.setMinutes( time.getMinutes() + diff + time.getTimezoneOffset() )
else else
time = new Date() time = new Date()
time.setMinutes( time.getMinutes() + diff + time.getTimezoneOffset() ) time.setMinutes( time.getMinutes() + diff )
#console.log('T', time, time.getHours(), time.getMinutes()) #console.log('T', time, time.getHours(), time.getMinutes())
item.closest('.form-group').find("[name=\"{datetime}#{name}___day\"]").val( time.getDate() ) item.closest('.form-group').find("[name=\"{datetime}#{name}___day\"]").val( time.getDate() )
item.closest('.form-group').find("[name=\"{datetime}#{name}___month\"]").val( time.getMonth()+1 ) item.closest('.form-group').find("[name=\"{datetime}#{name}___month\"]").val( time.getMonth()+1 )

View file

@ -2,15 +2,15 @@ class App.ControllerGenericNew extends App.ControllerModal
constructor: (params) -> constructor: (params) ->
super super
@head = App.i18n.translateContent( 'New' ) + ': ' + App.i18n.translateContent( @pageData.object ) @head = App.i18n.translateContent( 'New' ) + ': ' + App.i18n.translateContent( @pageData.object )
@cancel = true @cancel = true
@button = true @button = true
controller = new App.ControllerForm( controller = new App.ControllerForm(
model: App[ @genericObject ] model: App[ @genericObject ]
params: @item params: @item
screen: @screen || 'edit' screen: @screen || 'edit'
autofocus: true autofocus: true
) )
@content = controller.form @content = controller.form
@ -98,8 +98,8 @@ class App.ControllerGenericEdit extends App.ControllerModal
class App.ControllerGenericIndex extends App.Controller class App.ControllerGenericIndex extends App.Controller
events: events:
'click [data-type=edit]': 'edit' 'click [data-type = edit]': 'edit'
'click [data-type=new]': 'new' 'click [data-type = new]': 'new'
constructor: -> constructor: ->
super super
@ -160,6 +160,7 @@ class App.ControllerGenericIndex extends App.Controller
bindRow: bindRow:
events: events:
'click': @edit 'click': @edit
container: @container
}, },
@pageData.tableExtend @pageData.tableExtend
) )
@ -177,6 +178,7 @@ class App.ControllerGenericIndex extends App.Controller
id: item.id id: item.id
pageData: @pageData pageData: @pageData
genericObject: @genericObject genericObject: @genericObject
container: @container
) )
new: (e) -> new: (e) ->
@ -184,6 +186,7 @@ class App.ControllerGenericIndex extends App.Controller
new App.ControllerGenericNew( new App.ControllerGenericNew(
pageData: @pageData pageData: @pageData
genericObject: @genericObject genericObject: @genericObject
container: @container
) )
class App.ControllerGenericDestroyConfirm extends App.ControllerModal class App.ControllerGenericDestroyConfirm extends App.ControllerModal

View file

@ -82,7 +82,7 @@ class App.ControllerTable extends App.Controller
el: element el: element
overview: ['time', 'area', 'level', 'browser', 'location', 'data'] overview: ['time', 'area', 'level', 'browser', 'location', 'data']
attributes: [ attributes: [
{ name: 'time', display: 'Time', type: 'time' }, { name: 'time', display: 'Time', tag: 'datetime' },
{ name: 'area', display: 'Area', type: 'text' }, { name: 'area', display: 'Area', type: 'text' },
{ name: 'level', display: 'Level', type: 'text' }, { name: 'level', display: 'Level', type: 'text' },
{ name: 'browser', display: 'Browser', type: 'text' }, { name: 'browser', display: 'Browser', type: 'text' },
@ -235,13 +235,14 @@ class App.ControllerTable extends App.Controller
# bind on delete dialog # bind on delete dialog
if data.model && destroy if data.model && destroy
table.delegate('[data-type="destroy"]', 'click', (e) -> table.delegate('[data-type="destroy"]', 'click', (e) =>
e.stopPropagation() e.stopPropagation()
e.preventDefault() e.preventDefault()
itemId = $(e.target).parents('tr').data('id') itemId = $(e.target).parents('tr').data('id')
item = data.model.find(itemId) item = data.model.find(itemId)
new App.ControllerGenericDestroyConfirm( new App.ControllerGenericDestroyConfirm(
item: item item: item
container: @container
) )
) )

View file

@ -64,11 +64,16 @@ class App.ChannelEmailFilter extends App.Controller
new: (e) => new: (e) =>
e.preventDefault() e.preventDefault()
new App.ChannelEmailFilterEdit( {} ) new App.ChannelEmailFilterEdit(
container: @el.closest('.content')
)
edit: (id, e) => edit: (id, e) =>
e.preventDefault() e.preventDefault()
new App.ChannelEmailFilterEdit( object: App.PostmasterFilter.find(id) ) new App.ChannelEmailFilterEdit(
object: App.PostmasterFilter.find(id)
container: @el.closest('.content')
)
class App.ChannelEmailFilterEdit extends App.ControllerModal class App.ChannelEmailFilterEdit extends App.ControllerModal
constructor: -> constructor: ->
@ -152,12 +157,17 @@ class App.ChannelEmailAddress extends App.Controller
new: (e) => new: (e) =>
e.preventDefault() e.preventDefault()
new App.ChannelEmailAddressEdit( {} ) new App.ChannelEmailAddressEdit(
container: @el.closest('.content')
)
edit: (id, e) => edit: (id, e) =>
e.preventDefault() e.preventDefault()
item = App.EmailAddress.find(id) item = App.EmailAddress.find(id)
new App.ChannelEmailAddressEdit( object: item ) new App.ChannelEmailAddressEdit(
object: item
container: @el.closest('.content')
)
class App.ChannelEmailAddressEdit extends App.ControllerModal class App.ChannelEmailAddressEdit extends App.ControllerModal
constructor: -> constructor: ->
@ -238,12 +248,17 @@ class App.ChannelEmailSignature extends App.Controller
new: (e) => new: (e) =>
e.preventDefault() e.preventDefault()
new App.ChannelEmailSignatureEdit( {} ) new App.ChannelEmailSignatureEdit(
container: @el.closest('.content')
)
edit: (id, e) => edit: (id, e) =>
e.preventDefault() e.preventDefault()
item = App.Signature.find(id) item = App.Signature.find(id)
new App.ChannelEmailSignatureEdit( object: item ) new App.ChannelEmailSignatureEdit(
object: item
container: @el.closest('.content')
)
class App.ChannelEmailSignatureEdit extends App.ControllerModal class App.ChannelEmailSignatureEdit extends App.ControllerModal
constructor: -> constructor: ->
@ -324,12 +339,17 @@ class App.ChannelEmailInbound extends App.Controller
new: (e) => new: (e) =>
e.preventDefault() e.preventDefault()
new App.ChannelEmailInboundEdit( {} ) new App.ChannelEmailInboundEdit(
container: @el.closest('.content')
)
edit: (id, e) => edit: (id, e) =>
e.preventDefault() e.preventDefault()
item = App.Channel.find(id) item = App.Channel.find(id)
new App.ChannelEmailInboundEdit( object: item ) new App.ChannelEmailInboundEdit(
object: item
container: @el.closest('.content')
)
class App.ChannelEmailInboundEdit extends App.ControllerModal class App.ChannelEmailInboundEdit extends App.ControllerModal

View file

@ -2,9 +2,8 @@ App.Config.set( 'User', {
prio: 1000, prio: 1000,
parent: '', parent: '',
callback: -> callback: ->
item = {} item = {}
item['name'] = App.Session.get( 'login' ) item['name'] = App.Session.get( 'login' )
item['image'] = App.Session.get( 'imageUrl' )
if App.Session.get() if App.Session.get()
item['avatar'] = App.Session.get().avatar() item['avatar'] = App.Session.get().avatar()
return item return item

View file

@ -473,8 +473,8 @@ class Sidebar extends App.Controller
) )
new App.WidgetUser( new App.WidgetUser(
el: el el: el
user_id: user.id user_id: user.id
) )
editCustomer = (e, el) => editCustomer = (e, el) =>
@ -483,9 +483,10 @@ class Sidebar extends App.Controller
genericObject: 'User' genericObject: 'User'
screen: 'edit' screen: 'edit'
pageData: pageData:
title: 'Users' title: 'Users'
object: 'User' object: 'User'
objects: 'Users' objects: 'Users'
container: @el.closest('.content')
) )
items.push { items.push {
head: 'Customer' head: 'Customer'
@ -493,9 +494,9 @@ class Sidebar extends App.Controller
icon: 'person' icon: 'person'
actions: [ actions: [
{ {
title: 'Edit Customer' title: 'Edit Customer'
name: 'Edit Customer' name: 'Edit Customer'
class: 'glyphicon glyphicon-edit' class: 'glyphicon glyphicon-edit'
callback: editCustomer callback: editCustomer
}, },
] ]
@ -508,14 +509,15 @@ class Sidebar extends App.Controller
id: user.organization_id id: user.organization_id
genericObject: 'Organization' genericObject: 'Organization'
pageData: pageData:
title: 'Organizations' title: 'Organizations'
object: 'Organization' object: 'Organization'
objects: 'Organizations' objects: 'Organizations'
container: @el.closest('.content')
) )
showOrganization = (el) => showOrganization = (el) =>
new App.WidgetOrganization( new App.WidgetOrganization(
el: el el: el
organization_id: user.organization_id organization_id: user.organization_id
) )
items.push { items.push {
head: 'Organization' head: 'Organization'
@ -523,6 +525,7 @@ class Sidebar extends App.Controller
icon: 'group' icon: 'group'
actions: [ actions: [
{ {
title: 'Edit Organization'
name: 'Edit Organization' name: 'Edit Organization'
class: 'glyphicon glyphicon-edit' class: 'glyphicon glyphicon-edit'
callback: editOrganization callback: editOrganization

View file

@ -6,22 +6,22 @@ class Index extends App.ControllerContent
return if !@authenticate() return if !@authenticate()
new App.ControllerGenericIndex( new App.ControllerGenericIndex(
el: @el, el: @el
id: @id, id: @id
genericObject: 'Group', genericObject: 'Group'
pageData: { pageData:
title: 'Groups', title: 'Groups'
home: 'groups', home: 'groups'
object: 'Group', object: 'Group'
objects: 'Groups', objects: 'Groups'
navupdate: '#groups', navupdate: '#groups'
notes: [ notes: [
'Groups are ...' 'Groups are ...'
], ]
buttons: [ buttons: [
{ name: 'New Group', 'data-type': 'new', class: 'btn--success' }, { name: 'New Group', 'data-type': 'new', class: 'btn--success' },
], ]
}, container: @el.closest('.content')
) )
App.Config.set( 'Group', { prio: 1500, name: 'Groups', parent: '#manage', target: '#manage/groups', controller: Index, role: ['Admin'] }, 'NavBarAdmin' ) App.Config.set( 'Group', { prio: 1500, name: 'Groups', parent: '#manage', target: '#manage/groups', controller: Index, role: ['Admin'] }, 'NavBarAdmin' )

View file

@ -15,11 +15,11 @@ class Index extends App.ControllerContent
e.preventDefault() e.preventDefault()
params = @formParam(e.target) params = @formParam(e.target)
App.Event.trigger( App.Event.trigger(
'ws:send' 'ws:send'
action: 'broadcast' action: 'broadcast'
event: 'session:maintenance' event: 'session:maintenance'
spool: false spool: false
data: params data: params
) )
@notify @notify
type: 'success' type: 'success'

View file

@ -156,11 +156,8 @@ class App.Navigation extends App.Controller
area.result = [] area.result = []
for id in area.ids for id in area.ids
ticket = App.Ticket.find( id ) ticket = App.Ticket.find( id )
ticket.humanTime = @humanTime(ticket.created_at)
data = data =
display: "##{ticket.number} - #{ticket.title}" display: "##{ticket.number} - #{ticket.title}"
createt_at: "#{ticket.created_at}"
humanTime: "#{ticket.humanTime}"
id: ticket.id id: ticket.id
class: "task level-1 ticket-popover" class: "task level-1 ticket-popover"
url: ticket.uiUrl() url: ticket.uiUrl()

View file

@ -7,10 +7,10 @@ class Index extends App.ControllerTabs
# get data # get data
@ajax( @ajax(
id: 'object_manager_attributes_list', id: 'object_manager_attributes_list'
type: 'GET', type: 'GET'
url: @apiPath + '/object_manager_attributes_list', url: @apiPath + '/object_manager_attributes_list'
processData: true, processData: true
success: (data, status, xhr) => success: (data, status, xhr) =>
@build(data.objects) @build(data.objects)
) )
@ -19,9 +19,9 @@ class Index extends App.ControllerTabs
@tabs = [] @tabs = []
for object in objects for object in objects
item = item =
name: object, name: object
target: "c-#{object}", target: "c-#{object}"
controller: Items, controller: Items
params: params:
object: object object: object
@tabs.push item @tabs.push item
@ -120,18 +120,19 @@ class Items extends App.ControllerContent
objects: 'ObjectManagerAttributes' objects: 'ObjectManagerAttributes'
navupdate: '#object_manager' navupdate: '#object_manager'
genericObject: 'ObjectManagerAttribute' genericObject: 'ObjectManagerAttribute'
container: @el.closest('.content')
) )
edit: (e) => edit: (e) =>
e.preventDefault() e.preventDefault()
id = $( e.target ).closest('tr').data('id') id = $( e.target ).closest('tr').data('id')
new Edit( new Edit(
pageData: { pageData:
object: 'ObjectManagerAttribute' object: 'ObjectManagerAttribute'
},
genericObject: 'ObjectManagerAttribute' genericObject: 'ObjectManagerAttribute'
callback: @render callback: @render
id: id id: id
container: @el.closest('.content')
) )
destroy: (e) -> destroy: (e) ->
@ -158,7 +159,6 @@ class Edit extends App.ControllerModal
items: [] items: []
) ) ) )
item = App.ObjectManagerAttribute.find(@id) item = App.ObjectManagerAttribute.find(@id)
options = options =
@ -249,9 +249,8 @@ class Edit extends App.ControllerModal
@content.find('[name=data_type]').trigger('change') @content.find('[name=data_type]').trigger('change')
configureAttributesBottom = [ configureAttributesBottom = [
{ name: 'active', display: 'Active', tag: 'boolean', 'default': true, 'null': false }, { name: 'active', display: 'Active', tag: 'active', default: true },
] ]
controller = new App.ControllerForm( controller = new App.ControllerForm(
model: { configure_attributes: configureAttributesBottom, className: '' }, model: { configure_attributes: configureAttributesBottom, className: '' },
@ -263,8 +262,6 @@ class Edit extends App.ControllerModal
#@content = controller.form #@content = controller.form
#@show(content) #@show(content)
@show() @show()

View file

@ -7,9 +7,9 @@ class App.OrganizationHistory extends App.GenericHistory
# get data # get data
@ajax( @ajax(
id: 'organization_history', id: 'organization_history'
type: 'GET', type: 'GET'
url: @apiPath + '/organizations/history/' + @organization_id, url: @apiPath + '/organizations/history/' + @organization_id
success: (data, status, xhr) => success: (data, status, xhr) =>
# load assets # load assets

View file

@ -1,7 +1,4 @@
class App.OrganizationProfile extends App.Controller class App.OrganizationProfile extends App.Controller
events:
'focusout [contenteditable]': 'update'
constructor: (params) -> constructor: (params) ->
super super
@ -12,11 +9,8 @@ class App.OrganizationProfile extends App.Controller
@navupdate '#' @navupdate '#'
# subscribe and reload data / fetch new data if triggered # fetch new data if needed
@subscribeId = App.Organization.full( @organization_id, @render, false, true ) App.Organization.full( @organization_id, @render )
release: =>
App.Organization.unsubscribe(@subscribeId)
meta: => meta: =>
meta = meta =
@ -48,6 +42,39 @@ class App.OrganizationProfile extends App.Controller
@doNotLog = 1 @doNotLog = 1
@recentView( 'Organization', @organization_id ) @recentView( 'Organization', @organization_id )
@html App.view('organization_profile/index')(
organization: organization
)
new Object(
el: @$('.js-object-container')
organization: organization
)
new App.TicketStats(
el: @$('.js-ticket-stats')
organization: organization
)
new App.UpdateTastbar(
genericObject: organization
)
class Object extends App.Controller
events:
'focusout [contenteditable]': 'update'
constructor: (params) ->
super
# subscribe and reload data / fetch new data if triggered
@subscribeId = App.Organization.full( @organization.id, @render, false, true )
release: =>
App.Organization.unsubscribe(@subscribeId)
render: (organization) =>
# get display data # get display data
organizationData = [] organizationData = []
for attributeName, attributeConfig of App.Organization.attributesGet('view') for attributeName, attributeConfig of App.Organization.attributesGet('view')
@ -65,7 +92,7 @@ class App.OrganizationProfile extends App.Controller
if name isnt 'name' if name isnt 'name'
organizationData.push attributeConfig organizationData.push attributeConfig
@html App.view('organization_profile')( @html App.view('organization_profile/object')(
organization: organization organization: organization
organizationData: organizationData organizationData: organizationData
) )
@ -76,15 +103,6 @@ class App.OrganizationProfile extends App.Controller
maxlength: 250 maxlength: 250
}) })
new App.TicketStats(
el: @$('.js-ticket-stats')
organization: organization
)
new App.UpdateTastbar(
genericObject: organization
)
# start action controller # start action controller
showHistory = => showHistory = =>
new App.OrganizationHistory( organization_id: organization.id ) new App.OrganizationHistory( organization_id: organization.id )
@ -97,6 +115,7 @@ class App.OrganizationProfile extends App.Controller
title: 'Organizations' title: 'Organizations'
object: 'Organization' object: 'Organization'
objects: 'Organizations' objects: 'Organizations'
container: @el.closest('.content')
) )
actions = [ actions = [
@ -120,13 +139,14 @@ class App.OrganizationProfile extends App.Controller
update: (e) => update: (e) =>
name = $(e.target).attr('data-name') name = $(e.target).attr('data-name')
value = $(e.target).html() value = $(e.target).html()
org = App.Organization.find( @organization_id ) org = App.Organization.find( @organization.id )
if org[name] isnt value if org[name] isnt value
data = {} data = {}
data[name] = value data[name] = value
org.updateAttributes( data ) org.updateAttributes( data )
@log 'notice', 'update', name, value, org @log 'notice', 'update', name, value, org
class Router extends App.ControllerPermanent class Router extends App.ControllerPermanent
constructor: (params) -> constructor: (params) ->
super super

View file

@ -6,22 +6,22 @@ class Index extends App.ControllerContent
return if !@authenticate() return if !@authenticate()
new App.ControllerGenericIndex( new App.ControllerGenericIndex(
el: @el, el: @el
id: @id, id: @id
genericObject: 'Organization', genericObject: 'Organization'
pageData: { pageData:
title: 'Organizations', title: 'Organizations'
home: 'organizations', home: 'organizations'
object: 'Organization', object: 'Organization'
objects: 'Organizations', objects: 'Organizations'
navupdate: '#organizations', navupdate: '#organizations'
notes: [ notes: [
'Organizations are for any person in the system. Agents (Owners, Resposbiles, ...) and Customers.' 'Organizations are for any person in the system. Agents (Owners, Resposbiles, ...) and Customers.'
], ]
buttons: [ buttons: [
{ name: 'New Organization', 'data-type': 'new', class: 'btn--success' }, { name: 'New Organization', 'data-type': 'new', class: 'btn--success' }
], ]
}, container: @el.closest('.content')
) )
App.Config.set( 'Organization', { prio: 2000, name: 'Organizations', parent: '#manage', target: '#manage/organizations', controller: Index, role: ['Admin'] }, 'NavBarAdmin' ) App.Config.set( 'Organization', { prio: 2000, name: 'Organizations', parent: '#manage', target: '#manage/organizations', controller: Index, role: ['Admin'] }, 'NavBarAdmin' )

View file

@ -6,22 +6,22 @@ class Index extends App.ControllerContent
return if !@authenticate() return if !@authenticate()
new App.ControllerGenericIndex( new App.ControllerGenericIndex(
el: @el, el: @el
id: @id, id: @id
genericObject: 'Overview', genericObject: 'Overview'
pageData: { pageData:
title: 'Overviews', title: 'Overviews'
home: 'overviews', home: 'overviews'
object: 'Overview', object: 'Overview'
objects: 'Overviews', objects: 'Overviews'
navupdate: '#overviews', navupdate: '#overviews'
notes: [ notes: [
'Overview are ...' 'Overview are ...'
], ]
buttons: [ buttons: [
{ name: 'New Overview', 'data-type': 'new', class: 'btn--success' }, { name: 'New Overview', 'data-type': 'new', class: 'btn--success' }
], ]
}, container: @el.closest('.content')
) )
App.Config.set( 'Overview', { prio: 2300, name: 'Overviews', parent: '#manage', target: '#manage/overviews', controller: Index, role: ['Admin'] }, 'NavBarAdmin' ) App.Config.set( 'Overview', { prio: 2300, name: 'Overviews', parent: '#manage', target: '#manage/overviews', controller: Index, role: ['Admin'] }, 'NavBarAdmin' )

View file

@ -6,22 +6,22 @@ class Index extends App.ControllerContent
return if !@authenticate() return if !@authenticate()
new App.ControllerGenericIndex( new App.ControllerGenericIndex(
el: @el, el: @el
id: @id, id: @id
genericObject: 'Job', genericObject: 'Job'
pageData: { pageData:
title: 'Schedulers', title: 'Schedulers'
home: 'schedulers', home: 'schedulers'
object: 'Scheduler', object: 'Scheduler'
objects: 'Schedulers', objects: 'Schedulers'
navupdate: '#schedulers', navupdate: '#schedulers'
notes: [ notes: [
'Scheduler are ...' 'Scheduler are ...'
], ]
buttons: [ buttons: [
{ name: 'New Scheduler', 'data-type': 'new', class: 'btn--success' }, { name: 'New Scheduler', 'data-type': 'new', class: 'btn--success' }
], ]
}, container: @el.closest('.content')
) )
App.Config.set( 'Scheduler', { prio: 3000, name: 'Schedulers', parent: '#manage', target: '#manage/schedulers', controller: Index, role: ['Admin'] }, 'NavBarAdmin' ) App.Config.set( 'Scheduler', { prio: 3000, name: 'Schedulers', parent: '#manage', target: '#manage/schedulers', controller: Index, role: ['Admin'] }, 'NavBarAdmin' )

View file

@ -6,22 +6,22 @@ class Index extends App.ControllerContent
return if !@authenticate() return if !@authenticate()
new App.ControllerGenericIndex( new App.ControllerGenericIndex(
el: @el, el: @el
id: @id, id: @id
genericObject: 'Sla', genericObject: 'Sla'
pageData: { pageData:
title: 'SLA', title: 'SLA'
home: 'slas', home: 'slas'
object: 'SLA', object: 'SLA'
objects: 'SLAs', objects: 'SLAs'
navupdate: '#slas', navupdate: '#slas'
notes: [ notes: [
# 'SLA are ...' # 'SLA are ...'
], ]
buttons: [ buttons: [
{ name: 'New SLA', 'data-type': 'new', class: 'btn--success' }, { name: 'New SLA', 'data-type': 'new', class: 'btn--success' }
], ]
}, container: @el.closest('.content')
) )
App.Config.set( 'Sla', { prio: 2900, name: 'SLAs', parent: '#manage', target: '#manage/slas', controller: Index, role: ['Admin'] }, 'NavBarAdmin' ) App.Config.set( 'Sla', { prio: 2900, name: 'SLAs', parent: '#manage', target: '#manage/slas', controller: Index, role: ['Admin'] }, 'NavBarAdmin' )

View file

@ -6,22 +6,22 @@ class Index extends App.ControllerContent
return if !@authenticate() return if !@authenticate()
new App.ControllerGenericIndex( new App.ControllerGenericIndex(
el: @el, el: @el
id: @id, id: @id
genericObject: 'TextModule', genericObject: 'TextModule'
pageData: { pageData:
title: 'TextModules', title: 'TextModules'
home: 'text_modules', home: 'text_modules'
object: 'TextModule', object: 'TextModule'
objects: 'TextModules', objects: 'TextModules'
navupdate: '#text_modules', navupdate: '#text_modules'
notes: [ notes: [
'TextModules are ...' 'TextModules are ...'
], ]
buttons: [ buttons: [
{ name: 'New TextModule', 'data-type': 'new', class: 'btn--success' }, { name: 'New TextModule', 'data-type': 'new', class: 'btn--success' }
], ]
}, container: @el.closest('.content')
) )
App.Config.set( 'TextModule', { prio: 2300, name: 'TextModules', parent: '#manage', target: '#manage/text_modules', controller: Index, role: ['Admin'] }, 'NavBarAdmin' ) App.Config.set( 'TextModule', { prio: 2300, name: 'TextModules', parent: '#manage', target: '#manage/text_modules', controller: Index, role: ['Admin'] }, 'NavBarAdmin' )

View file

@ -482,7 +482,7 @@ class Table extends App.ControllerContent
new App.OverviewSettings( new App.OverviewSettings(
overview_id: @overview.id overview_id: @overview.id
view_mode: @view_mode view_mode: @view_mode
container: @el container: @el.closest('.content')
) )
class App.OverviewSettings extends App.ControllerModal class App.OverviewSettings extends App.ControllerModal

View file

@ -124,17 +124,27 @@ class App.TicketZoom extends App.Controller
@doNotLog = 1 @doNotLog = 1
@recentView( 'Ticket', ticket_id ) @recentView( 'Ticket', ticket_id )
error: (xhr, status, error) => error: (xhr) =>
statusText = xhr.statusText
status = xhr.status
detail = xhr.responseText
#console.log('error', status, statusText)
# do not close window if request is aborted # ignore if request is aborted
return if status is 'abort' if statusText is 'abort'
return
# if ticket is already loaded, ignore status "0" - network issues e. g. temp. not connection
if @ticketUpdatedAtLastCall && status is 0
console.log('network issues e. g. temp. not connection', status, statusText, detail)
return
# show error message # show error message
if xhr.status is 401 || error is 'Unauthorized' if status is 401 || statusText is 'Unauthorized'
@taskHead = '» ' + App.i18n.translateInline('Unauthorized') + ' «' @taskHead = '» ' + App.i18n.translateInline('Unauthorized') + ' «'
@taskIconClass = 'error' @taskIconClass = 'error'
@html App.view('generic/error/unauthorized')( objectName: 'Ticket' ) @html App.view('generic/error/unauthorized')( objectName: 'Ticket' )
else if xhr.status is 404 || error is 'Not Found' else if status is 404 || statusText is 'Not Found'
@taskHead = '» ' + App.i18n.translateInline('Not Found') + ' «' @taskHead = '» ' + App.i18n.translateInline('Not Found') + ' «'
@taskIconClass = 'error' @taskIconClass = 'error'
@html App.view('generic/error/not_found')( objectName: 'Ticket' ) @html App.view('generic/error/not_found')( objectName: 'Ticket' )
@ -142,9 +152,7 @@ class App.TicketZoom extends App.Controller
@taskHead = '» ' + App.i18n.translateInline('Error') + ' «' @taskHead = '» ' + App.i18n.translateInline('Error') + ' «'
@taskIconClass = 'error' @taskIconClass = 'error'
status = xhr.status if !detail
detail = xhr.responseText
if !status && !detail
detail = 'General communication error, maybe internet is not available!' detail = 'General communication error, maybe internet is not available!'
@html App.view('generic/error/generic')( @html App.view('generic/error/generic')(
status: status status: status
@ -321,21 +329,26 @@ class App.TicketZoom extends App.Controller
) )
showTicketHistory = => showTicketHistory = =>
new App.TicketHistory( ticket_id: @ticket.id ) new App.TicketHistory(
ticket_id: @ticket.id
container: @el.closest('.content')
)
showTicketMerge = => showTicketMerge = =>
new App.TicketMerge new App.TicketMerge(
ticket: @ticket ticket: @ticket
task_key: @task_key task_key: @task_key
container: @el container: @el.closest('.content')
)
changeCustomer = (e, el) => changeCustomer = (e, el) =>
new App.TicketCustomer( new App.TicketCustomer(
ticket: @ticket ticket: @ticket
container: @el.closest('.content')
) )
items = [ items = [
{ {
head: 'Ticket' head: 'Ticket'
name: 'ticket' name: 'ticket'
icon: 'message' icon: 'message'
callback: editTicket callback: editTicket
} }
] ]
@ -367,6 +380,7 @@ class App.TicketZoom extends App.Controller
title: 'Users' title: 'Users'
object: 'User' object: 'User'
objects: 'Users' objects: 'Users'
container: @el.closest('.content')
) )
showCustomer = (el) => showCustomer = (el) =>
new App.WidgetUser( new App.WidgetUser(
@ -374,9 +388,9 @@ class App.TicketZoom extends App.Controller
user_id: @ticket.customer_id user_id: @ticket.customer_id
) )
items.push { items.push {
head: 'Customer' head: 'Customer'
name: 'customer' name: 'customer'
icon: 'person' icon: 'person'
actions: [ actions: [
{ {
title: 'Change Customer' title: 'Change Customer'
@ -400,6 +414,7 @@ class App.TicketZoom extends App.Controller
title: 'Organizations' title: 'Organizations'
object: 'Organization' object: 'Organization'
objects: 'Organizations' objects: 'Organizations'
container: @el.closest('.content')
) )
showOrganization = (el) => showOrganization = (el) =>
new App.WidgetOrganization( new App.WidgetOrganization(
@ -421,8 +436,8 @@ class App.TicketZoom extends App.Controller
} }
new App.Sidebar( new App.Sidebar(
el: @el.find('.tabsSidebar') el: @el.find('.tabsSidebar')
items: items items: items
) )
# show article # show article

View file

@ -1,7 +1,4 @@
class App.UserProfile extends App.Controller class App.UserProfile extends App.Controller
events:
'focusout [contenteditable]': 'update'
constructor: (params) -> constructor: (params) ->
super super
@ -12,8 +9,8 @@ class App.UserProfile extends App.Controller
@navupdate '#' @navupdate '#'
# subscribe and reload data / fetch new data if triggered # fetch new data if needed
@subscribeId = App.User.full( @user_id, @render, false, true ) @subscribeId = App.User.full( @user_id, @render )
release: => release: =>
App.User.unsubscribe(@subscribeId) App.User.unsubscribe(@subscribeId)
@ -47,6 +44,40 @@ class App.UserProfile extends App.Controller
@doNotLog = 1 @doNotLog = 1
@recentView( 'User', @user_id ) @recentView( 'User', @user_id )
@html App.view('user_profile/index')(
user: user
)
new Object(
el: @$('.js-object-container')
user: user
)
new App.TicketStats(
el: @$('.js-ticket-stats')
user: user
)
new App.UpdateTastbar(
genericObject: user
)
class Object extends App.Controller
events:
'focusout [contenteditable]': 'update'
constructor: (params) ->
super
# subscribe and reload data / fetch new data if triggered
@subscribeId = App.User.full( @user.id, @render, false, true )
release: =>
App.User.unsubscribe(@subscribeId)
render: (user) =>
# get display data # get display data
userData = [] userData = []
for attributeName, attributeConfig of App.User.attributesGet('view') for attributeName, attributeConfig of App.User.attributesGet('view')
@ -64,7 +95,7 @@ class App.UserProfile extends App.Controller
if name isnt 'firstname' && name isnt 'lastname' && name isnt 'organization' if name isnt 'firstname' && name isnt 'lastname' && name isnt 'organization'
userData.push attributeConfig userData.push attributeConfig
@html App.view('user_profile')( @html App.view('user_profile/object')(
user: user user: user
userData: userData userData: userData
) )
@ -75,15 +106,6 @@ class App.UserProfile extends App.Controller
maxlength: 250 maxlength: 250
}) })
new App.TicketStats(
el: @$('.js-ticket-stats')
user: user
)
new App.UpdateTastbar(
genericObject: user
)
# start action controller # start action controller
showHistory = => showHistory = =>
new App.UserHistory( user_id: user.id ) new App.UserHistory( user_id: user.id )
@ -97,6 +119,7 @@ class App.UserProfile extends App.Controller
title: 'Users' title: 'Users'
object: 'User' object: 'User'
objects: 'Users' objects: 'Users'
container: @el.closest('.content')
) )
actions = [ actions = [
@ -120,7 +143,7 @@ class App.UserProfile extends App.Controller
update: (e) => update: (e) =>
name = $(e.target).attr('data-name') name = $(e.target).attr('data-name')
value = $(e.target).html() value = $(e.target).html()
user = App.User.find( @user_id ) user = App.User.find( @user.id )
if user[name] isnt value if user[name] isnt value
data = {} data = {}
data[name] = value data[name] = value

View file

@ -10,29 +10,117 @@
#= require_tree ./lib/app_post #= require_tree ./lib/app_post
class App extends Spine.Controller class App extends Spine.Controller
@viewPrint: (object, attribute_name) ->
attributes = {}
if object.constructor.attributesGet
attributes = object.constructor.attributesGet()
attribute_config = attributes[attribute_name]
value = object[attribute_name]
valueRef = undefined
# check if relation is requested
if !attribute_config
attribute_name_new = "#{attribute_name}_id"
attribute_config = attributes[attribute_name_new]
if attribute_config
attribute_name = attribute_name_new
if object[attribute_name]
valueRef = value
value = object[attribute_name]
# in case of :: key, get the sub value
if !value
parts = attribute_name.split('::')
if parts[0] && parts[1] && object[ parts[0] ]
value = object[ parts[0] ][ parts[1] ]
#console.log('Pa', attribute_name, object, attribute_config, object[attribute_name], valueRef, value)
# if we have no config, get output this way
if !attribute_config
return @viewPrintItem( value )
# check if valueRef already exists, no lookup needed later
if !valueRef
attribute_name_without_ref = attribute_name.substr(attribute_name.length-3, attribute_name.length)
if attribute_name_without_ref is '_id'
attribute_name_without_ref = attribute_name.substr(0, attribute_name.length-3)
if object[attribute_name_without_ref]
valueRef = object[attribute_name_without_ref]
return @viewPrintItem( value, attribute_config, valueRef )
# define print name helper
@viewPrintItem: ( item, attribute_config = {}, valueRef ) ->
return '-' if item is undefined
return '-' if item is ''
return item if !item
result = item
# lookup relation
if attribute_config.relation || valueRef
if valueRef
item = valueRef
else
item = App[attribute_config.relation].find(item)
# if date is a object, get name of the object
isObject = false
if typeof item is 'object'
isObject = true
if item.displayNameLong
result = item.displayNameLong()
else if item.displayName
result = item.displayName()
else
result = item.name
# execute callback on content
if attribute_config.callback
result = attribute_config.callback( result, attribute_config )
# text2html in textarea view
isHtmlEscape = false
if attribute_config.tag is 'textarea'
isHtmlEscape = true
result = App.Utils.text2html( result )
# remember, html snippets are already escaped
else if attribute_config.tag is 'richtext'
isHtmlEscape = true
# fillup options
if !_.isEmpty(attribute_config.options)
if attribute_config.options[result]
result = attribute_config.options[result]
# translate content
if attribute_config.translate || ( isObject && item.translate && item.translate() )
isHtmlEscape = true
result = App.i18n.translateContent( result )
# transform date
if attribute_config.tag is 'date'
isHtmlEscape = true
result = App.i18n.translateDate(result)
# use pretty time for datetime
else if attribute_config.tag is 'datetime'
isHtmlEscape = true
result = "<span class=\"humanTimeFromNow #{attribute_config.class}\" data-time=\"#{result}\">?</span>"
#result = App.i18n.translateTimestamp(result)
if !isHtmlEscape && typeof result is 'string'
result = App.Utils.htmlEscape(result)
result
@view: (name) -> @view: (name) ->
template = ( params = {} ) => template = ( params = {} ) =>
# define print name helper # define print name helper
params.P = ( item, row = {} ) -> params.P = ( object, attribute_name ) ->
return '-' if item is undefined App.viewPrint( object, attribute_name )
return '-' if item is ''
return item if !item
# if date is a object, get name of the object
if typeof item is 'object'
if item.displayNameLong
return item.displayNameLong()
else if item.displayName
return item.displayName()
return item.name
# execute callback on content
if row.callback
return row.callback( item, row )
# return raw data
item
# define date format helper # define date format helper
params.date = ( time ) -> params.date = ( time ) ->

View file

@ -30,7 +30,7 @@ class App.Browser
# define min. required browser version # define min. required browser version
map = map =
Chrome2: 37 Chrome2: 37
Firefox: 28 Firefox: 31
Explorer: 10 Explorer: 10
Safari: 6 Safari: 6
Opera: 22 Opera: 22

View file

@ -276,7 +276,8 @@ class App.UserOrganizationAutocompletion extends App.Controller
if e if e
e.preventDefault() e.preventDefault()
new UserNew( new UserNew(
parent: @ parent: @
container: @el.closest('.content')
) )
class UserNew extends App.ControllerModal class UserNew extends App.ControllerModal

View file

@ -4,11 +4,11 @@ class App.EmailAddress extends App.Model
@url: @apiPath + '/email_addresses' @url: @apiPath + '/email_addresses'
@configure_attributes = [ @configure_attributes = [
{ name: 'realname', display: 'Realname', tag: 'input', type: 'text', limit: 250, 'null': false, 'class': 'span4' }, { name: 'realname', display: 'Realname', tag: 'input', type: 'text', limit: 250, null: false },
{ name: 'email', display: 'Email', tag: 'input', type: 'text', limit: 250, 'null': false, 'class': 'span4' }, { name: 'email', display: 'Email', tag: 'input', type: 'text', limit: 250, null: false },
{ name: 'note', display: 'Note', tag: 'textarea', note: 'Notes are visible to agents only, never to customers.', limit: 250, 'null': true, 'class': 'span4' }, { name: 'note', display: 'Note', tag: 'textarea', note: 'Notes are visible to agents only, never to customers.', limit: 250, null: true },
{ name: 'updated_at', display: 'Updated', type: 'time', readonly: 1 }, { name: 'updated_at', display: 'Updated', tag: 'datetime', readonly: 1 },
{ name: 'active', display: 'Active', tag: 'boolean', type: 'boolean', 'default': true, 'null': false, 'class': 'span4' }, { name: 'active', display: 'Active', tag: 'active', default: true },
] ]
@configure_overview = [ @configure_overview = [
'realname', 'email' 'realname', 'email'

View file

@ -4,15 +4,15 @@ class App.Group extends App.Model
@url: @apiPath + '/groups' @url: @apiPath + '/groups'
@configure_attributes = [ @configure_attributes = [
{ name: 'name', display: 'Name', tag: 'input', type: 'text', limit: 100, 'null': false, 'class': 'span4' }, { name: 'name', display: 'Name', tag: 'input', type: 'text', limit: 100, null: false },
{ name: 'assignment_timeout', display: 'Assignment Timeout', tag: 'input', note: 'Assignment timeout in minutes if assigned agent is not working on it. Ticket will be shown as unassigend.', type: 'text', limit: 100, 'null': true, 'class': 'span4' }, { name: 'assignment_timeout', display: 'Assignment Timeout', tag: 'input', note: 'Assignment timeout in minutes if assigned agent is not working on it. Ticket will be shown as unassigend.', type: 'text', limit: 100, null: true },
{ name: 'follow_up_possible', display: 'Follow up possible',tag: 'select', default: 'yes', options: { yes: 'yes', reject: 'reject follow up/do not reopen Ticket', 'new_ticket': 'do not reopen Ticket but create new Ticket' }, 'null': false, note: 'Follow up for closed ticket possible or not.', 'class': 'span4' }, { name: 'follow_up_possible', display: 'Follow up possible',tag: 'select', default: 'yes', options: { yes: 'yes', reject: 'reject follow up/do not reopen Ticket', 'new_ticket': 'do not reopen Ticket but create new Ticket' }, null: false, note: 'Follow up for closed ticket possible or not.' },
{ name: 'follow_up_assignment', display: 'Assign Follow Ups', tag: 'select', default: 'yes', options: { true: 'yes', false: 'no' }, 'null': false, note: 'Assign follow up to latest agent again.', 'class': 'span4' }, { name: 'follow_up_assignment', display: 'Assign Follow Ups', tag: 'select', default: 'yes', options: { true: 'yes', false: 'no' }, 'null': false, note: 'Assign follow up to latest agent again.' },
{ name: 'email_address_id', display: 'Email', tag: 'select', multiple: false, null: true, relation: 'EmailAddress', nulloption: true, class: 'span4' }, { name: 'email_address_id', display: 'Email', tag: 'select', multiple: false, null: true, relation: 'EmailAddress', nulloption: true },
{ name: 'signature_id', display: 'Signature', tag: 'select', multiple: false, null: true, relation: 'Signature', nulloption: true, class: 'span4' }, { name: 'signature_id', display: 'Signature', tag: 'select', multiple: false, null: true, relation: 'Signature', nulloption: true },
{ name: 'note', display: 'Note', tag: 'textarea', note: 'Notes are visible to agents only, never to customers.', limit: 250, 'null': true, 'class': 'span4' }, { name: 'note', display: 'Note', tag: 'textarea', note: 'Notes are visible to agents only, never to customers.', limit: 250, null: true },
{ name: 'updated_at', display: 'Updated', type: 'time', readonly: 1 }, { name: 'updated_at', display: 'Updated', tag: 'datetime', readonly: 1 },
{ name: 'active', display: 'Active', tag: 'boolean', type: 'boolean', 'default': true, 'null': false, 'class': 'span4' }, { name: 'active', display: 'Active', tag: 'active', default: true },
] ]
@configure_overview = [ @configure_overview = [
'name', 'name',

View file

@ -8,15 +8,15 @@ class App.Job extends App.Model
{ name: 'condition', display: 'Conditions for matching objects.', tag: 'ticket_attribute_selection', null: true }, { name: 'condition', display: 'Conditions for matching objects.', tag: 'ticket_attribute_selection', null: true },
{ name: 'execute', display: 'Execute changes on objects.', tag: 'ticket_attribute_set', null: true }, { name: 'execute', display: 'Execute changes on objects.', tag: 'ticket_attribute_set', null: true },
{ name: 'note', display: 'Note', tag: 'textarea', note: 'Notes are visible to agents only, never to customers.', limit: 250, null: true }, { name: 'note', display: 'Note', tag: 'textarea', note: 'Notes are visible to agents only, never to customers.', limit: 250, null: true },
{ name: 'active', display: 'Active', tag: 'boolean', note: 'boolean', 'default': true, null: false }, { name: 'active', display: 'Active', tag: 'active', default: true },
{ name: 'matching', display: 'Matching', readonly: 1 }, { name: 'matching', display: 'Matching', readonly: 1 },
{ name: 'processed', display: 'Processed', readonly: 1 }, { name: 'processed', display: 'Processed', readonly: 1 },
{ name: 'last_run_at', display: 'Last run', type: 'time', readonly: 1 }, { name: 'last_run_at', display: 'Last run', tag: 'datetime', readonly: 1 },
{ name: 'running', display: 'Running', tag: 'boolean', readonly: 1 }, { name: 'running', display: 'Running', tag: 'boolean', readonly: 1 },
{ name: 'created_by_id', display: 'Created by', relation: 'User', readonly: 1 }, { name: 'created_by_id', display: 'Created by', relation: 'User', readonly: 1 },
{ name: 'created_at', display: 'Created', type: 'time', readonly: 1 }, { name: 'created_at', display: 'Created', tag: 'datetime', readonly: 1 },
{ name: 'updated_by_id', display: 'Updated by', relation: 'User', readonly: 1 }, { name: 'updated_by_id', display: 'Updated by', relation: 'User', readonly: 1 },
{ name: 'updated_at', display: 'Updated', type: 'time', readonly: 1 }, { name: 'updated_at', display: 'Updated', tag: 'datetime', readonly: 1 },
] ]
@configure_delete = true @configure_delete = true
@configure_overview = [ @configure_overview = [

View file

@ -2,8 +2,8 @@ class App.Network extends App.Model
@configure 'Network', 'name', 'note', 'active', 'updated_at' @configure 'Network', 'name', 'note', 'active', 'updated_at'
@extend Spine.Model.Ajax @extend Spine.Model.Ajax
@configure_attributes = [ @configure_attributes = [
{ name: 'name', display: 'Name', tag: 'input', type: 'text', limit: 100, 'null': false, 'class': 'xlarge' }, { name: 'name', display: 'Name', tag: 'input', type: 'text', limit: 100, null: false },
{ name: 'note', display: 'Note', note: 'Notes are visible to agents only, never to customers.', tag: 'textarea', limit: 250, 'null': true, 'class': 'xlarge' }, { name: 'note', display: 'Note', note: 'Notes are visible to agents only, never to customers.', tag: 'textarea', limit: 250, null: true },
{ name: 'updated_at', display: 'Updated', type: 'time', readonly: 1 }, { name: 'updated_at', display: 'Updated', tag: 'datetime', readonly: 1 },
{ name: 'active', display: 'Active', tag: 'boolean', type: 'boolean', 'default': true, 'null': false, 'class': 'xlarge' }, { name: 'active', display: 'Active', tag: 'active', default: true },
] ]

View file

@ -3,13 +3,13 @@ class App.ObjectManagerAttribute extends App.Model
@extend Spine.Model.Ajax @extend Spine.Model.Ajax
@url: @apiPath + '/object_manager_attributes' @url: @apiPath + '/object_manager_attributes'
@configure_attributes = [ @configure_attributes = [
{ name: 'name', display: 'Name', tag: 'input', type: 'text', limit: 100, 'null': false }, { name: 'name', display: 'Name', tag: 'input', type: 'text', limit: 100, null: false },
{ name: 'display', display: 'Anzeige', tag: 'input', type: 'text', limit: 100, 'null': false }, { name: 'display', display: 'Anzeige', tag: 'input', type: 'text', limit: 100, null: false },
{ name: 'object', display: 'Object', tag: 'input', readonly: 1 }, { name: 'object', display: 'Object', tag: 'input', readonly: 1 },
{ name: 'position', display: 'Position', tag: 'input', readonly: 1 }, { name: 'position', display: 'Position', tag: 'input', readonly: 1 },
{ name: 'active', display: 'Active', tag: 'boolean', 'default': true, 'null': false }, { name: 'active', display: 'Active', tag: 'active', default: true },
{ name: 'data_type', display: 'Format', tag: 'input', type: 'text', limit: 100, 'null': false }, { name: 'data_type', display: 'Format', tag: 'input', type: 'text', limit: 100, null: false },
{ name: 'updated_at', display: 'Updated', type: 'time', readonly: 1 }, { name: 'updated_at', display: 'Updated', tag: 'datetime', readonly: 1 },
] ]
@configure_overview = [ @configure_overview = [
#'name', #'name',

View file

@ -3,11 +3,11 @@ class App.Organization extends App.Model
@extend Spine.Model.Ajax @extend Spine.Model.Ajax
@url: @apiPath + '/organizations' @url: @apiPath + '/organizations'
@configure_attributes = [ @configure_attributes = [
{ name: 'name', display: 'Name', tag: 'input', type: 'text', limit: 100, 'null': false, info: true }, { name: 'name', display: 'Name', tag: 'input', type: 'text', limit: 100, null: false, info: true },
{ name: 'shared', display: 'Shared organization', tag: 'boolean', note: 'Customers in the organization can view each other items.', type: 'boolean', 'default': true, 'null': false, info: false }, { name: 'shared', display: 'Shared organization', tag: 'boolean', note: 'Customers in the organization can view each other items.', type: 'boolean', default: true, null: false, info: false },
{ name: 'note', display: 'Note', tag: 'textarea', note: 'Notes are visible to agents only, never to customers.', limit: 250, 'null': true, info: true }, { name: 'note', display: 'Note', tag: 'textarea', note: 'Notes are visible to agents only, never to customers.', limit: 250, null: true, info: true },
{ name: 'updated_at', display: 'Updated', type: 'time', readonly: 1, info: false }, { name: 'updated_at', display: 'Updated', tag: 'datetime', readonly: 1, info: false },
{ name: 'active', display: 'Active', tag: 'boolean', 'default': true, 'null': false, info: false }, { name: 'active', display: 'Active', tag: 'active', default: true, info: false },
] ]
@configure_overview = [ @configure_overview = [
'name', 'name',

View file

@ -131,11 +131,11 @@ class App.Overview extends App.Model
owner: 'Owner' owner: 'Owner'
class: 'span4' class: 'span4'
}, },
{ name: 'active', display: 'Active', tag: 'boolean', note: 'boolean', 'default': true, 'null': false, 'class': 'span4' }, { name: 'active', display: 'Active', tag: 'active', default: true },
{ name: 'created_by_id', display: 'Created by', relation: 'User', readonly: 1 }, { name: 'created_by_id', display: 'Created by', relation: 'User', readonly: 1 },
{ name: 'created_at', display: 'Created', type: 'time', readonly: 1 }, { name: 'created_at', display: 'Created', tag: 'datetime', readonly: 1 },
{ name: 'updated_by_id', display: 'Updated by', relation: 'User', readonly: 1 }, { name: 'updated_by_id', display: 'Updated by', relation: 'User', readonly: 1 },
{ name: 'updated_at', display: 'Updated', type: 'time', readonly: 1 }, { name: 'updated_at', display: 'Updated', tag: 'datetime', readonly: 1 },
] ]
@configure_delete = true @configure_delete = true
@configure_overview = [ @configure_overview = [

View file

@ -8,13 +8,13 @@ class App.PostmasterFilter extends App.Model
{ name: 'channel', display: 'Channel', type: 'input', readonly: 1 }, { name: 'channel', display: 'Channel', type: 'input', readonly: 1 },
{ name: 'match', display: 'Match all of the following', tag: 'postmaster_match' }, { name: 'match', display: 'Match all of the following', tag: 'postmaster_match' },
{ name: 'perform', display: 'Perform action of the following', tag: 'postmaster_set' }, { name: 'perform', display: 'Perform action of the following', tag: 'postmaster_set' },
{ name: 'note', display: 'Note', tag: 'textarea', note: 'Notes are visible to agents only, never to customers.', limit: 250, 'null': true }, { name: 'note', display: 'Note', tag: 'textarea', note: 'Notes are visible to agents only, never to customers.', limit: 250, null: true },
{ name: 'updated_at', display: 'Updated', type: 'time', readonly: 1 }, { name: 'updated_at', display: 'Updated', tag: 'datetime', readonly: 1 },
{ name: 'active', display: 'Active', tag: 'boolean', type: 'boolean', 'default': true, 'null': false }, { name: 'active', display: 'Active', tag: 'active', default: true },
{ name: 'created_by_id', display: 'Created by', relation: 'User', readonly: 1 }, { name: 'created_by_id', display: 'Created by', relation: 'User', readonly: 1 },
{ name: 'created_at', display: 'Created', type: 'time', readonly: 1 }, { name: 'created_at', display: 'Created', tag: 'datetime', readonly: 1 },
{ name: 'updated_by_id', display: 'Updated by', relation: 'User', readonly: 1 }, { name: 'updated_by_id', display: 'Updated by', relation: 'User', readonly: 1 },
{ name: 'updated_at', display: 'Updated', type: 'time', readonly: 1 }, { name: 'updated_at', display: 'Updated', tag: 'datetime', readonly: 1 },
] ]
@configure_delete = true @configure_delete = true
@configure_overview = [ @configure_overview = [

View file

@ -3,13 +3,13 @@ class App.Role extends App.Model
@extend Spine.Model.Ajax @extend Spine.Model.Ajax
@url: @apiPath + '/roles' @url: @apiPath + '/roles'
@configure_attributes = [ @configure_attributes = [
{ name: 'name', display: 'Name', tag: 'input', type: 'text', limit: 100, null: false }, { name: 'name', display: 'Name', tag: 'input', type: 'text', limit: 100, null: false },
{ name: 'note', display: 'Note', tag: 'textarea', note: 'Notes are visible to agents only, never to customers.', limit: 250, null: true }, { name: 'note', display: 'Note', tag: 'textarea', note: 'Notes are visible to agents only, never to customers.', limit: 250, null: true },
{ name: 'active', display: 'Active', tag: 'boolean', type: 'boolean', 'default': true, null: false }, { name: 'active', display: 'Active', tag: 'active', default: true },
{ name: 'created_by_id', display: 'Created by', relation: 'User', readonly: 1 }, { name: 'created_by_id', display: 'Created by', relation: 'User', readonly: 1 },
{ name: 'created_at', display: 'Created', type: 'time', readonly: 1 }, { name: 'created_at', display: 'Created', tag: 'datetime', readonly: 1 },
{ name: 'updated_by_id', display: 'Updated by', relation: 'User', readonly: 1 }, { name: 'updated_by_id', display: 'Updated by', relation: 'User', readonly: 1 },
{ name: 'updated_at', display: 'Updated', type: 'time', readonly: 1 }, { name: 'updated_at', display: 'Updated', tag: 'datetime', readonly: 1 },
] ]
@configure_overview = [ @configure_overview = [
'name', 'name',

View file

@ -4,14 +4,14 @@ class App.Signature extends App.Model
@url: @apiPath + '/signatures' @url: @apiPath + '/signatures'
@configure_attributes = [ @configure_attributes = [
{ name: 'name', display: 'Name', tag: 'input', type: 'text', limit: 100, 'null': false, 'class': 'span4' }, { name: 'name', display: 'Name', tag: 'input', type: 'text', limit: 100, 'null': false },
{ name: 'body', display: 'Text', tag: 'textarea', limit: 250, 'null': true, 'class': 'span4', rows: 10 }, { name: 'body', display: 'Text', tag: 'textarea', limit: 250, 'null': true, rows: 10 },
{ name: 'note', display: 'Note', tag: 'textarea', note: 'Notes are visible to agents only, never to customers.', limit: 250, 'null': true, 'class': 'span4' }, { name: 'note', display: 'Note', tag: 'textarea', note: 'Notes are visible to agents only, never to customers.', limit: 250, 'null': true },
{ name: 'active', display: 'Active', tag: 'boolean', type: 'boolean', 'default': true, 'null': false, 'class': 'span4' }, { name: 'active', display: 'Active', tag: 'active', default: true },
{ name: 'created_by_id', display: 'Created by', relation: 'User', readonly: 1 }, { name: 'created_by_id', display: 'Created by', relation: 'User', readonly: 1 },
{ name: 'created_at', display: 'Created', type: 'time', readonly: 1 }, { name: 'created_at', display: 'Created', tag: 'datetime', readonly: 1 },
{ name: 'updated_by_id', display: 'Updated by', relation: 'User', readonly: 1 }, { name: 'updated_by_id', display: 'Updated by', relation: 'User', readonly: 1 },
{ name: 'updated_at', display: 'Updated', type: 'time', readonly: 1 }, { name: 'updated_at', display: 'Updated', tag: 'datetime', readonly: 1 },
] ]
@configure_overview = [ @configure_overview = [
'name', 'name',

View file

@ -31,11 +31,11 @@ class App.Sla extends App.Model
group: 'Group' group: 'Group'
owner: 'Owner' owner: 'Owner'
}, },
{ name: 'active', display: 'Active', tag: 'boolean', note: 'boolean', 'default': true, 'null': false }, { name: 'active', display: 'Active', tag: 'active', default: true },
{ name: 'created_by_id', display: 'Created by', relation: 'User', readonly: 1 }, { name: 'created_by_id', display: 'Created by', relation: 'User', readonly: 1 },
{ name: 'created_at', display: 'Created', type: 'time', readonly: 1 }, { name: 'created_at', display: 'Created', tag: 'datetime', readonly: 1 },
{ name: 'updated_by_id', display: 'Updated by', relation: 'User', readonly: 1 }, { name: 'updated_by_id', display: 'Updated by', relation: 'User', readonly: 1 },
{ name: 'updated_at', display: 'Updated', type: 'time', readonly: 1 }, { name: 'updated_at', display: 'Updated', tag: 'datetime', readonly: 1 },
] ]
@configure_delete = true @configure_delete = true
@configure_overview = [ @configure_overview = [

View file

@ -3,11 +3,11 @@ class App.TextModule extends App.Model
@extend Spine.Model.Ajax @extend Spine.Model.Ajax
@url: @apiPath + '/text_modules' @url: @apiPath + '/text_modules'
@configure_attributes = [ @configure_attributes = [
{ name: 'name', display: 'Name', tag: 'input', type: 'text', limit: 100, 'null': false, 'class': 'span4' }, { name: 'name', display: 'Name', tag: 'input', type: 'text', limit: 100, null: false },
{ name: 'keywords', display: 'Keywords', tag: 'input', type: 'text', limit: 100, 'null': true, 'class': 'span4' }, { name: 'keywords', display: 'Keywords', tag: 'input', type: 'text', limit: 100, null: true },
{ name: 'content', display: 'Content', tag: 'textarea', limit: 250, 'null': false, 'class': 'span4' }, { name: 'content', display: 'Content', tag: 'textarea', limit: 250, null: false },
{ name: 'updated_at', display: 'Updated', type: 'time', readonly: 1 }, { name: 'updated_at', display: 'Updated', tag: 'datetime', readonly: 1 },
{ name: 'active', display: 'Active', tag: 'boolean', note: 'boolean', 'default': true, 'null': false, 'class': 'span4' }, { name: 'active', display: 'Active', tag: 'active', default: true },
] ]
@configure_delete = true @configure_delete = true
@configure_overview = [ @configure_overview = [

View file

@ -11,18 +11,18 @@ class App.Ticket extends App.Model
{ name: 'title', display: 'Title', tag: 'input', type: 'text', limit: 100, null: false, parentClass: 'noTruncate' }, { name: 'title', display: 'Title', tag: 'input', type: 'text', limit: 100, null: false, parentClass: 'noTruncate' },
{ name: 'state_id', display: 'State', tag: 'select', multiple: false, null: false, relation: 'TicketState', default: 'new', style: 'width: 12%', edit: true, customer: true, }, { name: 'state_id', display: 'State', tag: 'select', multiple: false, null: false, relation: 'TicketState', default: 'new', style: 'width: 12%', edit: true, customer: true, },
{ name: 'priority_id', display: 'Priority', tag: 'select', multiple: false, null: false, relation: 'TicketPriority', default: '2 normal', style: 'width: 12%', edit: true, customer: true, }, { name: 'priority_id', display: 'Priority', tag: 'select', multiple: false, null: false, relation: 'TicketPriority', default: '2 normal', style: 'width: 12%', edit: true, customer: true, },
{ name: 'last_contact', display: 'Last contact', type: 'time', null: true, style: 'width: 12%', parentClass: 'noTruncate' }, { name: 'last_contact', display: 'Last contact', tag: 'datetime', null: true, style: 'width: 12%', parentClass: 'noTruncate' },
{ name: 'last_contact_agent', display: 'Last contact (Agent)', type: 'time', null: true, style: 'width: 12%', parentClass: 'noTruncate' }, { name: 'last_contact_agent', display: 'Last contact (Agent)', tag: 'datetime', null: true, style: 'width: 12%', parentClass: 'noTruncate' },
{ name: 'last_contact_customer', display: 'Last contact (Customer)', type: 'time', null: true, style: 'width: 12%', parentClass: 'noTruncate' }, { name: 'last_contact_customer', display: 'Last contact (Customer)', tag: 'datetime', null: true, style: 'width: 12%', parentClass: 'noTruncate' },
{ name: 'first_response', display: 'First response', type: 'time', null: true, style: 'width: 12%', parentClass: 'noTruncate' }, { name: 'first_response', display: 'First response', tag: 'datetime', null: true, style: 'width: 12%', parentClass: 'noTruncate' },
{ name: 'close_time', display: 'Close time', type: 'time', null: true, style: 'width: 12%', parentClass: 'noTruncate' }, { name: 'close_time', display: 'Close time', tag: 'datetime', null: true, style: 'width: 12%', parentClass: 'noTruncate' },
{ name: 'pending_time', display: 'Pending Time', type: 'time', null: true, style: 'width: 12%', parentClass: 'noTruncate' }, { name: 'pending_time', display: 'Pending Time', tag: 'datetime', null: true, style: 'width: 12%', parentClass: 'noTruncate' },
{ name: 'escalation_time', display: 'Escalation', type: 'time', null: true, style: 'width: 12%', class: 'escalation', parentClass: 'noTruncate' }, { name: 'escalation_time', display: 'Escalation', tag: 'datetime', null: true, style: 'width: 12%', class: 'escalation', parentClass: 'noTruncate' },
{ name: 'article_count', display: 'Article#', style: 'width: 12%' }, { name: 'article_count', display: 'Article#', style: 'width: 12%' },
{ name: 'created_by_id', display: 'Created by', relation: 'User', readonly: 1 }, { name: 'created_by_id', display: 'Created by', relation: 'User', readonly: 1 },
{ name: 'created_at', display: 'Created', type: 'time', style: 'width: 120px', readonly: 1, parentClass: 'noTruncate' }, { name: 'created_at', display: 'Created', tag: 'datetime', style: 'width: 120px', readonly: 1, parentClass: 'noTruncate' },
{ name: 'updated_by_id', display: 'Updated by', relation: 'User', readonly: 1 }, { name: 'updated_by_id', display: 'Updated by', relation: 'User', readonly: 1 },
{ name: 'updated_at', display: 'Updated', type: 'time', style: 'width: 120px', readonly: 1, parentClass: 'noTruncate' }, { name: 'updated_at', display: 'Updated', tag: 'datetime', style: 'width: 120px', readonly: 1, parentClass: 'noTruncate' },
] ]
uiUrl: -> uiUrl: ->

View file

@ -3,19 +3,19 @@ class App.TicketArticle extends App.Model
@extend Spine.Model.Ajax @extend Spine.Model.Ajax
@url: @apiPath + '/ticket_articles' @url: @apiPath + '/ticket_articles'
@configure_attributes = [ @configure_attributes = [
{ name: 'ticket_id', display: 'TicketID', null: false, readonly: 1, }, { name: 'ticket_id', display: 'TicketID', null: false, readonly: 1, },
{ name: 'from', display: 'From', tag: 'input', type: 'text', limit: 100, null: false, }, { name: 'from', display: 'From', tag: 'input', type: 'text', limit: 100, null: false },
{ name: 'to', display: 'To', tag: 'input', type: 'text', limit: 100, null: true, }, { name: 'to', display: 'To', tag: 'input', type: 'text', limit: 100, null: true },
{ name: 'cc', display: 'Cc', tag: 'input', type: 'text', limit: 100, null: true, }, { name: 'cc', display: 'Cc', tag: 'input', type: 'text', limit: 100, null: true },
{ name: 'subject', display: 'Subject', tag: 'input', type: 'text', limit: 100, null: true, }, { name: 'subject', display: 'Subject', tag: 'input', type: 'text', limit: 100, null: true },
{ name: 'body', display: 'Text', tag: 'textarea', rows: 5, limit: 100, null: false, }, { name: 'body', display: 'Text', tag: 'textarea', rows: 5, limit: 100, null: false },
{ name: 'type_id', display: 'Type', tag: 'select', multiple: false, null: false, relation: 'TicketArticleType', default: '', class: 'medium' }, { name: 'type_id', display: 'Type', tag: 'select', multiple: false, null: false, relation: 'TicketArticleType', default: '' },
{ name: 'sender_id', display: 'Sender', tag: 'select', multiple: false, null: false, relation: 'TicketArticleSender', default: '', class: 'medium' }, { name: 'sender_id', display: 'Sender', tag: 'select', multiple: false, null: false, relation: 'TicketArticleSender', default: '' },
{ name: 'internal', display: 'Visibility', tag: 'radio', default: false, null: true, options: { true: 'internal', false: 'public' }, class: 'medium' }, { name: 'internal', display: 'Visibility', tag: 'radio', default: false, null: true, options: { true: 'internal', false: 'public' } },
{ name: 'created_by_id', display: 'Created by', relation: 'User', readonly: 1 }, { name: 'created_by_id', display: 'Created by', relation: 'User', readonly: 1 },
{ name: 'created_at', display: 'Created', type: 'time', readonly: 1 }, { name: 'created_at', display: 'Created', tag: 'datetime', readonly: 1 },
{ name: 'updated_by_id', display: 'Updated by', relation: 'User', readonly: 1 }, { name: 'updated_by_id', display: 'Updated by', relation: 'User', readonly: 1 },
{ name: 'updated_at', display: 'Updated', type: 'time', readonly: 1 }, { name: 'updated_at', display: 'Updated', tag: 'datetime', readonly: 1 },
] ]
uiUrl: -> uiUrl: ->

View file

@ -3,10 +3,10 @@ class App.TicketPriority extends App.Model
@extend Spine.Model.Ajax @extend Spine.Model.Ajax
@url: @apiPath + '/ticket_priorities' @url: @apiPath + '/ticket_priorities'
@configure_attributes = [ @configure_attributes = [
{ name: 'name', display: 'Name', tag: 'input', type: 'text', limit: 100, 'null': false, translate: true }, { name: 'name', display: 'Name', tag: 'input', type: 'text', limit: 100, null: false, translate: true },
{ name: 'active', display: 'Active', tag: 'boolean', type: 'boolean', 'default': true, 'null': false }, { name: 'active', display: 'Active', tag: 'active', default: true },
{ name: 'updated_at', display: 'Updated', type: 'time', readonly: 1 }, { name: 'updated_at', display: 'Updated', tag: 'datetime', readonly: 1 },
{ name: 'created_at', display: 'Created', type: 'time', readonly: 1 }, { name: 'created_at', display: 'Created', tag: 'datetime', readonly: 1 },
] ]
@configure_translate = true @configure_translate = true
@configure_overview = [ @configure_overview = [

View file

@ -3,10 +3,10 @@ class App.TicketState extends App.Model
@extend Spine.Model.Ajax @extend Spine.Model.Ajax
@url: @apiPath + '/ticket_states' @url: @apiPath + '/ticket_states'
@configure_attributes = [ @configure_attributes = [
{ name: 'name', display: 'Name', tag: 'input', type: 'text', limit: 100, 'null': false, translate: true }, { name: 'name', display: 'Name', tag: 'input', type: 'text', limit: 100, null: false, translate: true },
{ name: 'active', display: 'Active', tag: 'boolean', type: 'boolean', 'default': true, 'null': false }, { name: 'active', display: 'Active', tag: 'active', default: true },
{ name: 'updated_at', display: 'Updated', type: 'time', readonly: 1 }, { name: 'updated_at', display: 'Updated', tag: 'datetime', readonly: 1 },
{ name: 'created_at', display: 'Created', type: 'time', readonly: 1 }, { name: 'created_at', display: 'Created', tag: 'datetime', readonly: 1 },
] ]
@configure_translate = true @configure_translate = true
@configure_overview = [ @configure_overview = [

View file

@ -5,25 +5,25 @@ class App.User extends App.Model
# @hasMany 'roles', 'App.Role' # @hasMany 'roles', 'App.Role'
@configure_attributes = [ @configure_attributes = [
{ name: 'login', display: 'Login', tag: 'input', type: 'text', limit: 100, null: false, class: 'span4', autocapitalize: false, signup: false, quick: false }, { name: 'login', display: 'Login', tag: 'input', type: 'text', limit: 100, null: false, autocapitalize: false, signup: false, quick: false },
{ name: 'firstname', display: 'Firstname', tag: 'input', type: 'text', limit: 100, null: false, class: 'span4', signup: true, info: true, invite_agent: true }, { name: 'firstname', display: 'Firstname', tag: 'input', type: 'text', limit: 100, null: false, signup: true, info: true, invite_agent: true },
{ name: 'lastname', display: 'Lastname', tag: 'input', type: 'text', limit: 100, null: false, class: 'span4', signup: true, info: true, invite_agent: true }, { name: 'lastname', display: 'Lastname', tag: 'input', type: 'text', limit: 100, null: false, signup: true, info: true, invite_agent: true },
{ name: 'email', display: 'Email', tag: 'input', type: 'email', limit: 100, null: false, class: 'span4', signup: true, info: true, invite_agent: true }, { name: 'email', display: 'Email', tag: 'input', type: 'email', limit: 100, null: false, signup: true, info: true, invite_agent: true },
{ name: 'web', display: 'Web', tag: 'input', type: 'url', limit: 100, null: true, class: 'span4', signup: false, info: true }, { name: 'web', display: 'Web', tag: 'input', type: 'url', limit: 100, null: true, signup: false, info: true },
{ name: 'phone', display: 'Phone', tag: 'input', type: 'phone', limit: 100, null: true, class: 'span4', signup: false, info: true }, { name: 'phone', display: 'Phone', tag: 'input', type: 'phone', limit: 100, null: true, signup: false, info: true },
{ name: 'mobile', display: 'Mobile', tag: 'input', type: 'phone', limit: 100, null: true, class: 'span4', signup: false, info: true }, { name: 'mobile', display: 'Mobile', tag: 'input', type: 'phone', limit: 100, null: true, signup: false, info: true },
{ name: 'fax', display: 'Fax', tag: 'input', type: 'phone', limit: 100, null: true, class: 'span4', signup: false, info: true }, { name: 'fax', display: 'Fax', tag: 'input', type: 'phone', limit: 100, null: true, signup: false, info: true },
{ name: 'organization_id', display: 'Organization', tag: 'select', multiple: false, nulloption: true, null: true, relation: 'Organization', class: 'span4', signup: false, info: true }, { name: 'organization_id', display: 'Organization', tag: 'select', multiple: false, nulloption: true, null: true, relation: 'Organization', signup: false, info: true },
{ name: 'department', display: 'Department', tag: 'input', type: 'text', limit: 200, null: true, class: 'span4', signup: false, info: true }, { name: 'department', display: 'Department', tag: 'input', type: 'text', limit: 200, null: true, signup: false, info: true },
{ name: 'street', display: 'Street', tag: 'input', type: 'text', limit: 100, null: true, class: 'span4', signup: false, info: true }, { name: 'street', display: 'Street', tag: 'input', type: 'text', limit: 100, null: true, signup: false, info: true },
{ name: 'zip', display: 'Zip', tag: 'input', type: 'text', limit: 100, null: true, class: 'span4', signup: false, info: true }, { name: 'zip', display: 'Zip', tag: 'input', type: 'text', limit: 100, null: true, signup: false, info: true },
{ name: 'city', display: 'City', tag: 'input', type: 'text', limit: 100, null: true, class: 'span4', signup: false, info: true }, { name: 'city', display: 'City', tag: 'input', type: 'text', limit: 100, null: true, signup: false, info: true },
{ name: 'password', display: 'Password', tag: 'input', type: 'password', limit: 50, null: true, autocomplete: 'off', class: 'span4', signup: true, }, { name: 'password', display: 'Password', tag: 'input', type: 'password', limit: 50, null: true, autocomplete: 'off', signup: true, },
{ name: 'note', display: 'Note', tag: 'textarea', note: 'Notes are visible to agents only, never to customers.', limit: 250, null: true, class: 'span4', info: true }, { name: 'note', display: 'Note', tag: 'textarea', note: 'Notes are visible to agents only, never to customers.', limit: 250, null: true, info: true },
{ name: 'role_ids', display: 'Roles', tag: 'checkbox', multiple: true, null: false, relation: 'Role', class: 'span4' }, { name: 'role_ids', display: 'Roles', tag: 'checkbox', multiple: true, null: false, relation: 'Role' },
{ name: 'group_ids', display: 'Groups', tag: 'checkbox', multiple: true, null: true, relation: 'Group', class: 'span4', invite_agent: true }, { name: 'group_ids', display: 'Groups', tag: 'checkbox', multiple: true, null: true, relation: 'Group', invite_agent: true },
{ name: 'active', display: 'Active', tag: 'boolean', default: true, null: true, class: 'span4' }, { name: 'active', display: 'Active', tag: 'active', default: true },
{ name: 'updated_at', display: 'Updated', type: 'time', readonly: 1 }, { name: 'updated_at', display: 'Updated', tag: 'datetime', readonly: 1 },
] ]
@configure_overview = [ @configure_overview = [
# 'login', 'firstname', 'lastname', 'email', 'updated_at', # 'login', 'firstname', 'lastname', 'email', 'updated_at',
@ -52,21 +52,21 @@ class App.User extends App.Model
else else
return '??' return '??'
avatar: (size = 40, placement = '', cssClass = '') -> avatar: (size = 40, placement = '', cssClass = '', unique = false, avatar) ->
cssClass += " size-#{ size }" cssClass += " size-#{size}"
if placement if placement
placement = "data-placement=\"#{placement}\"" placement = "data-placement=\"#{placement}\""
if !@image || @image is 'none' if !@image || @image is 'none' || unique
return @uniqueAvatar(size, placement, cssClass) return @uniqueAvatar(size, placement, cssClass, avatar)
else else
"<span class=\"avatar user-popover #{cssClass}\" data-id=\"#{@id}\" style=\"background-image: url(#{ @imageUrl })\" #{placement}></span>" if @vip
cssClass += " vip"
uniqueAvatar: (size = 40, placement = '', cssClass = '', avatar) -> image = @imageUrl()
if size "<span class=\"avatar user-popover #{cssClass}\" data-id=\"#{@id}\" style=\"background-image: url(#{image})\" #{placement}></span>"
cssClass += " size-#{ size }"
uniqueAvatar: (size, placement = '', cssClass = '', avatar) ->
width = 300 width = 300
height = 226 height = 226
size = parseInt(size, 10) size = parseInt(size, 10)
@ -76,12 +76,21 @@ class App.User extends App.Model
y = rng() * (height - size) y = rng() * (height - size)
if !avatar if !avatar
cssClass += "#{cssClass} user-popover" cssClass += " user-popover"
data = "data-id=\"#{@id}\"" data = "data-id=\"#{@id}\""
else else
data = "data-avatar-id=\"#{avatar.id}\"" data = "data-avatar-id=\"#{avatar.id}\""
if @vip
cssClass += " vip"
"<span class=\"avatar unique #{cssClass}\" #{data} style=\"background-position: -#{ x }px -#{ y }px;\" #{placement}>#{ @initials() }</span>" "<span class=\"avatar unique #{cssClass}\" #{data} style=\"background-position: -#{ x }px -#{ y }px;\" #{placement}>#{ @initials() }</span>"
imageUrl: ->
return if !@image
# set image url
@constructor.apiPath + '/users/image/' + @image
@_fillUp: (data) -> @_fillUp: (data) ->
# set socal media links # set socal media links
@ -92,9 +101,6 @@ class App.User extends App.Model
if account == 'facebook' if account == 'facebook'
data['accounts'][account]['link'] = 'https://www.facebook.com/profile.php?id=' + data['accounts'][account]['uid'] data['accounts'][account]['link'] = 'https://www.facebook.com/profile.php?id=' + data['accounts'][account]['uid']
# set image url
data.imageUrl = @apiPath + '/users/image/' + data.image
if data.organization_id if data.organization_id
data.organization = App.Organization.find(data.organization_id) data.organization = App.Organization.find(data.organization_id)

View file

@ -11,30 +11,30 @@
<input type="checkbox" value="<%= ticket.id %>" name="bulk" class="pull-left"/> <input type="checkbox" value="<%= ticket.id %>" name="bulk" class="pull-left"/>
</td> </td>
<td class="span1"> <td class="span1">
<img class="thumbnail user-popover" data-id="<%= ticket.customer_id %>" src="<%= ticket.customer.imageUrl %>" alt=""> <img class="thumbnail user-popover" data-id="<%= ticket.customer_id %>" src="<%- ticket.customer.imageUrl() %>" alt="">
</td> </td>
<td class="span10"> <td class="span10">
<h3><a href="#" data-type="edit"><%= ticket.title %></a> <small><%= ticket.number %> <span class="humanTimeFromNow" data-time="<%= ticket.created_at %>">?</span></small></h3> <h3><a href="#" data-type="edit"><%= ticket.title %></a> <small><%= ticket.number %> <span class="humanTimeFromNow" data-time="<%= ticket.created_at %>">?</span></small></h3>
<div class="row"> <div class="row">
<div class="span2"> <div class="span2">
<b><%- @T( 'State' ) %></b> <%- @T( ticket.state.name ) %> <b><%- @T( 'State' ) %></b> <%- @P( ticket, 'state' ) %>
</div> </div>
<div class="span2"> <div class="span2">
<b><%- @T( 'Group' ) %></b> <%= ticket.group.name %> <b><%- @T( 'Group' ) %></b> <%- @P( ticket, 'group' ) %>
</div> </div>
<div class="span2"> <div class="span2">
<b><%- @T( 'Customer' ) %></b> <%= ticket.customer.displayName() %> <b><%- @T( 'Customer' ) %></b> <%- @P( ticket, 'customer' ) %>
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<div class="span2"> <div class="span2">
<b><%- @T( 'Priority' ) %></b> <%- @T( ticket.priority.name ) %> <b><%- @T( 'Priority' ) %></b> <%- @P( ticket, 'priority' ) %>
</div> </div>
<div class="span2"> <div class="span2">
<b><%- @T( 'Owner' ) %></b> <%= ticket.owner.displayName() %> <b><%- @T( 'Owner' ) %></b> <%- @P( ticket, 'owner' ) %>
</div> </div>
<div class="span2"> <div class="span2">
<b><%- @T( 'Organization' ) %></b> <%= @P( ticket.customer.organization ) %> <b><%- @T( 'Organization' ) %></b> <%- @P( ticket, 'organization' ) %>
</div> </div>
</div> </div>
</div> </div>

View file

@ -29,16 +29,9 @@
<% groupLast = '' %> <% groupLast = '' %>
<% for object in @objects: %> <% for object in @objects: %>
<% if @groupBy: %> <% if @groupBy: %>
<% if object[@groupBy] && object[@groupBy].displayName: %> <% groupByName = @P( object, @groupBy ) %>
<% groupByName = object[@groupBy].displayName() %>
<% if object[@groupBy].translate(): %>
<% groupByName = @T(groupByName) %>
<% end %>
<% else: %>
<% groupByName = object[@groupBy] || '-' %>
<% end %>
<% if groupLast isnt groupByName: %> <% if groupLast isnt groupByName: %>
<tr class=""><td colspan="<%= length %>"><b><%- @P( groupByName ) %></b></td></tr> <tr class=""><td colspan="<%= length %>"><b><%= groupByName %></b></td></tr>
<% groupLast = groupByName %> <% groupLast = groupByName %>
<% end %> <% end %>
<% end %> <% end %>
@ -56,47 +49,26 @@
<td><input type="radio" value="<%= object.id %>" name="radio"/></td> <td><input type="radio" value="<%= object.id %>" name="radio"/></td>
<% end %> <% end %>
<% for item in @header: %> <% for item in @header: %>
<% translation = false %> <% value = @P( object, item.name ) %>
<% value = object[item.name] %>
<% item_id = item.name.substr(item.name.length-3, item.name.length) %>
<% if item_id is '_id' && object[ item.name.substr(0, item.name.length-3) ]: %>
<% value = object[ item.name.substr(0, item.name.length-3) ] %>
<% refObject = object[ item.name.substr(0, item.name.length-3) ] %>
<% end %>
<% if !value: %>
<% parts = item.name.split '::' %>
<% if parts[0] && parts[1] && object[ parts[0] ]: %>
<% value = object[ parts[0] ][ parts[1] ] %>
<% end %>
<% end %>
<% if value && value.displayNameLong : %>
<% translation = true %>
<% value = value.displayNameLong() %>
<% else if value && value.displayName : %>
<% translation = true %>
<% value = value.displayName() %>
<% end %>
<% item_clone = item %>
<% if @callbacks: %> <% if @callbacks: %>
<% if item.name.substr(item.name.length-3, item.name.length) is '_id' && object[ item.name.substr(0, item.name.length-3) ]: %>
<% refObject = object[ item.name.substr(0, item.name.length-3) ] %>
<% end %>
<% for attribute, callbacksAll of @callbacks: %> <% for attribute, callbacksAll of @callbacks: %>
<% if attribute is item.name || attribute is item_id: %> <% if attribute is item.name: %>
<% for callback in callbacksAll: %> <% for callback in callbacksAll: %>
<% value = callback( value, object, item_clone, @header, refObject ) %> <% value = callback( value, object, item, @header, refObject ) %>
<% end %> <% end %>
<% end %> <% end %>
<% end %> <% end %>
<% end %> <% end %>
<% #console.log('HH', item_clone.name, item_clone.type, item_clone.translate, item_clone, object.translate(), refObject, translation) %>
<td <% if item_clone.parentClass: %>class="<%= item_clone.parentClass %>"<% end %>> <td <% if item.parentClass: %>class="<%= item.parentClass %>"<% end %>>
<% if item_clone.link: %><a href="<%- item_clone.link %>" <% if item_clone.target: %>target="<%= item_clone.target %>"<% end %>><% end %>
<% if item_clone.translate || ( translation && !refObject && object.translate && object.translate() ) || ( translation && refObject && refObject.translate && refObject.translate() ) : %> <% if item.link: %><a href="<%- item.link %>" <% if item.target: %>target="<%= item.target %>"<% end %>><% end %>
<span <% if item_clone.class: %>class="<%= item_clone.class %>"<% end %>><%- @T( @P( value, item_clone ) ) %></span> <span <% if item.class: %>class="<%= item.class %>"<% end %> <% if item.title: %>title="<%= item.title %>"<% end %> <% if item.data: %><% for data_key, data_item of item.data: %>data-<%- data_key %>="<%= data_item %>" <% end %><% end %>><%- value %></span>
<% else if item_clone.type is 'time': %> <% if item.link: %></a><% end %>
<span class="humanTimeFromNow <% if item_clone.class: %><%= item_clone.class %><% end %>" data-time="<%- object[item_clone.name] %>">?</span>
<% else: %>
<span <% if item_clone.class: %>class="<%= item_clone.class %>"<% end %> <% if item_clone.title: %>title="<%= item_clone.title %>"<% end %> <% if item_clone.data: %><% for data_key, data_item of item_clone.data: %>data-<%- data_key %>="<%= data_item %>" <% end %><% end %>><%= @P(value) %></span>
<% end %>
<% if item_clone.link: %></a><% end %>
</td> </td>
<% end %> <% end %>
<% if @destroy: %> <% if @destroy: %>

View file

@ -243,6 +243,10 @@ Oliver<br>
<div class="attachment-name u-highlight">something with very long line which is slonger as the container arount it if you can see it I wish you a very happy day.txt</div> <div class="attachment-name u-highlight">something with very long line which is slonger as the container arount it if you can see it I wish you a very happy day.txt</div>
<div class="attachment-size">17.1 kb</div> <div class="attachment-size">17.1 kb</div>
</div> </div>
<div class="attachment horizontal">
<div class="attachment-name u-highlight">something-withverylonglinewhichisslongerasthecontainerarountitifyoucanseeitIwishyouaveryhappyday.txt</div>
<div class="attachment-size">17.1 kb</div>
</div>
</div> </div>
</div> </div>
</div> </div>

View file

@ -1,50 +0,0 @@
<div class="flex profile">
<div class="profile-window">
<div class="profile-section vertical centered">
<div class="align-right profile-action js-action"></div>
<div class="profile-organizationIcon">
<div class="organization icon"></div>
</div>
<h1><%= @organization.displayName() %></h1>
</div>
<div class="profile-section">
<div class="profile-details horizontal wrap">
<% for row in @organizationData: %>
<% if @organization[row.name]: %>
<% if row.tag is 'richtext': %>
<div class="profile-detailsEntry" style="width: 100%;">
<label><%- @Ti( row.display ) %></label>
<div contenteditable="true" data-name="<%= row.name %>" data-type="update" data-placeholder="<%- @T('Add a Note') %>"><%- @organization[row.name] %></div>
</div>
<% else: %>
<div class="profile-detailsEntry">
<label><%- @Ti( row.display ) %></label>
<%- @L( @P( @organization[row.name] ) ) %>
</div>
<% end %>
<% end %>
<% end %>
</div>
</div>
<% if @organization.members: %>
<div class="profile-section profile-memberSection">
<label><%- @T('Members') %></label>
<div class="profile-details horizontal wrap">
<% for user in @organization.members: %>
<div class="profile-organizationMember">
<%- user.avatar("40") %>
<a href="<%- user.uiUrl() %>" class="user-popover" data-id="<%- user.id %>"><%= user.displayName() %></a>
</div>
<% end %>
</div>
</div>
<% end %>
<div class="profile-section js-ticket-stats"></div>
</div>
</div>

View file

@ -0,0 +1,6 @@
<div class="flex profile">
<div class="profile-window">
<div class="js-object-container"></div>
<div class="profile-section js-ticket-stats"></div>
</div>
</div>

View file

@ -0,0 +1,39 @@
<div class="profile-section vertical centered">
<div class="align-right profile-action js-action"></div>
<div class="profile-organizationIcon">
<div class="organization icon"></div>
</div>
<h1><%= @organization.displayName() %></h1>
</div>
<div class="profile-section">
<div class="profile-details horizontal wrap">
<% for row in @organizationData: %>
<% if row.tag is 'richtext': %>
<div class="profile-detailsEntry" style="width: 100%;">
<label><%- @Ti( row.display ) %></label>
<div contenteditable="true" data-name="<%= row.name %>" data-type="update" data-placeholder="<%- @T('Add a Note') %>"><%- @organization[row.name] %></div>
</div>
<% else: %>
<% if @organization[row.name]: %>
<div class="profile-detailsEntry">
<label><%- @Ti( row.display ) %></label>
<%- @P( @organization, row.name ) %>
</div>
<% end %>
<% end %>
<% end %>
</div>
</div>
<% if @organization.members: %>
<div class="profile-section profile-memberSection">
<label><%- @T('Members') %></label>
<div class="profile-details horizontal wrap">
<% for user in @organization.members: %>
<div class="profile-organizationMember">
<%- user.avatar("40") %>
<a href="<%- user.uiUrl() %>" class="user-popover" data-id="<%- user.id %>"><%= user.displayName() %></a>
</div>
<% end %>
</div>
</div>
<% end %>

View file

@ -1,24 +1,19 @@
<hr> <hr>
<div>
<% for row in @organizationData: %> <% for row in @organizationData: %>
<% if @organization[row.name]: %> <% if @organization[row.name]: %>
<div class="column"> <div class="popover-block">
<h3><%- @T( row.display ) %></h3> <label><%- @T( row.display ) %></label>
<% if row.tag is 'richtext': %> <%- @P( @organization, row.name ) %>
<div><%- @organization[row.name] %></div>
<% else: %>
<div><%- @L( @P( @organization[row.name] ) ) %></div>
<% end %>
</div> </div>
<% end %> <% end %>
<% end %> <% end %>
</div> </div>
<% if @organization.members: %> <% if @organization.members: %>
<hr> <hr>
<h3><%- @T('Members') %></h3> <div class="popover-block">
<% for user in @organization.members: %> <label><%- @T('Members') %></label>
<div class="person"> <% for user in @organization.members: %>
<%= user.displayName() %> <div class="person"><%= user.displayName() %></div>
<% end %>
</div> </div>
<% end %>
<% end %> <% end %>

View file

@ -2,36 +2,40 @@
<span class="<%- @ticket.icon() %>" title="<%- @ticket.iconTitle() %>"></span> <span class="<%- @ticket.iconTextClass() %>"><%- @ticket.iconTitle() %></span> <span class="<%- @ticket.icon() %>" title="<%- @ticket.iconTitle() %>"></span> <span class="<%- @ticket.iconTextClass() %>"><%- @ticket.iconTitle() %></span>
</div> </div>
<hr> <hr>
<h3><%- @P('Agent') %></h3> <div class="popover-block">
<div class="person"> <label><%- @T('Agent') %></label>
<%= @ticket.owner.displayName() %> <div class="person">
<% if @ticket.owner.organization_id: %> <%= @ticket.owner.displayName() %>
<span class="organization"><%= @ticket.owner.organization.displayName() %></span> <% if @ticket.owner.organization: %>
<% end %> <span class="organization"><%= @ticket.owner.organization.displayName() %></span>
<% end %>
</div>
</div> </div>
<h3><%- @P('Customer') %></h3> <div class="popover-block">
<div class="person"> <label><%- @T('Customer') %></label>
<%= @ticket.customer.displayName() %> <div class="person">
<% if @ticket.customer.organization_id: %> <%= @ticket.customer.displayName() %>
<span class="organization"><%= @ticket.customer.organization.displayName() %></span> <% if @ticket.customer.organization: %>
<% end %> <span class="organization"><%= @ticket.customer.organization.displayName() %></span>
<% end %>
</div>
</div> </div>
<hr> <hr>
<div class="horizontal two-columns"> <div class="horizontal two-columns">
<div class="column"> <div class="column">
<h3>#</h3> <label>#</label>
<div class="u-textTruncate"><%- @P( @ticket.number ) %></div> <div class="u-textTruncate"><%- @P( @ticket, 'number' ) %></div>
</div> </div>
<div class="column"> <div class="column">
<h3><%- @T( 'Priority' ) %></h3> <label><%- @T( 'Priority' ) %></label>
<div class="u-textTruncate"><%- @T( @ticket.priority.name ) %></div> <div class="u-textTruncate"><%- @P( @ticket, 'priority' ) %></div>
</div> </div>
<div class="column"> <div class="column">
<h3><%- @T( 'Created' ) %></h3> <label><%- @T( 'Created' ) %></label>
<div class="u-textTruncate"><%- @P( @ticket.humanTime ) %></div> <div class="u-textTruncate"><%- @P( @ticket, 'created_at' ) %></div>
</div> </div>
<div class="column"> <div class="column">
<h3><%- @T( 'Group' ) %></h3> <label><%- @T( 'Group' ) %></label>
<div class="u-textTruncate"><%- @P( @ticket.group ) %></div> <div class="u-textTruncate"><%- @P( @ticket, 'group' ) %></div>
</div> </div>
</div> </div>

View file

@ -5,19 +5,15 @@
<div> <div>
<% for row in @userData: %> <% for row in @userData: %>
<% if @user[row.name]: %> <% if @user[row.name]: %>
<div class="column"> <div class="popover-block">
<h3><%- @T( row.display ) %></h3> <label><%- @T( row.display ) %></label>
<% if row.tag is 'richtext': %> <%- @P( @user, row.name ) %>
<div><%- @user[row.name] %></div>
<% else: %>
<div><%- @L( @P( @user[row.name] ) ) %></div>
<% end %>
</div> </div>
<% end %> <% end %>
<% end %> <% end %>
<% if !_.isEmpty(@user['accounts']): %> <% if !_.isEmpty(@user['accounts']): %>
<div class="customer-info"> <div class="popover-block">
<h3><%- @T( 'Linked Accounts' ) %></h3> <label><%- @T( 'Linked Accounts' ) %></label>
<% for account of @user['accounts']: %> <% for account of @user['accounts']: %>
<a href="<%= @user['accounts'][account]['link'] %>" target="_blank"><%= account %></a> <a href="<%= @user['accounts'][account]['link'] %>" target="_blank"><%= account %></a>
<% end %> <% end %>
@ -25,8 +21,8 @@
<% end %> <% end %>
<% if !_.isEmpty( @user['links'] ): %> <% if !_.isEmpty( @user['links'] ): %>
<% for link in @user['links']: %> <% for link in @user['links']: %>
<div class="customer-info"> <div class="popover-block">
<h3><%- @T( link['title'] ) %></h3> <label><%- @T( link['title'] ) %></label>
<% for item in link['items']: %> <% for item in link['items']: %>
<% if item['url']: %> <% if item['url']: %>
<a href="<%= item['url'] %>" title="<%- @Ti( item['title'] ) %>" style="<%= item['style'] %>" data-type="<%= item['data'] %>" class="<%= item['class'] %>" <% if link.new_window: %>target="_blank"<% end %>> <a href="<%= item['url'] %>" title="<%- @Ti( item['title'] ) %>" style="<%= item['style'] %>" data-type="<%= item['data'] %>" class="<%= item['class'] %>" <% if link.new_window: %>target="_blank"<% end %>>

View file

@ -1,3 +1,8 @@
<ul>
<% for ticket in @tickets: %> <% for ticket in @tickets: %>
<div class="customer-info"><a href="#ticket/zoom/<%= ticket.id %>" title="<%= ticket.title %>">T:<%= ticket.number %></a> <span title="<%= ticket.created_at_short %>"><%= ticket.humanTime %></span><br/><%= ticket.title %></div> <li>
<a href="#ticket/zoom/<%= ticket.id %>" title="<%= ticket.title %>">T:<%= ticket.number %></a> <%- @P( ticket, 'title') %><br/>
<%- @T('Created') %> <%- @P( ticket, 'created_at') %>
</li>
<% end %> <% end %>
<ul>

View file

@ -17,7 +17,7 @@
<% if avatar.default: %> <% if avatar.default: %>
<% cssClass = 'is-active' %> <% cssClass = 'is-active' %>
<% end %> <% end %>
<%- App.Session.get().uniqueAvatar('50', '', cssClass, avatar) %> <%- App.Session.get().avatar('50', '', cssClass, true, avatar) %>
<% else: %> <% else: %>
<span class="avatar size-50 <% if avatar.default: %>is-active<% end %>" data-avatar-id="<%- avatar.id %>" style="background-image: url(<%- avatar.content %>)"></span> <span class="avatar size-50 <% if avatar.default: %>is-active<% end %>" data-avatar-id="<%- avatar.id %>" style="background-image: url(<%- avatar.content %>)"></span>
<% if avatar.deletable: %> <% if avatar.deletable: %>

View file

@ -1 +1 @@
<h1 contenteditable="true" class="ticket-title-update" data-placeholder="<%= @T('Enter Title...') %>"><%= @P( @ticket.title ) %></h1> <div contenteditable="true" class="ticket-title-update" data-placeholder="<%= @T('Enter Title...') %>"><%= @ticket.title %></div>

View file

@ -1,37 +0,0 @@
<div class="flex profile">
<div class="profile-window">
<div class="profile-section vertical centered">
<div class="align-right profile-action js-action"></div>
<%- @user.avatar("80") %>
<h1><%= @user.displayName() %></h1>
<% if @user.organization: %>
<div class="profile-organization"><a href="<%- @user.organization.uiUrl() %>"><%= @user.organization.displayName() %></a></div>
<% end %>
</div>
<div class="profile-section">
<div class="profile-details horizontal wrap">
<% for row in @userData: %>
<% if @user[row.name]: %>
<% if row.tag is 'richtext': %>
<div class="profile-detailsEntry" style="width: 100%;">
<label><%- @Ti( row.display ) %></label>
<div contenteditable="true" data-name="<%= row.name %>" data-type="update" data-placeholder="<%- @T('Add a Note') %>"><%- @user[row.name] %></div>
</div>
<% else: %>
<div class="profile-detailsEntry">
<label><%- @Ti( row.display ) %></label>
<%- @L( @P( @user[row.name] ) ) %>
</div>
<% end %>
<% end %>
<% end %>
</div>
</div>
<div class="profile-section js-ticket-stats"></div>
</div>
</div>

View file

@ -0,0 +1,6 @@
<div class="flex profile">
<div class="profile-window">
<div class="js-object-container"></div>
<div class="profile-section js-ticket-stats"></div>
</div>
</div>

View file

@ -0,0 +1,27 @@
<div class="profile-section vertical centered">
<div class="align-right profile-action js-action"></div>
<%- @user.avatar("80") %>
<h1><%= @user.displayName() %></h1>
<% if @user.organization: %>
<div class="profile-organization"><a href="<%- @user.organization.uiUrl() %>"><%= @user.organization.displayName() %></a></div>
<% end %>
</div>
<div class="profile-section">
<div class="profile-details horizontal wrap">
<% for row in @userData: %>
<% if row.tag is 'richtext': %>
<div class="profile-detailsEntry" style="width: 100%;">
<label><%- @Ti( row.display ) %></label>
<div contenteditable="true" data-name="<%= row.name %>" data-type="update" data-placeholder="<%- @T('Add a Note') %>"><%- @user[row.name] %></div>
</div>
<% else: %>
<% if @user[row.name]: %>
<div class="profile-detailsEntry">
<label><%- @Ti( row.display ) %></label>
<%- @P( @user, row.name ) %>
</div>
<% end %>
<% end %>
<% end %>
</div>
</div>

View file

@ -1,7 +1,8 @@
<div class="sidebar-block"> <div class="sidebar-block">
<h3 class="u-textTruncate" title="<%- @Ti( 'Name') %>"> <div class="avatar organizationInfo-avatar size-50">
<%= @organization.displayName() %> <a href="<%- @organization.uiUrl() %>" class="organization icon"></a>
</h3> </div>
<h3 title="<%- @Ti( 'Name') %>"><%= @organization.displayName() %></h3>
</div> </div>
<% for row in @organizationData: %> <% for row in @organizationData: %>
@ -9,7 +10,7 @@
<div class="sidebar-block"> <div class="sidebar-block">
<% if row.tag isnt 'richtext': %> <% if row.tag isnt 'richtext': %>
<label><%- @T( row.display ) %></label> <label><%- @T( row.display ) %></label>
<%- @L( @P( @organization[row.name] ) ) %> <%- @P( @organization, row.name ) %>
<% else: %> <% else: %>
<label><%- @T( row.display ) %></label> <label><%- @T( row.display ) %></label>
<div contenteditable="true" data-name="<%= row.name %>" data-type="update-org" data-placeholder="<%- @T('Add a Note') %>"><%- @organization[row.name] %></div> <div contenteditable="true" data-name="<%= row.name %>" data-type="update-org" data-placeholder="<%- @T('Add a Note') %>"><%- @organization[row.name] %></div>

View file

@ -1,16 +1,13 @@
<div class="userInfo"> <div class="sidebar-block">
<div class="sidebar-block"> <%- @user.avatar("50", "", "userInfo-avatar") %>
<%- @user.avatar("50", "", "userInfo-avatar") %> <h3 title="<%- @Ti( 'Name') %>"><%= @user.displayName() %></h3>
<h3 class="u-textTruncate" title="<%- @Ti( 'Name') %>"> </div>
<%= @user.displayName() %>
</h3>
</div>
<% for row in @userData: %> <% for row in @userData: %>
<% if @user[row.name] || row.name is 'note': %> <% if @user[row.name] || row.name is 'note': %>
<div class="sidebar-block"> <div class="sidebar-block">
<% if row.tag isnt 'richtext': %> <% if row.tag isnt 'richtext': %>
<label><%- @T( row.display ) %></label> <label><%- @T( row.display ) %></label>
<%- @L( @P( @user[row.name] ) ) %> <%- @P( @user, row.name ) %>
<% else: %> <% else: %>
<label><%- @T( row.display ) %></label> <label><%- @T( row.display ) %></label>
<div contenteditable="true" data-name="<%= row.name %>" data-type="update" data-placeholder="<%- @T('Add a Note') %>"><%- @user[row.name] %></div> <div contenteditable="true" data-name="<%= row.name %>" data-type="update" data-placeholder="<%- @T('Add a Note') %>"><%- @user[row.name] %></div>
@ -46,4 +43,3 @@
<% end %> <% end %>
<% end %> <% end %>
<% end %> <% end %>
</div>

View file

@ -74,41 +74,49 @@ function difference(object1, object2) {
} }
// clone, just data, no instances of objects // clone, just data, no instances of objects
function clone(item) { function clone(item, full) {
if (!item) { return item; } if (!item) { return item }
// ignore certain objects // ignore certain objects
var acceptedInstances = [ 'Object', 'Number', 'String', 'Boolean', 'Array' ]; var acceptedInstances = [ 'Object', 'Number', 'String', 'Boolean', 'Array' ]
if (full) {
acceptedInstances.push( 'Function' )
}
if (item && item.constructor) { if (item && item.constructor) {
if (!_.contains(acceptedInstances, item.constructor.name)) { if (!_.contains(acceptedInstances, item.constructor.name)) {
return; return
} }
} }
var result;
// copy array // copy array
var result;
if ( _.isArray(item) ) { if ( _.isArray(item) ) {
result = []; result = []
item.forEach(function(child, index, array) { item.forEach(function(child, index, array) {
result[index] = clone( child ); result[index] = clone( child, full )
}); });
} }
// copy function
else if ( _.isFunction(item) ) {
result = item.bind({})
}
// copy object // copy object
else if ( _.isObject(item) ) { else if ( _.isObject(item) ) {
result = {}; result = {}
for(var key in item) { for(var key in item) {
if (item.hasOwnProperty(key)) { if (item.hasOwnProperty(key)) {
result[key] = clone(item[key]) result[key] = clone( item[key], full )
} }
} }
} }
// copy others // copy others
else { else {
result = item; result = item
} }
return result; return result
} }
// taken from http://stackoverflow.com/questions/4459928/how-to-deep-clone-in-javascript // taken from http://stackoverflow.com/questions/4459928/how-to-deep-clone-in-javascript

View file

@ -714,6 +714,10 @@ textarea,
background: none; background: none;
} }
textarea.form-control {
height: 118px;
}
select.form-control { select.form-control {
padding-left: 10px; padding-left: 10px;
padding-right: 34px; padding-right: 34px;
@ -2492,6 +2496,11 @@ footer {
vertical-align: bottom; vertical-align: bottom;
} }
:not(.navigation) .avatar.vip {
border: 2px dotted;
border-color: #fff;
}
.avatar.size-50 { .avatar.size-50 {
width: 50px; width: 50px;
height: 50px; height: 50px;
@ -2515,16 +2524,30 @@ footer {
cursor: default; cursor: default;
} }
:not(.navigation) .unique.avatar.vip {
border: 2px dotted;
line-height: 40px;
}
.unique.avatar.size-50 { .unique.avatar.size-50 {
font-size: 16px; font-size: 16px;
line-height: 52px; line-height: 52px;
} }
:not(.navigation) .unique.avatar.size-50.vip {
line-height: 50px;
}
.unique.avatar.size-80 { .unique.avatar.size-80 {
font-size: 20px; font-size: 20px;
line-height: 84px; line-height: 84px;
} }
:not(.navigation) .unique.avatar.size-80.vip {
font-size: 20px;
line-height: 82px;
}
.sidebar { .sidebar {
width: 32%; width: 32%;
max-width: 300px; max-width: 300px;
@ -2549,12 +2572,18 @@ footer {
.sidebar-block { .sidebar-block {
margin: 20px 0; margin: 20px 0;
word-wrap: break-word;
&:first-child { &:first-child {
margin-top: 0; margin-top: 0;
} }
} }
.sidebar-block,
.sidebar-block > * {
@extend .u-textTruncate;
}
.sidebar-block [contenteditable=true] {
white-space: normal; // do no u-textTruncate, we want to edit it inline
}
.main + .sidebar { .main + .sidebar {
border-right: none; border-right: none;
@ -2718,10 +2747,16 @@ footer {
} }
.popover .user-organization { .popover .user-organization {
@extend .u-textTruncate;
margin-bottom: 8px; margin-bottom: 8px;
margin-top: -4px; margin-top: -4px;
} }
.popover-block {
@extend .sidebar-block;
margin: 10px 0;
}
.popover hr { .popover hr {
margin: 8px 0; margin: 8px 0;
} }
@ -2729,7 +2764,15 @@ footer {
.popover .person .organization:before { content: '('; } .popover .person .organization:before { content: '('; }
.popover .person .organization:after { content: ')'; } .popover .person .organization:after { content: ')'; }
.popover .column h3 { .popover label {
font-size: 13px;
color: #a9bcc4;
font-weight: 200;
text-transform: uppercase;
letter-spacing: 0.07em;
}
.popover .column label {
margin: 8px 0 1px; margin: 8px 0 1px;
} }
@ -3140,14 +3183,12 @@ footer {
padding: 0 81px; padding: 0 81px;
} }
.ticket-title h1 {
margin-top: 15px;
margin-bottom: 8px;
text-align: center;
}
.ticket-title-update { .ticket-title-update {
@extend h1;
white-space: normal; white-space: normal;
margin-top: 15px;
margin-bottom: 8px;
text-align: center;
} }
.task-subline { .task-subline {
@ -4178,6 +4219,7 @@ footer {
border-radius: 0; border-radius: 0;
border: 1px solid hsl(0,0%,90%); border: 1px solid hsl(0,0%,90%);
box-shadow: none; box-shadow: none;
word-wrap: break-word;
} }
.modal-header { .modal-header {
@ -4381,15 +4423,9 @@ footer {
position: relative; position: relative;
} }
.userInfo-avatar:after { .organizationInfo-avatar {
content: ""; @extend .userInfo-avatar;
background: image_url("/assets/images/sprite.svg"); padding: 18px 0 0 18px;
background-position: -236px 0;
right: 0;
top: 0;
width: 97px;
height: 96px;
position: absolute;
} }
.userList { .userList {
@ -4397,17 +4433,16 @@ footer {
padding: 0; padding: 0;
li { li {
@extend .horizontal; position: relative;
@extend .center;
margin: 10px 0; margin: 10px 0;
} }
a { a {
@extend .u-textTruncate; @extend .u-textTruncate;
} position: absolute;
top: 10px;
.avatar { right: 0px;
margin-right: 7px; left: 48px;
} }
} }

View file

@ -264,7 +264,7 @@ class ApplicationController < ActionController::Base
begin begin
# create object # create object
generic_object = object.new( object.param_cleanup( params[object.to_app_model_url] ) ) generic_object = object.new( object.param_cleanup( params[object.to_app_model_url], true ) )
# save object # save object
generic_object.save! generic_object.save!

View file

@ -97,9 +97,7 @@ class TicketsController < ApplicationController
:customer_id => params[:customer_id], :customer_id => params[:customer_id],
:limit => 15, :limit => 15,
) )
render :json => { render :json => result
:tickets => result
}
end end
# GET /api/v1/ticket_history/1 # GET /api/v1/ticket_history/1

View file

@ -64,7 +64,7 @@ class UsersController < ApplicationController
# @response_message 200 [User] Created User record. # @response_message 200 [User] Created User record.
# @response_message 401 Invalid session. # @response_message 401 Invalid session.
def create def create
user = User.new( User.param_cleanup(params) ) user = User.new( User.param_cleanup(params, true) )
begin begin
# check if it's first user # check if it's first user
@ -122,7 +122,7 @@ class UsersController < ApplicationController
end end
end end
user.save user.save!
# if first user was added, set system init done # if first user was added, set system init done
if count <= 2 if count <= 2
@ -702,7 +702,7 @@ curl http://localhost/api/v1/users/avatar -v -u #{login}:#{password} -H "Content
private private
def password_policy(password) def password_policy(password)
if Setting.get('password_min_size') > password.length if Setting.get('password_min_size').to_i > password.length
return ["Can\'t update password, it must be at least %s characters long!", Setting.get('password_min_size')] return ["Can\'t update password, it must be at least %s characters long!", Setting.get('password_min_size')]
end end
if Setting.get('password_need_digit').to_i == 1 && password !~ /\d/ if Setting.get('password_need_digit').to_i == 1 && password !~ /\d/

View file

@ -41,10 +41,10 @@ class ApplicationModel < ActiveRecord::Base
attr_accessor :history_changes_last_done attr_accessor :history_changes_last_done
@@import_class_list = ['Ticket', 'Ticket::Article', 'History', 'Ticket::State', 'Ticket::Priority', 'Group', 'User' ] @@import_class_list = ['Ticket', 'Ticket::Article', 'History', 'Ticket::State', 'Ticket::StateType', 'Ticket::Priority', 'Group', 'User', 'Role' ]
def check_attributes_protected def check_attributes_protected
if Setting.get('import_mode') && @@import_class_list.include?( self.class.to_s ) if ( !Setting.get('system_init_done') || Setting.get('import_mode')) && @@import_class_list.include?( self.class.to_s )
# do noting, use id as it is # do noting, use id as it is
else else
self[:id] = nil self[:id] = nil
@ -57,18 +57,28 @@ remove all not used model attributes of params
result = Model.param_cleanup(params) result = Model.param_cleanup(params)
for object creation, ignore id's
result = Model.param_cleanup(params, true)
returns returns
result = params # params with valid attributes of model result = params # params with valid attributes of model
=end =end
def self.param_cleanup(params) def self.param_cleanup(params, newObject = false)
if params == nil if params == nil
raise "No params for #{self.to_s}!" raise "No params for #{self.to_s}!"
end end
# ignore id for new objects
if newObject && params[:id]
params[:id] = nil
end
# only use object attributes # only use object attributes
data = {} data = {}
self.new.attributes.each {|item| self.new.attributes.each {|item|

View file

@ -27,7 +27,7 @@ add a new history entry for an object
:id_to => 3, :id_to => 3,
:id_from => 2, :id_from => 2,
:value_from => 'open', :value_from => 'open',
:value_to => 'pending', :value_to => 'pending reminder',
:created_by_id => 1, :created_by_id => 1,
:created_at => '2013-06-04 10:00:00', :created_at => '2013-06-04 10:00:00',
:updated_at => '2013-06-04 10:00:00' :updated_at => '2013-06-04 10:00:00'

View file

@ -65,7 +65,7 @@ list of all attributes
=begin =begin
add a new activity entry for an object add a new attribute entry for an object
ObjectManager::Attribute.add( ObjectManager::Attribute.add(
:object => 'Ticket', :object => 'Ticket',
@ -101,7 +101,6 @@ add a new activity entry for an object
:updated_at => '2014-06-04 10:00:00', :updated_at => '2014-06-04 10:00:00',
) )
=end =end
def self.add(data) def self.add(data)
@ -114,8 +113,8 @@ add a new activity entry for an object
# check newest entry - is needed # check newest entry - is needed
result = ObjectManager::Attribute.where( result = ObjectManager::Attribute.where(
:object_lookup_id => data[:object_lookup_id], :object_lookup_id => data[:object_lookup_id],
:name => data[:name], :name => data[:name],
).first ).first
if result if result
# raise "ERROR: attribute #{data[:name]} for #{data[:object]} already exists" # raise "ERROR: attribute #{data[:name]} for #{data[:object]} already exists"
@ -129,6 +128,30 @@ add a new activity entry for an object
=begin =begin
get the attribute model based on object and name
attribute = ObjectManager::Attribute.get(
:object => 'Ticket',
:name => 'group_id',
)
=end
def self.get(data)
# lookups
if data[:object]
data[:object_lookup_id] = ObjectLookup.by_name( data[:object] )
end
ObjectManager::Attribute.where(
:object_lookup_id => data[:object_lookup_id],
:name => data[:name],
).first
end
=begin
get user based list of object attributes get user based list of object attributes
attribute_list = ObjectManager::Attribute.by_object('Ticket', user) attribute_list = ObjectManager::Attribute.by_object('Ticket', user)

View file

@ -128,6 +128,9 @@ class Observer::Ticket::Notification < ActiveRecord::Observer
# return if we run import mode # return if we run import mode
return if Setting.get('import_mode') return if Setting.get('import_mode')
# ignore updates on articles / we just want send notifications on ticket updates
return if record.class.name == 'Ticket::Article'
# ignore certain attributes # ignore certain attributes
real_changes = {} real_changes = {}
record.changes.each {|key, value| record.changes.each {|key, value|

View file

@ -165,13 +165,16 @@ class Observer::Ticket::Notification::BackgroundJob
attribute_name = key.to_s attribute_name = key.to_s
object_manager_attribute = attribute_list[attribute_name] object_manager_attribute = attribute_list[attribute_name]
if attribute_name[-3,3] == '_id' if attribute_name[-3,3] == '_id'
attribute_name = attribute_name[ 0, attribute_name.length-3 ] attribute_name = attribute_name[ 0, attribute_name.length-3 ].to_s
end
if key == attribute_name
changes[key] = value
end end
value_id = [] # add item to changes hash
if key.to_s == attribute_name
changes[attribute_name] = value
end
# if changed item is an _id field/reference, do an lookup for the realy values
value_id = []
value_str = [ value[0], value[1] ] value_str = [ value[0], value[1] ]
if key.to_s[-3,3] == '_id' if key.to_s[-3,3] == '_id'
value_id[0] = value[0] value_id[0] = value[0]
@ -201,9 +204,16 @@ class Observer::Ticket::Notification::BackgroundJob
end end
end end
end end
# check if we have an dedcated display name for it
display = attribute_name display = attribute_name
if object_manager_attribute && object_manager_attribute[:display] if object_manager_attribute && object_manager_attribute[:display]
display = object_manager_attribute[:display]
# delete old key
changes.delete( display )
# set new key
display = object_manager_attribute[:display].to_s
end end
if object_manager_attribute && object_manager_attribute[:translate] if object_manager_attribute && object_manager_attribute[:translate]
changes[display] = ["i18n(#{value_str[0].to_s})", "i18n(#{value_str[1].to_s})"] changes[display] = ["i18n(#{value_str[0].to_s})", "i18n(#{value_str[1].to_s})"]

View file

@ -0,0 +1,30 @@
# Copyright (C) 2012-2014 Zammad Foundation, http://zammad-foundation.org/
class Observer::Ticket::RefObjectTouch < ActiveRecord::Observer
observe 'ticket'
def after_create(record)
ref_object_touch(record)
end
def after_update(record)
ref_object_touch(record)
end
def after_touch(record)
ref_object_touch(record)
end
def after_destroy(record)
ref_object_touch(record)
end
def ref_object_touch(record)
# return if we run import mode
return if Setting.get('import_mode')
if record.customer
record.customer.touch
end
if record.organization
record.organization.touch
end
end
end

View file

@ -15,7 +15,7 @@ class Ticket < ApplicationModel
extend Ticket::Search extend Ticket::Search
before_create :check_generate, :check_defaults, :check_title before_create :check_generate, :check_defaults, :check_title
before_update :check_defaults, :check_title before_update :check_defaults, :check_title, :reset_pending_time
before_destroy :destroy_dependencies before_destroy :destroy_dependencies
notify_clients_support notify_clients_support
@ -187,6 +187,21 @@ returns
end end
end end
def reset_pending_time
# ignore if no state has changed
return if !self.changes['state_id']
# check if new state isn't pending*
current_state = Ticket::State.lookup( :id => self.state_id )
current_state_type = Ticket::StateType.lookup( :id => current_state.state_type_id )
# in case, set pending_time to nil
if current_state_type.name !~ /^pending/i
self.pending_time = nil
end
end
def destroy_dependencies def destroy_dependencies
# delete articles # delete articles

View file

@ -133,8 +133,9 @@ list tickets by customer groupd in state categroie open and closed
returns returns
result = { result = {
:open => tickets_open, :ticket_ids_open => tickets_open,
:closed => tickets_closed, :ticket_ids_closed => tickets_closed,
:assets => { ...list of assets... },
} }
=end =end
@ -150,15 +151,27 @@ returns
:customer_id => data[:customer_id], :customer_id => data[:customer_id],
:state_id => state_list_open :state_id => state_list_open
).limit( data[:limit] || 15 ).order('created_at DESC') ).limit( data[:limit] || 15 ).order('created_at DESC')
assets = {}
ticket_ids_open = []
tickets_open.each {|ticket|
ticket_ids_open.push ticket.id
assets = ticket.assets(assets)
}
tickets_closed = Ticket.where( tickets_closed = Ticket.where(
:customer_id => data[:customer_id], :customer_id => data[:customer_id],
:state_id => state_list_closed :state_id => state_list_closed
).limit( data[:limit] || 15 ).order('created_at DESC') ).limit( data[:limit] || 15 ).order('created_at DESC')
ticket_ids_closed = []
tickets_closed.each {|ticket|
ticket_ids_closed.push ticket.id
assets = ticket.assets(assets)
}
return { return {
:open => tickets_open, :ticket_ids_open => ticket_ids_open,
:closed => tickets_closed, :ticket_ids_closed => ticket_ids_closed,
:assets => assets,
} }
end end

View file

@ -192,7 +192,7 @@ returns
end end
# check failed logins # check failed logins
max_login_failed = Setting.get('password_max_login_failed') || 10 max_login_failed = Setting.get('password_max_login_failed').to_i || 10
if user && user.login_failed > max_login_failed if user && user.login_failed > max_login_failed
return false return false
end end

View file

@ -0,0 +1,16 @@
<link rel="stylesheet" href="/assets/tests/qunit-1.10.0.css">
<script src="/assets/tests/qunit-1.10.0.js"></script>
<script src="/assets/tests/model-ui.js"></script>
<style type="text/css">
body {
padding-top: 0px;
}
</style>
<script type="text/javascript">
</script>
<div id="qunit"></div>

View file

@ -40,6 +40,7 @@ module Zammad
'observer::_ticket::_notification', 'observer::_ticket::_notification',
'observer::_ticket::_reset_new_state', 'observer::_ticket::_reset_new_state',
'observer::_ticket::_escalation_calculation', 'observer::_ticket::_escalation_calculation',
'observer::_ticket::_ref_object_touch',
'observer::_tag::_ticket_history', 'observer::_tag::_ticket_history',
'observer::_user::_geo' 'observer::_user::_geo'

View file

@ -3,6 +3,7 @@ Zammad::Application.routes.draw do
match '/tests-core', :to => 'tests#core', :via => :get match '/tests-core', :to => 'tests#core', :via => :get
match '/tests-ui', :to => 'tests#ui', :via => :get match '/tests-ui', :to => 'tests#ui', :via => :get
match '/tests-model', :to => 'tests#model', :via => :get match '/tests-model', :to => 'tests#model', :via => :get
match '/tests-model-ui', :to => 'tests#model_ui', :via => :get
match '/tests-form', :to => 'tests#form', :via => :get match '/tests-form', :to => 'tests#form', :via => :get
match '/tests-form-extended', :to => 'tests#form_extended', :via => :get match '/tests-form-extended', :to => 'tests#form_extended', :via => :get
match '/tests-form-validation', :to => 'tests#form_validation', :via => :get match '/tests-form-validation', :to => 'tests#form_validation', :via => :get

View file

@ -463,6 +463,7 @@ class UpdateObjectManager2 < ActiveRecord::Migration
:maxlength => 100, :maxlength => 100,
:null => true, :null => true,
:autocomplete => 'off', :autocomplete => 'off',
:item_class => 'formGroup--halfSize',
}, },
:editable => false, :editable => false,
:active => true, :active => true,
@ -492,10 +493,11 @@ class UpdateObjectManager2 < ActiveRecord::Migration
:display => 'Note', :display => 'Note',
:data_type => 'richtext', :data_type => 'richtext',
:data_option => { :data_option => {
:type => 'text', :type => 'text',
:maxlength => 250, :maxlength => 250,
:null => true, :null => true,
:note => 'Notes are visible to agents only, never to customers.', :note => 'Notes are visible to agents only, never to customers.',
# :item_class => 'formGroup--halfSize',
}, },
:editable => false, :editable => false,
:active => true, :active => true,
@ -591,9 +593,8 @@ class UpdateObjectManager2 < ActiveRecord::Migration
:object => 'User', :object => 'User',
:name => 'active', :name => 'active',
:display => 'Active', :display => 'Active',
:data_type => 'boolean', :data_type => 'active',
:data_option => { :data_option => {
:maxlength => 250,
:null => true, :null => true,
:default => true, :default => true,
}, },
@ -722,10 +723,8 @@ class UpdateObjectManager2 < ActiveRecord::Migration
:object => 'Organization', :object => 'Organization',
:name => 'active', :name => 'active',
:display => 'Active', :display => 'Active',
:data_type => 'boolean', :data_type => 'active',
:data_option => { :data_option => {
:maxlength => 250,
:null => true,
:default => true, :default => true,
}, },
:editable => false, :editable => false,

View file

@ -0,0 +1,44 @@
class CreateVip < ActiveRecord::Migration
def up
add_column :users, :vip, :boolean, :default => false
ObjectManager::Attribute.add(
:object => 'User',
:name => 'vip',
:display => 'VIP',
:data_type => 'boolean',
:data_option => {
:null => true,
:default => false,
:item_class => 'formGroup--halfSize',
:options => {
:false => 'no',
:true => 'yes',
},
:translate => true,
},
:editable => false,
:active => true,
:screens => {
:edit => {
:Admin => {
:null => true,
},
},
:view => {
'-all-' => {
:shown => false,
},
},
},
:pending_migration => false,
:position => 1490,
:created_by_id => 1,
:updated_by_id => 1,
)
end
def down
end
end

View file

@ -0,0 +1,70 @@
class CreateAddress < ActiveRecord::Migration
def up
add_column :users, :address, :string, :limit => 500, :null => true
User.all.each {|user|
address = ''
if user.street && !user.street.empty?
address += "#{user.street}\n"
end
if user.zip && !user.zip.empty?
address += "#{user.zip} "
end
if user.city && !user.city.empty?
address += "#{user.city}"
end
if !address.empty?
user.address = address
user.save
end
}
['street', 'zip', 'city', 'department'].each {|attribute_name|
attribute = ObjectManager::Attribute.get(
:object => 'User',
:name => attribute_name,
)
if attribute
attribute.active = false
attribute.save
end
}
ObjectManager::Attribute.add(
:object => 'User',
:name => 'address',
:display => 'Address',
:data_type => 'textarea',
:data_option => {
:type => 'text',
:maxlength => 500,
:null => true,
:item_class => 'formGroup--halfSize',
},
:editable => false,
:active => true,
:screens => {
:signup => {},
:invite_agent => {},
:edit => {
'-all-' => {
:null => true,
},
},
:view => {
'-all-' => {
:shown => true,
},
},
},
:pending_migration => false,
:position => 1350,
:created_by_id => 1,
:updated_by_id => 1,
)
end
def down
end
end

View file

@ -0,0 +1,100 @@
class UpdateObjectManager3 < ActiveRecord::Migration
def up
ObjectManager::Attribute.add(
:object => 'User',
:name => 'active',
:display => 'Active',
:data_type => 'active',
:data_option => {
:default => true,
},
:editable => false,
:active => true,
:screens => {
:signup => {},
:invite_agent => {},
:edit => {
:Admin => {
:null => false,
},
},
:view => {
'-all-' => {
:shown => false,
},
},
},
:pending_migration => false,
:position => 1800,
:created_by_id => 1,
:updated_by_id => 1,
)
ObjectManager::Attribute.add(
:object => 'Organization',
:name => 'active',
:display => 'Active',
:data_type => 'active',
:data_option => {
:default => true,
},
:editable => false,
:active => true,
:screens => {
:edit => {
:Admin => {
:null => false,
},
},
:view => {
'-all-' => {
:shown => false,
},
},
},
:pending_migration => false,
:position => 1800,
:created_by_id => 1,
:updated_by_id => 1,
)
ObjectManager::Attribute.add(
:object => 'User',
:name => 'password',
:display => 'Password',
:data_type => 'input',
:data_option => {
:type => 'password',
:maxlength => 100,
:null => true,
:autocomplete => 'off',
:item_class => 'formGroup--halfSize',
},
:editable => false,
:active => true,
:screens => {
:signup => {
'-all-' => {
:null => false,
},
},
:invite_agent => {},
:edit => {
:Admin => {
:null => true,
},
},
:view => {}
},
:pending_migration => false,
:position => 1400,
:created_by_id => 1,
:updated_by_id => 1,
)
end
def down
end
end

View file

@ -1335,13 +1335,13 @@ Link::Object.create_if_not_exists( :name => 'Question/Answer' )
Link::Object.create_if_not_exists( :name => 'Idea' ) Link::Object.create_if_not_exists( :name => 'Idea' )
Link::Object.create_if_not_exists( :name => 'Bug' ) Link::Object.create_if_not_exists( :name => 'Bug' )
Ticket::StateType.create_if_not_exists( :id => 1, :name => 'new', :updated_by_id => 1 ) Ticket::StateType.create_if_not_exists( :id => 1, :name => 'new' )
Ticket::StateType.create_if_not_exists( :id => 2, :name => 'open', :updated_by_id => 1 ) Ticket::StateType.create_if_not_exists( :id => 2, :name => 'open' )
Ticket::StateType.create_if_not_exists( :id => 3, :name => 'pending reminder', :updated_by_id => 1 ) Ticket::StateType.create_if_not_exists( :id => 3, :name => 'pending reminder' )
Ticket::StateType.create_if_not_exists( :id => 4, :name => 'pending action', :updated_by_id => 1 ) Ticket::StateType.create_if_not_exists( :id => 4, :name => 'pending action' )
Ticket::StateType.create_if_not_exists( :id => 5, :name => 'closed', :updated_by_id => 1 ) Ticket::StateType.create_if_not_exists( :id => 5, :name => 'closed' )
Ticket::StateType.create_if_not_exists( :id => 6, :name => 'merged', :updated_by_id => 1 ) Ticket::StateType.create_if_not_exists( :id => 6, :name => 'merged' )
Ticket::StateType.create_if_not_exists( :id => 7, :name => 'removed', :updated_by_id => 1 ) Ticket::StateType.create_if_not_exists( :id => 7, :name => 'removed' )
Ticket::State.create_if_not_exists( :id => 1, :name => 'new', :state_type_id => Ticket::StateType.where(:name => 'new').first.id ) Ticket::State.create_if_not_exists( :id => 1, :name => 'new', :state_type_id => Ticket::StateType.where(:name => 'new').first.id )
Ticket::State.create_if_not_exists( :id => 2, :name => 'open', :state_type_id => Ticket::StateType.where(:name => 'open').first.id ) Ticket::State.create_if_not_exists( :id => 2, :name => 'open', :state_type_id => Ticket::StateType.where(:name => 'open').first.id )
@ -1938,6 +1938,7 @@ Translation.create_if_not_exists( :locale => 'de', :source => "to", :target => "
Translation.create_if_not_exists( :locale => 'de', :source => "%s ago", :target => "vor %s" ) Translation.create_if_not_exists( :locale => 'de', :source => "%s ago", :target => "vor %s" )
Translation.create_if_not_exists( :locale => 'de', :source => "in %s", :target => "in %s" ) Translation.create_if_not_exists( :locale => 'de', :source => "in %s", :target => "in %s" )
Translation.create_if_not_exists( :locale => 'de', :source => "Mark all as seen.", :target => "Alle als gelesen markieren." ) Translation.create_if_not_exists( :locale => 'de', :source => "Mark all as seen.", :target => "Alle als gelesen markieren." )
Translation.create_if_not_exists( :locale => 'de', :source => "Pending till", :target => "Warten bis" )
#Translation.create_if_not_exists( :locale => 'de', :source => "", :target => "" ) #Translation.create_if_not_exists( :locale => 'de', :source => "", :target => "" )
# install all packages in auto_install # install all packages in auto_install

View file

@ -971,6 +971,12 @@ module Import::OTRS2
# check if agent already exists # check if agent already exists
user_old = User.where( :id => user_new[:id] ).first user_old = User.where( :id => user_new[:id] ).first
# check if login is already used
login_in_use = User.where( "login = ? AND id != #{user_new[:id]}", user_new[:login].downcase ).count
if login_in_use > 0
user_new[:login] = "#{user_new[:login]}_#{user_new[:id]}"
end
# create / update agent # create / update agent
if user_old if user_old
puts "update User.find(#{user_old[:id]})" puts "update User.find(#{user_old[:id]})"

View file

@ -80,7 +80,7 @@ module NotificationFactory
} }
# translate # translate
data[:string].gsub!( /i18n\((.+?)\)/ ) { |placeholder| data[:string].gsub!( /i18n\((|.+?)\)/ ) { |placeholder|
string = $1 string = $1
locale = data[:locale] || 'en' locale = data[:locale] || 'en'
placeholder = Translation.translate( locale, string ) placeholder = Translation.translate( locale, string )

View file

@ -486,10 +486,12 @@ test( "clone", function() {
var source = [ var source = [
{ name: 'some name' }, { name: 'some name' },
{ name: 'some name2' }, { name: 'some name2' },
{ fn: function() { return 'test' } },
] ]
var reference = [ var reference = [
{ name: 'some name' }, { name: 'some name' },
{ name: 'some name2' }, { name: 'some name2' },
{ fn: undefined },
] ]
var result = clone( source ) var result = clone( source )
@ -498,6 +500,33 @@ test( "clone", function() {
deepEqual( result, reference, 'clone' ); deepEqual( result, reference, 'clone' );
// full test
var source = [
{ name: 'some name' },
{ name: 'some name2' },
{ fn: function a() { return 'test' } },
]
var reference = [
{ name: 'some name' },
{ name: 'some name2' },
{ fn: function a() { return 'test' } },
]
var result = clone( source, true )
// modify source later, should not have any result
source[0].name = 'some new name'
source[2].fn = 'some new name'
deepEqual( result[0], reference[0], 'clone full' );
deepEqual( result[1], reference[1], 'clone full' );
equal( typeof reference[2].fn, 'function')
equal( typeof result[2].fn, 'function')
equal( reference[2].fn(), 'test')
equal( result[2].fn(), 'test')
}); });
// diff // diff

View file

@ -17,7 +17,7 @@ test( "form validation check", function() {
{ name: 'richtext1', display: 'Richtext1', tag: 'richtext', maxlength: 100, null: false, type: 'richtext', multiline: true, upload: true, default: defaults['richtext1'] }, { name: 'richtext1', display: 'Richtext1', tag: 'richtext', maxlength: 100, null: false, type: 'richtext', multiline: true, upload: true, default: defaults['richtext1'] },
{ name: 'datetime1', display: 'Datetime1', tag: 'datetime', null: false, default: defaults['datetime1'] }, { name: 'datetime1', display: 'Datetime1', tag: 'datetime', null: false, default: defaults['datetime1'] },
{ name: 'date1', display: 'Date1', tag: 'date', null: false, default: defaults['date1'] }, { name: 'date1', display: 'Date1', tag: 'date', null: false, default: defaults['date1'] },
{ name: 'active1', display: 'Active1', tag: 'boolean', type: 'boolean', default: defaults['active1'], null: false }, { name: 'active1', display: 'Active1', tag: 'active', default: defaults['active1'] },
], ],
}, },
params: defaults, params: defaults,
@ -249,3 +249,83 @@ test( "date validation check", function() {
equal( el.find('[data-name="date1"]').closest('.form-group').find('.help-inline').text(), '', 'check date1 error message') equal( el.find('[data-name="date1"]').closest('.form-group').find('.help-inline').text(), '', 'check date1 error message')
}); });
test( "datetime selector check", function() {
$('#forms').append('<hr><h1>datetime selector check</h1><form id="form4"></form>')
var el = $('#form4')
var defaults = {}
var form = new App.ControllerForm({
el: el,
model: {
configure_attributes: [
{ name: 'datetime1', display: 'Datetime1', tag: 'datetime', null: false, default: defaults['datetime1'] },
],
},
params: defaults,
});
// check params
params = App.ControllerForm.params( el )
test_params = {
datetime1: undefined,
}
deepEqual( params, test_params, 'params check' )
el.find('.js-today').click()
// check params
timeStamp = new Date()
currentTime = timeStamp.toISOString()
currentTime = currentTime.replace(/(\d\d\.\d\d\dZ)$/, '00.000Z')
params = App.ControllerForm.params( el )
test_params = {
datetime1: currentTime,
}
deepEqual( params, test_params, 'params check' )
});
test( "date selector check", function() {
$('#forms').append('<hr><h1>date selector check</h1><form id="form5"></form>')
var el = $('#form5')
var defaults = {}
var form = new App.ControllerForm({
el: el,
model: {
configure_attributes: [
{ name: 'date1', display: 'Datet1', tag: 'date', null: false, default: defaults['date1'] },
],
},
params: defaults,
});
// check params
params = App.ControllerForm.params( el )
test_params = {
date1: undefined,
}
deepEqual( params, test_params, 'params check' )
el.find('.js-today').click()
// check params
format = function (number) {
if ( parseInt(number) < 10 ) {
number = '0' + number.toString()
}
return number
}
timeStamp = new Date()
currentTime = timeStamp.getFullYear() + '-' + format(timeStamp.getMonth()+1) + '-' + format(timeStamp.getDate())
params = App.ControllerForm.params( el )
test_params = {
date1: currentTime,
}
deepEqual( params, test_params, 'params check' )
});

View file

@ -159,8 +159,8 @@ test( "form params check", function() {
{ name: 'date2', display: 'Date2', tag: 'date', null: true, default: defaults['date2'] }, { name: 'date2', display: 'Date2', tag: 'date', null: true, default: defaults['date2'] },
{ name: 'date3', display: 'Date3', tag: 'date', null: false, default: defaults['date3'] }, { name: 'date3', display: 'Date3', tag: 'date', null: false, default: defaults['date3'] },
{ name: 'date4', display: 'Date4', tag: 'date', null: false, default: defaults['date4'] }, { name: 'date4', display: 'Date4', tag: 'date', null: false, default: defaults['date4'] },
{ name: 'active1', display: 'Active1', tag: 'boolean', type: 'boolean', default: defaults['active1'], null: false }, { name: 'active1', display: 'Active1', tag: 'active', default: defaults['active1'] },
{ name: 'active2', display: 'Active2', tag: 'boolean', type: 'boolean', default: defaults['active2'], null: false }, { name: 'active2', display: 'Active2', tag: 'active', default: defaults['active2'] },
], ],
}, },
params: defaults, params: defaults,
@ -795,7 +795,7 @@ test( "form required_if + shown_if", function() {
{ name: 'input2', display: 'Input2', tag: 'input', type: 'text', limit: 100, null: true, default: 'some used default', required_if: { active: true }, shown_if: { active: true } }, { name: 'input2', display: 'Input2', tag: 'input', type: 'text', limit: 100, null: true, default: 'some used default', required_if: { active: true }, shown_if: { active: true } },
{ name: 'input3', display: 'Input3', tag: 'input', type: 'text', limit: 100, null: true, default: 'some used default', required_if: { active: [true,false] }, shown_if: { active: [true,false] } }, { name: 'input3', display: 'Input3', tag: 'input', type: 'text', limit: 100, null: true, default: 'some used default', required_if: { active: [true,false] }, shown_if: { active: [true,false] } },
{ name: 'input4', display: 'Input4', tag: 'input', type: 'text', limit: 100, null: true, default: 'some used default', required_if: { active: [55,66] }, shown_if: { active: [55,66] } }, { name: 'input4', display: 'Input4', tag: 'input', type: 'text', limit: 100, null: true, default: 'some used default', required_if: { active: [55,66] }, shown_if: { active: [55,66] } },
{ name: 'active', display: 'Active', tag: 'boolean', type: 'boolean', 'default': true, null: false }, { name: 'active', display: 'Active', tag: 'active', 'default': true },
], ],
}, },
params: defaults, params: defaults,

View file

@ -0,0 +1,87 @@
window.onload = function() {
// model
test( "model ui basic tests", function() {
// load ref object
App.Collection.loadAssets({
TicketState: {
1: {
name: 'new', id: 1, updated_at: "2014-11-07T23:43:08.000Z",
},
2: {
name: 'open', id: 2, updated_at: "2014-11-07T23:43:08.000Z",
},
3: {
name: 'closed <>&', id: 3, updated_at: "2014-11-07T23:43:08.000Z",
},
},
})
// create ticket
var attribute1 = {
name: 'date', display: 'date 1', tag: 'date', null: true
};
App.Ticket.configure_attributes.push( attribute1 )
var attribute2 = {
name: 'textarea', display: 'textarea 1', tag: 'textarea', null: true
};
App.Ticket.configure_attributes.push( attribute2 )
var ticket = new App.Ticket()
ticket.load({
id: 1000,
title: 'some title <>&',
state_id: 2,
updated_at: '2014-11-07T23:43:08.000Z',
date: '2015-02-07',
textarea: "some new\nline"
})
App.i18n.set('en')
equal( App.viewPrint( ticket, 'id' ), 1000)
equal( App.viewPrint( ticket, 'title' ), 'some title &lt;&gt;&amp;')
equal( App.viewPrint( ticket, 'state' ), 'open')
equal( App.viewPrint( ticket, 'state_id' ), 'open')
equal( App.viewPrint( ticket, 'not_existing' ), '-')
equal( App.viewPrint( ticket, 'updated_at' ), "<span class=\"humanTimeFromNow undefined\" data-time=\"2014-11-07T23:43:08.000Z\">?</span>")
equal( App.viewPrint( ticket, 'date' ), '2015-02-07')
equal( App.viewPrint( ticket, 'textarea' ), '<div>some new</div><div>line</div>')
App.i18n.set('de')
equal( App.viewPrint( ticket, 'id' ), 1000)
equal( App.viewPrint( ticket, 'title' ), 'some title &lt;&gt;&amp;')
equal( App.viewPrint( ticket, 'state' ), 'offen')
equal( App.viewPrint( ticket, 'state_id' ), 'offen')
equal( App.viewPrint( ticket, 'not_existing' ), '-')
equal( App.viewPrint( ticket, 'updated_at' ), "<span class=\"humanTimeFromNow undefined\" data-time=\"2014-11-07T23:43:08.000Z\">?</span>")
equal( App.viewPrint( ticket, 'date' ), '07.02.2015')
equal( App.viewPrint( ticket, 'textarea' ), '<div>some new</div><div>line</div>')
App.i18n.set('en')
ticket.state_id = 3
equal( App.viewPrint( ticket, 'state' ), 'closed &lt;&gt;&amp;')
equal( App.viewPrint( ticket, 'state_id' ), 'closed &lt;&gt;&amp;')
App.i18n.set('de')
equal( App.viewPrint( ticket, 'state' ), 'closed &lt;&gt;&amp;')
equal( App.viewPrint( ticket, 'state_id' ), 'closed &lt;&gt;&amp;')
// normal string
data = {
a: 1,
b: 'abc',
c: {
displayName: function() { return "my displayName <>&" }
},
}
equal( App.viewPrint( data, 'a' ), 1)
equal( App.viewPrint( data, 'b' ), 'abc')
equal( App.viewPrint( data, 'c' ), 'my displayName &lt;&gt;&amp;')
});
}

View file

@ -410,14 +410,13 @@ test( "table test 2", function() {
equal( el.find('tbody > tr:nth-child(1) > td:nth-child(1)').text().trim(), 'adapter1', 'check row 1') equal( el.find('tbody > tr:nth-child(1) > td:nth-child(1)').text().trim(), 'adapter1', 'check row 1')
equal( el.find('tbody > tr:nth-child(1) > td:nth-child(2)').text().trim(), 'host1', 'check row 1') equal( el.find('tbody > tr:nth-child(1) > td:nth-child(2)').text().trim(), 'host1', 'check row 1')
equal( el.find('tbody > tr:nth-child(1) > td:nth-child(3)').text().trim(), 'user1', 'check row 1') equal( el.find('tbody > tr:nth-child(1) > td:nth-child(3)').text().trim(), 'user1', 'check row 1')
equal( el.find('tbody > tr:nth-child(1) > td:nth-child(4)').text().trim(), 'true', 'check row 1') equal( el.find('tbody > tr:nth-child(1) > td:nth-child(4)').text().trim(), 'ja', 'check row 1')
equal( el.find('tbody > tr:nth-child(1) > td:nth-child(4)').text().trim(), 'true', 'check row 1')
equal( el.find('tbody > tr:nth-child(1) > td:nth-child(5)').text().trim(), '', 'check row 1') equal( el.find('tbody > tr:nth-child(1) > td:nth-child(5)').text().trim(), '', 'check row 1')
equal( el.find('tbody > tr:nth-child(2) > td').length, 5, 'check row 2') equal( el.find('tbody > tr:nth-child(2) > td').length, 5, 'check row 2')
equal( el.find('tbody > tr:nth-child(2) > td:nth-child(1)').text().trim(), 'adapter2', 'check row 2') equal( el.find('tbody > tr:nth-child(2) > td:nth-child(1)').text().trim(), 'adapter2', 'check row 2')
equal( el.find('tbody > tr:nth-child(2) > td:nth-child(2)').text().trim(), 'host2', 'check row 2') equal( el.find('tbody > tr:nth-child(2) > td:nth-child(2)').text().trim(), 'host2', 'check row 2')
equal( el.find('tbody > tr:nth-child(2) > td:nth-child(3)').text().trim(), 'user2', 'check row 2') equal( el.find('tbody > tr:nth-child(2) > td:nth-child(3)').text().trim(), 'user2', 'check row 2')
equal( el.find('tbody > tr:nth-child(2) > td:nth-child(4)').text().trim(), 'true', 'check row 2') equal( el.find('tbody > tr:nth-child(2) > td:nth-child(4)').text().trim(), 'ja', 'check row 2')
equal( el.find('tbody > tr:nth-child(2) > td:nth-child(5)').text().trim(), '', 'check row 2') equal( el.find('tbody > tr:nth-child(2) > td:nth-child(5)').text().trim(), '', 'check row 2')
}); });

View file

@ -68,6 +68,28 @@ class AAbUnitTest < TestCase
] ]
browser_single_test(tests) browser_single_test(tests)
end end
def test_model_ui
tests = [
{
:name => 'start',
:instance => browser_instance,
:url => browser_url + '/tests-model-ui',
:action => [
{
:execute => 'wait',
:value => 8,
},
{
:execute => 'match',
:css => '.result .failed',
:value => '0',
:match_result => true,
},
],
},
]
browser_single_test(tests)
end
def test_form def test_form
tests = [ tests = [
{ {

View file

@ -0,0 +1,188 @@
# encoding: utf-8
require 'browser_test_helper'
class AgentOrganizationProfileTest < TestCase
def test_search_and_edit_verify_in_second
message = 'comment 1 ' + rand(99999999999999999).to_s
tests = [
{
:name => 'start',
:instance1 => browser_instance,
:instance2 => browser_instance,
:instance1_username => 'master@example.com',
:instance1_password => 'test',
:instance2_username => 'agent1@example.com',
:instance2_password => 'test',
:url => browser_url,
:action => [
{
:where => :instance1,
:execute => 'close_all_tasks',
},
{
:where => :instance2,
:execute => 'close_all_tasks',
},
{
:where => :instance1,
:execute => 'search_organization',
:term => 'Zammad Foundation',
},
{
:where => :instance2,
:execute => 'search_organization',
:term => 'Zammad Foundation',
},
# update note
{
:where => :instance1,
:execute => 'set',
:css => '.active [data-name="note"]',
:value => message,
},
{
:where => :instance1,
:execute => 'click',
:css => '.active .profile',
},
{
:where => :instance1,
:execute => 'wait',
:value => 3,
},
# verify
{
:where => :instance2,
:execute => 'match',
:css => '.active .profile-window',
:value => message,
:match_result => true,
},
],
},
]
browser_double_test(tests)
end
def test_search_and_edit_in_one
message = '1 ' + rand(99999999).to_s
tests = [
{
:name => 'search and edit',
:action => [
{
:execute => 'close_all_tasks',
},
# search and open org
{
:execute => 'search_organization',
:term => 'Zammad Foundation',
},
{
:execute => 'match',
:css => '.active .profile-window',
:value => 'note',
:match_result => true,
},
{
:execute => 'match',
:css => '.active .profile-window',
:value => 'member',
:match_result => true,
},
# update note
{
:execute => 'set',
:css => '.active [data-name="note"]',
:value => 'some note 123'
},
{
:execute => 'click',
:css => '.active .profile',
},
{
:execute => 'wait',
:value => 1,
},
# check and change note again in edit screen
{
:execute => 'click',
:css => '.active .js-action .select-arrow',
},
{
:execute => 'click',
:css => '.active .js-action a[data-type="edit"]',
},
{
:execute => 'wait',
:value => 1,
},
{
:execute => 'match',
:css => '.active .modal',
:value => 'note',
:match_result => true,
},
{
:execute => 'match',
:css => '.active .modal',
:value => 'some note 123',
:match_result => true,
},
{
:execute => 'set',
:css => '.active .modal [data-name="note"]',
:value => 'some note abc'
},
{
:execute => 'click',
:css => '.active .modal button.js-submit',
},
{
:execute => 'wait',
:value => 4,
},
{
:execute => 'match',
:css => '.active .profile-window',
:value => 'some note abc',
:match_result => true,
},
# create new ticket
{
:execute => 'create_ticket',
:group => 'Users',
:subject => 'org profile check ' + message,
:body => 'org profile check ' + message,
},
{
:execute => 'wait',
:value => 4,
},
# switch to org tab, verify if ticket is shown
{
:execute => 'search_organization',
:term => 'Zammad Foundation',
},
{
:execute => 'match',
:css => '.active .profile-window',
:value => 'org profile check ' + message,
:match_result => true,
},
],
},
]
browser_signle_test_with_login(tests, { :username => 'master@example.com' })
end
end

View file

@ -0,0 +1,188 @@
# encoding: utf-8
require 'browser_test_helper'
class AgentUserProfileTest < TestCase
def test_search_and_edit_verify_in_second
message = 'comment 1 ' + rand(99999999999999999).to_s
tests = [
{
:name => 'start',
:instance1 => browser_instance,
:instance2 => browser_instance,
:instance1_username => 'master@example.com',
:instance1_password => 'test',
:instance2_username => 'agent1@example.com',
:instance2_password => 'test',
:url => browser_url,
:action => [
{
:where => :instance1,
:execute => 'close_all_tasks',
},
{
:where => :instance2,
:execute => 'close_all_tasks',
},
{
:where => :instance1,
:execute => 'search_user',
:term => 'Braun',
},
{
:where => :instance2,
:execute => 'search_user',
:term => 'Braun',
},
# update note
{
:where => :instance1,
:execute => 'set',
:css => '.active [data-name="note"]',
:value => message,
},
{
:where => :instance1,
:execute => 'click',
:css => '.active .profile',
},
{
:where => :instance1,
:execute => 'wait',
:value => 3,
},
# verify
{
:where => :instance2,
:execute => 'match',
:css => '.active .profile-window',
:value => message,
:match_result => true,
},
],
},
]
browser_double_test(tests)
end
def test_search_and_edit_in_one
message = '1 ' + rand(99999999).to_s
tests = [
{
:name => 'search and edit',
:action => [
{
:execute => 'close_all_tasks',
},
# search and open user
{
:execute => 'search_user',
:term => 'Braun',
},
{
:execute => 'match',
:css => '.active .profile-window',
:value => 'note',
:match_result => true,
},
{
:execute => 'match',
:css => '.active .profile-window',
:value => 'email',
:match_result => true,
},
# update note
{
:execute => 'set',
:css => '.active [data-name="note"]',
:value => 'some note 123'
},
{
:execute => 'click',
:css => '.active .profile',
},
{
:execute => 'wait',
:value => 1,
},
# check and change note again in edit screen
{
:execute => 'click',
:css => '.active .js-action .select-arrow',
},
{
:execute => 'click',
:css => '.active .js-action a[data-type="edit"]',
},
{
:execute => 'wait',
:value => 1,
},
{
:execute => 'match',
:css => '.active .modal',
:value => 'note',
:match_result => true,
},
{
:execute => 'match',
:css => '.active .modal',
:value => 'some note 123',
:match_result => true,
},
{
:execute => 'set',
:css => '.active .modal [data-name="note"]',
:value => 'some note abc'
},
{
:execute => 'click',
:css => '.active .modal button.js-submit',
},
{
:execute => 'wait',
:value => 4,
},
{
:execute => 'match',
:css => '.active .profile-window',
:value => 'some note abc',
:match_result => true,
},
# create new ticket
{
:execute => 'create_ticket',
:group => 'Users',
:subject => 'user profile check ' + message,
:body => 'user profile check ' + message,
},
{
:execute => 'wait',
:value => 4,
},
# switch to org tab, verify if ticket is shown
{
:execute => 'search_user',
:term => 'Braun',
},
{
:execute => 'match',
:css => '.active .profile-window',
:value => 'user profile check ' + message,
:match_result => true,
},
],
},
]
browser_signle_test_with_login(tests, { :username => 'master@example.com' })
end
end

View file

@ -525,6 +525,55 @@ class TestCase < Test::Unit::TestCase
} }
assert( false, "(#{test[:name]}) ticket creation failed, can't get zoom url" ) assert( false, "(#{test[:name]}) ticket creation failed, can't get zoom url" )
return return
elsif action[:execute] == 'search_user'
element = instance.find_elements( { :css => '#global-search' } )[0]
element.click
element.clear
if @stack
action[:term].gsub! '###stack###', @stack
end
element.send_keys( action[:term] )
sleep 3
element = instance.find_element( { :partial_link_text => action[:term] } ).click
name = instance.find_elements( { :css => '.active h1' } )[0].text
if name !~ /#{action[:term]}/
assert( false, "(#{test[:name]}) unable to search/find user #{action[:term]}!" )
return
end
assert( true, "(#{test[:name]}) user #{action[:term]} found" )
return
elsif action[:execute] == 'search_organization'
element = instance.find_elements( { :css => '#global-search' } )[0]
element.click
element.clear
if @stack
action[:term].gsub! '###stack###', @stack
end
element.send_keys( action[:term] )
sleep 3
instance.find_elements( { :css => '.search .empty-search' } )[0].click
sleep 0.5
text = instance.find_elements( { :css => '#global-search' } )[0].attribute('value')
if !text
assert( false, "(#{test[:name]}) #global-search is not empty!" )
return
end
element = instance.find_elements( { :css => '#global-search' } )[0]
element.click
element.clear
if @stack
action[:term].gsub! '###stack###', @stack
end
element.send_keys( action[:term] )
sleep 2
element = instance.find_element( { :partial_link_text => action[:term] } ).click
name = instance.find_elements( { :css => '.active h1' } )[0].text
if name !~ /#{action[:term]}/
assert( false, "(#{test[:name]}) unable to search/find org #{action[:term]}!" )
return
end
assert( true, "(#{test[:name]}) org #{action[:term]} found" )
return
elsif action[:execute] == 'search_ticket' elsif action[:execute] == 'search_ticket'
element = instance.find_elements( { :css => '#global-search' } )[0] element = instance.find_elements( { :css => '#global-search' } )[0]
element.click element.click
@ -544,7 +593,7 @@ class TestCase < Test::Unit::TestCase
element.clear element.clear
action[:number].gsub! '###stack###', @stack action[:number].gsub! '###stack###', @stack
element.send_keys( action[:number] ) element.send_keys( action[:number] )
sleep 3 sleep 2
element = instance.find_element( { :partial_link_text => action[:number] } ).click element = instance.find_element( { :partial_link_text => action[:number] } ).click
number = instance.find_elements( { :css => '.active .page-header .ticket-number' } )[0].text number = instance.find_elements( { :css => '.active .page-header .ticket-number' } )[0].text
if number !~ /#{action[:number]}/ if number !~ /#{action[:number]}/
@ -684,14 +733,14 @@ class TestCase < Test::Unit::TestCase
match = false match = false
if action[:no_quote] if action[:no_quote]
#puts "aaaa #{text}/#{action[:value]}" #puts "aaaa #{text}/#{action[:value]}"
if text =~ /#{action[:value]}/ if text =~ /#{action[:value]}/i
if $1 if $1
@stack = $1 @stack = $1
end end
match = $1 || true match = $1 || true
end end
else else
if text =~ /#{Regexp.quote(action[:value])}/ if text =~ /#{Regexp.quote(action[:value])}/i
match = true match = true
end end
end end

View file

@ -0,0 +1,56 @@
# encoding: utf-8
require 'test_helper'
class DbAutoIncrementTest < ActiveSupport::TestCase
test 'id overwrite' do
setting_backup = Setting.get('system_init_done')
Setting.set('system_init_done', false)
Ticket::StateType.create_if_not_exists( :id => 200, :name => 'unit test 1', :updated_by_id => 1, :created_by_id => 1 )
state_type = Ticket::StateType.where( :name => 'unit test 1' ).first
assert_equal( Ticket::StateType.to_s, state_type.class.to_s )
assert_equal( 'unit test 1', state_type.name )
Ticket::StateType.create_if_not_exists( :id => 200, :name => 'unit test 1 _ should not be created', :updated_by_id => 1, :created_by_id => 1 )
state_type = Ticket::StateType.where( :id => 200 ).first
assert_equal( Ticket::StateType.to_s, state_type.class.to_s )
assert_equal( 'unit test 1', state_type.name )
Ticket::StateType.create_or_update( :id => 200, :name => 'unit test 1 _ should be updated', :updated_by_id => 1, :created_by_id => 1 )
state_type = Ticket::StateType.where( :name => 'unit test 1 _ should be updated' ).first
assert_equal( Ticket::StateType.to_s, state_type.class.to_s )
assert_equal( 'unit test 1 _ should be updated', state_type.name )
state_type = Ticket::StateType.where( :id => 200 ).first
assert_equal( Ticket::StateType.to_s, state_type.class.to_s )
assert_equal( 'unit test 1 _ should be updated', state_type.name )
Ticket::State.create_if_not_exists( :id => 210, :name => 'unit test 1', :state_type_id => Ticket::StateType.where(:name => 'unit test 1 _ should be updated').first.id, :updated_by_id => 1, :created_by_id => 1 )
state = Ticket::State.where( :name => 'unit test 1' ).first
assert_equal( Ticket::State.to_s, state.class.to_s )
assert_equal( 'unit test 1', state.name )
Ticket::State.create_if_not_exists( :id => 210, :name => 'unit test 1 _ should not be created', :state_type_id => Ticket::StateType.where(:name => 'unit test 1 _ should be updated').first.id, :updated_by_id => 1, :created_by_id => 1 )
state = Ticket::State.where( :id => 210 ).first
assert_equal( Ticket::State.to_s, state.class.to_s )
assert_equal( 'unit test 1', state.name )
Ticket::State.create_or_update( :id => 210, :name => 'unit test 1 _ should be updated', :state_type_id => Ticket::StateType.where(:name => 'unit test 1 _ should be updated').first.id, :updated_by_id => 1, :created_by_id => 1 )
state = Ticket::State.where( :name => 'unit test 1 _ should be updated' ).first
assert_equal( Ticket::State.to_s, state.class.to_s )
assert_equal( 'unit test 1 _ should be updated', state.name )
state = Ticket::State.where( :id => 210 ).first
assert_equal( Ticket::State.to_s, state.class.to_s )
assert_equal( 'unit test 1 _ should be updated', state.name )
Setting.set('system_init_done', setting_backup)
end
end

View file

@ -126,6 +126,21 @@ class NotificationFactoryTest < ActiveSupport::TestCase
:string => '\#{puts `ls`}', :string => '\#{puts `ls`}',
:result => '\#{puts `ls`} (not allowed)', :result => '\#{puts `ls`} (not allowed)',
}, },
{
:locale => 'de',
:string => 'test i18n(new)',
:result => 'test neu',
},
{
:locale => 'de',
:string => 'test i18n()',
:result => 'test ',
},
{
:locale => 'de',
:string => 'test i18n(new) i18n(open)',
:result => 'test neu offen',
},
] ]
tests.each { |test| tests.each { |test|
result = NotificationFactory.build( result = NotificationFactory.build(

View file

@ -288,6 +288,20 @@ class TicketNotificationTest < ActiveSupport::TestCase
assert_equal( 0, notification_check(ticket3, agent2), ticket3.id ) assert_equal( 0, notification_check(ticket3, agent2), ticket3.id )
# update article / not notification should be sent
article_inbound.internal = true
article_inbound.save
# execute ticket events
Observer::Ticket::Notification.transaction
#puts Delayed::Job.all.inspect
Delayed::Worker.new.work_off
# verify notifications not to agent1 and not to agent2
assert_equal( 2, notification_check(ticket3, agent1), ticket3.id )
assert_equal( 0, notification_check(ticket3, agent2), ticket3.id )
delete = ticket1.destroy delete = ticket1.destroy
assert( delete, "ticket1 destroy" ) assert( delete, "ticket1 destroy" )
@ -392,16 +406,22 @@ class TicketNotificationTest < ActiveSupport::TestCase
:article_id => article.id, :article_id => article.id,
:type => 'update', :type => 'update',
:changes => { :changes => {
:priority_id => [1, 2], 'priority_id' => [1, 2],
'pending_time' => [nil, Time.parse("2015-01-11 23:33:47 UTC")],
}, },
) )
# check changed attributes # check changed attributes
human_changes = bg.human_changes(agent1,ticket1) human_changes = bg.human_changes(agent1,ticket1)
assert( human_changes['Priority'], 'Check if attributes translated based on ObjectManager::Attribute' ) assert( human_changes['Priority'], 'Check if attributes translated based on ObjectManager::Attribute' )
assert( human_changes['Pending till'], 'Check if attributes translated based on ObjectManager::Attribute' )
assert_equal( 'i18n(1 low)', human_changes['Priority'][0] ) assert_equal( 'i18n(1 low)', human_changes['Priority'][0] )
assert_equal( 'i18n(2 normal)', human_changes['Priority'][1] ) assert_equal( 'i18n(2 normal)', human_changes['Priority'][1] )
assert_equal( 'i18n()', human_changes['Pending till'][0] )
assert_equal( 'i18n(2015-01-11 23:33:47 UTC)', human_changes['Pending till'][1] )
assert_not( human_changes['priority_id'] ) assert_not( human_changes['priority_id'] )
assert_not( human_changes['pending_time'] )
assert_not( human_changes['pending_till'] )
# en template # en template
template = bg.template_update(agent2, ticket1, article, human_changes) template = bg.template_update(agent2, ticket1, article, human_changes)
@ -410,6 +430,8 @@ class TicketNotificationTest < ActiveSupport::TestCase
assert_match( /Priority/, template[:body] ) assert_match( /Priority/, template[:body] )
assert_match( /1 low/, template[:body] ) assert_match( /1 low/, template[:body] )
assert_match( /2 normal/, template[:body] ) assert_match( /2 normal/, template[:body] )
assert_match( /Pending till/, template[:body] )
assert_match( /2015-01-11 23:33:47 UTC/, template[:body] )
assert_match( /updated/i, template[:subject] ) assert_match( /updated/i, template[:subject] )
# en notification # en notification
@ -435,7 +457,11 @@ class TicketNotificationTest < ActiveSupport::TestCase
assert_match( /Priority/, body ) assert_match( /Priority/, body )
assert_match( /1 low/, body ) assert_match( /1 low/, body )
assert_match( /2 normal/, body ) assert_match( /2 normal/, body )
assert_match( /Pending till/, body )
assert_match( /2015-01-11 23:33:47 UTC/, body )
assert_match( /update/, body ) assert_match( /update/, body )
assert_no_match( /pending_till/, body )
assert_no_match( /i18n/, body )
# de template # de template
template = bg.template_update(agent1, ticket1, article, human_changes) template = bg.template_update(agent1, ticket1, article, human_changes)
@ -444,6 +470,8 @@ class TicketNotificationTest < ActiveSupport::TestCase
assert_match( /Priority/, template[:body] ) assert_match( /Priority/, template[:body] )
assert_match( /1 low/, template[:body] ) assert_match( /1 low/, template[:body] )
assert_match( /2 normal/, template[:body] ) assert_match( /2 normal/, template[:body] )
assert_match( /Pending till/, template[:body] )
assert_match( /2015-01-11 23:33:47 UTC/, template[:body] )
assert_match( /aktualis/, template[:subject] ) assert_match( /aktualis/, template[:subject] )
# de notification # de notification
@ -470,7 +498,45 @@ class TicketNotificationTest < ActiveSupport::TestCase
assert_match( /Priorität/, body ) assert_match( /Priorität/, body )
assert_match( /1 niedrig/, body ) assert_match( /1 niedrig/, body )
assert_match( /2 normal/, body ) assert_match( /2 normal/, body )
assert_match( /Warten/, body )
assert_match( /2015-01-11 23:33:47 UTC/, body )
assert_match( /aktualis/, body ) assert_match( /aktualis/, body )
assert_no_match( /pending_till/, body )
assert_no_match( /i18n/, body )
bg = Observer::Ticket::Notification::BackgroundJob.new(
:ticket_id => ticket1.id,
:article_id => article.id,
:type => 'update',
:changes => {
:title => ['some notification template test 1', 'some notification template test 1 #2'],
:priority_id => [2, 3],
},
)
puts "hc #{human_changes.inspect}"
# check changed attributes
human_changes = bg.human_changes(agent1,ticket1)
assert( human_changes['Title'], 'Check if attributes translated based on ObjectManager::Attribute' )
assert( human_changes['Priority'], 'Check if attributes translated based on ObjectManager::Attribute' )
assert_equal( 'i18n(2 normal)', human_changes['Priority'][0] )
assert_equal( 'i18n(3 high)', human_changes['Priority'][1] )
assert_equal( 'some notification template test 1', human_changes['Title'][0] )
assert_equal( 'some notification template test 1 #2', human_changes['Title'][1] )
assert_not( human_changes['priority_id'] )
assert_not( human_changes['pending_time'] )
assert_not( human_changes['pending_till'] )
human_changes = bg.human_changes(agent2,ticket1)
puts "hc2 #{human_changes.inspect}"
template = bg.template_update(agent1, ticket1, article, human_changes)
puts "t1 #{template.inspect}"
template = bg.template_update(agent2, ticket1, article, human_changes)
puts "t2 #{template.inspect}"
end end

View file

@ -0,0 +1,161 @@
# encoding: utf-8
require 'test_helper'
class TicketRefObjectTouchTest < ActiveSupport::TestCase
# create base
groups = Group.where( :name => 'Users' )
roles = Role.where( :name => 'Agent' )
agent1 = User.create_or_update(
:login => 'ticket-ref-object-update-agent1@example.com',
:firstname => 'Notification',
:lastname => 'Agent1',
:email => 'ticket-ref-object-update-agent1@example.com',
:password => 'agentpw',
:active => true,
:roles => roles,
:groups => groups,
:updated_at => '2015-02-05 16:37:00',
:updated_by_id => 1,
:created_by_id => 1,
)
roles = Role.where( :name => 'Customer' )
organization1 = Organization.create_if_not_exists(
:name => 'Ref Object Update Org',
:updated_at => '2015-02-05 16:37:00',
:updated_by_id => 1,
:created_by_id => 1,
)
customer1 = User.create_or_update(
:login => 'ticket-ref-object-update-customer1@example.com',
:firstname => 'Notification',
:lastname => 'Agent1',
:email => 'ticket-ref-object-update-customer1@example.com',
:password => 'customerpw',
:active => true,
:organization_id => organization1.id,
:roles => roles,
:updated_at => '2015-02-05 16:37:00',
:updated_by_id => 1,
:created_by_id => 1,
)
customer2 = User.create_or_update(
:login => 'ticket-ref-object-update-customer2@example.com',
:firstname => 'Notification',
:lastname => 'Agent2',
:email => 'ticket-ref-object-update-customer2@example.com',
:password => 'customerpw',
:active => true,
:organization_id => nil,
:roles => roles,
:updated_at => '2015-02-05 16:37:00',
:updated_by_id => 1,
:created_by_id => 1,
)
test 'a - check if customer and organization has been updated' do
ticket = Ticket.create(
:title => "some title1\n äöüß",
:group => Group.lookup( :name => 'Users'),
:customer_id => customer1.id,
:owner_id => agent1.id,
:state => Ticket::State.lookup( :name => 'new' ),
:priority => Ticket::Priority.lookup( :name => '2 normal' ),
:updated_by_id => 1,
:created_by_id => 1,
)
assert( ticket, "ticket created" )
assert_equal( ticket.customer.id, customer1.id )
assert_equal( ticket.organization.id, organization1.id )
# check if customer and organization has been touched
customer1 = User.find(customer1.id)
if customer1.updated_at > 2.second.ago
assert( true, "customer1.updated_at has been updated" )
else
assert( false, "customer1.updated_at has not been updated" )
end
organization1 = Organization.find(organization1.id)
if organization1.updated_at > 2.second.ago
assert( true, "organization1.updated_at has been updated" )
else
assert( false, "organization1.updated_at has not been updated" )
end
sleep 4
delete = ticket.destroy
assert( delete, "ticket destroy" )
# check if customer and organization has been touched
customer1 = User.find(customer1.id)
if customer1.updated_at > 2.second.ago
assert( true, "customer1.updated_at has been updated" )
else
assert( false, "customer1.updated_at has not been updated" )
end
organization1 = Organization.find(organization1.id)
if organization1.updated_at > 2.second.ago
assert( true, "organization1.updated_at has been updated" )
else
assert( false, "organization1.updated_at has not been updated" )
end
end
test 'b - check if customer (not organization) has been updated' do
sleep 3
ticket = Ticket.create(
:title => "some title2\n äöüß",
:group => Group.lookup( :name => 'Users'),
:customer_id => customer2.id,
:owner_id => agent1.id,
:state => Ticket::State.lookup( :name => 'new' ),
:priority => Ticket::Priority.lookup( :name => '2 normal' ),
:updated_by_id => 1,
:created_by_id => 1,
)
assert( ticket, "ticket created" )
assert_equal( ticket.customer.id, customer2.id )
assert_equal( ticket.organization, nil )
# check if customer and organization has been touched
customer2 = User.find(customer2.id)
if customer2.updated_at > 2.second.ago
assert( true, "customer2.updated_at has been updated" )
else
assert( false, "customer2.updated_at has not been updated" )
end
organization1 = Organization.find(organization1.id)
if organization1.updated_at > 2.second.ago
assert( false, "organization1.updated_at has been updated" )
else
assert( true, "organization1.updated_at has not been updated" )
end
sleep 3
delete = ticket.destroy
assert( delete, "ticket destroy" )
# check if customer and organization has been touched
customer2 = User.find(customer2.id)
if customer2.updated_at > 2.second.ago
assert( true, "customer2.updated_at has been updated" )
else
assert( false, "customer2.updated_at has not been updated" )
end
organization1 = Organization.find(organization1.id)
if organization1.updated_at > 2.second.ago
assert( false, "organization1.updated_at has been updated" )
else
assert( true, "organization1.updated_at has not been updated" )
end
end
end

View file

@ -101,6 +101,25 @@ class TicketTest < ActiveSupport::TestCase
assert( ticket.close_time, 'ticket.close_time verify - state update' ) assert( ticket.close_time, 'ticket.close_time verify - state update' )
# set pending time
ticket.state_id = Ticket::State.where(:name => 'pending reminder').first.id
ticket.pending_time = Time.parse("1977-10-27 22:00:00 +0000")
ticket.save
ticket = Ticket.find(ticket.id)
assert_equal( ticket.state.name, 'pending reminder', 'state verify' )
assert_equal( ticket.pending_time, Time.parse("1977-10-27 22:00:00 +0000"), 'pending_time verify' )
# reset pending state, should also reset pending time
ticket.state_id = Ticket::State.where(:name => 'closed').first.id
ticket.save
ticket = Ticket.find(ticket.id)
assert_equal( ticket.state.name, 'closed', 'state verify' )
assert_equal( ticket.pending_time, nil )
delete = ticket.destroy delete = ticket.destroy
assert( delete, "ticket destroy" ) assert( delete, "ticket destroy" )
end end