Init sla management feature.
This commit is contained in:
parent
6aac51b633
commit
c21f1ce93f
17 changed files with 516 additions and 435 deletions
|
@ -227,284 +227,6 @@ class App.ControllerForm extends App.Controller
|
|||
if App.UiElement[attribute.tag]
|
||||
item = App.UiElement[attribute.tag].render(attribute, @params, @)
|
||||
|
||||
# working_hour
|
||||
else if attribute.tag is 'time_before_last'
|
||||
if !attribute.value
|
||||
attribute.value = {}
|
||||
item = $( App.view('generic/time_before_last')( attribute: attribute ) )
|
||||
item.find( "[name=\"#{attribute.name}::direction\"]").find("option[value=\"#{attribute.value.direction}\"]").attr( 'selected', 'selected' )
|
||||
item.find( "[name=\"#{attribute.name}::count\"]").find("option[value=\"#{attribute.value.count}\"]").attr( 'selected', 'selected' )
|
||||
item.find( "[name=\"#{attribute.name}::area\"]").find("option[value=\"#{attribute.value.area}\"]").attr( 'selected', 'selected' )
|
||||
|
||||
# ticket attribute set
|
||||
else if attribute.tag is 'ticket_attribute_set'
|
||||
|
||||
# list of possible attributes
|
||||
item = $(
|
||||
App.view('generic/ticket_attribute_manage')(
|
||||
attribute: attribute
|
||||
)
|
||||
)
|
||||
|
||||
addShownAttribute = ( key, value ) =>
|
||||
parts = key.split(/::/)
|
||||
key = parts[0]
|
||||
type = parts[1]
|
||||
if key is 'tickets.title'
|
||||
attribute_config = {
|
||||
name: attribute.name + '::tickets.title'
|
||||
display: 'Title'
|
||||
tag: 'input'
|
||||
type: 'text'
|
||||
null: false
|
||||
value: value
|
||||
remove: true
|
||||
}
|
||||
else if key is 'tickets.group_id'
|
||||
attribute_config = {
|
||||
name: attribute.name + '::tickets.group_id'
|
||||
display: 'Group'
|
||||
tag: 'select'
|
||||
multiple: false
|
||||
null: false
|
||||
nulloption: false
|
||||
relation: 'Group'
|
||||
value: value
|
||||
remove: true
|
||||
}
|
||||
else if key is 'tickets.owner_id' || key is 'tickets.customer_id'
|
||||
display = 'Owner'
|
||||
name = 'owner_id'
|
||||
if key is 'customer_id'
|
||||
display = 'Customer'
|
||||
name = 'customer_id'
|
||||
attribute_config = {
|
||||
name: attribute.name + '::tickets.' + name
|
||||
display: display
|
||||
tag: 'select'
|
||||
multiple: false
|
||||
null: false
|
||||
nulloption: false
|
||||
relation: 'User'
|
||||
value: value || null
|
||||
remove: true
|
||||
filter: ( all, type ) ->
|
||||
return all if type isnt 'collection'
|
||||
all = _.filter( all, (item) ->
|
||||
return if item.id is 1
|
||||
return item
|
||||
)
|
||||
all.unshift( {
|
||||
id: ''
|
||||
name: '--'
|
||||
} )
|
||||
all.unshift( {
|
||||
id: 1
|
||||
name: '*** not set ***'
|
||||
} )
|
||||
all.unshift( {
|
||||
id: 'current_user.id'
|
||||
name: '*** current user ***'
|
||||
} )
|
||||
all
|
||||
}
|
||||
else if key is 'tickets.organization_id'
|
||||
attribute_config = {
|
||||
name: attribute.name + '::tickets.organization_id'
|
||||
display: 'Organization'
|
||||
tag: 'select'
|
||||
multiple: false
|
||||
null: false
|
||||
nulloption: false
|
||||
relation: 'Organization'
|
||||
value: value || null
|
||||
remove: true
|
||||
filter: ( all, type ) ->
|
||||
return all if type isnt 'collection'
|
||||
all.unshift( {
|
||||
id: ''
|
||||
name: '--'
|
||||
} )
|
||||
all.unshift( {
|
||||
id: 'current_user.organization_id'
|
||||
name: '*** organization of current user ***'
|
||||
} )
|
||||
all
|
||||
}
|
||||
else if key is 'tickets.state_id'
|
||||
attribute_config = {
|
||||
name: attribute.name + '::tickets.state_id'
|
||||
display: 'State'
|
||||
tag: 'select'
|
||||
multiple: false
|
||||
null: false
|
||||
nulloption: false
|
||||
relation: 'TicketState'
|
||||
value: value
|
||||
translate: true
|
||||
remove: true
|
||||
}
|
||||
else if key is 'tickets.priority_id'
|
||||
attribute_config = {
|
||||
name: attribute.name + '::tickets.priority_id'
|
||||
display: 'Priority'
|
||||
tag: 'select'
|
||||
multiple: false
|
||||
null: false
|
||||
nulloption: false
|
||||
relation: 'TicketPriority'
|
||||
value: value
|
||||
translate: true
|
||||
remove: true
|
||||
}
|
||||
else
|
||||
attribute_config = {
|
||||
name: attribute.name + '::' + key
|
||||
display: 'FIXME!'
|
||||
tag: 'input'
|
||||
type: 'text'
|
||||
value: value
|
||||
remove: true
|
||||
}
|
||||
item.find('select[name=ticket_attribute_list] option[value="' + key + '"]').hide().prop('disabled', true)
|
||||
|
||||
itemSub = @formGenItem( attribute_config )
|
||||
itemSub.find('.glyphicon-minus').bind('click', (e) ->
|
||||
e.preventDefault()
|
||||
value = $(e.target).closest('.controls').find('[name]').attr('name')
|
||||
if value
|
||||
value = value.replace("#{attribute.name}::", '')
|
||||
$(e.target).closest('.sub_attribute').find('select[name=ticket_attribute_list] option[value="' + value + '"]').show().prop('disabled', false)
|
||||
$(@).parent().parent().parent().remove()
|
||||
)
|
||||
# itemSub.append('<a href=\"#\" class=\"icon-minus\"></a>')
|
||||
item.find('.ticket_attribute_item').append( itemSub )
|
||||
|
||||
# list of existing attributes
|
||||
attribute_config = {
|
||||
name: 'ticket_attribute_list'
|
||||
display: 'Add Attribute'
|
||||
tag: 'select'
|
||||
multiple: false
|
||||
null: false
|
||||
# nulloption: true
|
||||
options: [
|
||||
{
|
||||
value: ''
|
||||
name: '-- Ticket --'
|
||||
selected: false
|
||||
disable: true
|
||||
},
|
||||
{
|
||||
value: 'tickets.title'
|
||||
name: 'Title'
|
||||
selected: false
|
||||
disable: false
|
||||
},
|
||||
{
|
||||
value: 'tickets.group_id'
|
||||
name: 'Group'
|
||||
selected: false
|
||||
disable: false
|
||||
},
|
||||
{
|
||||
value: 'tickets.state_id'
|
||||
name: 'State'
|
||||
selected: false
|
||||
disable: false
|
||||
},
|
||||
{
|
||||
value: 'tickets.priority_id'
|
||||
name: 'Priority'
|
||||
selected: true
|
||||
disable: false
|
||||
},
|
||||
{
|
||||
value: 'tickets.owner_id'
|
||||
name: 'Owner'
|
||||
selected: true
|
||||
disable: false
|
||||
},
|
||||
# # {
|
||||
# value: 'tag'
|
||||
# name: 'Tag'
|
||||
# selected: true
|
||||
# disable: false
|
||||
# },
|
||||
# {
|
||||
# value: '-a'
|
||||
# name: '-- ' + App.i18n.translateInline('Article') + ' --'
|
||||
# selected: false
|
||||
# disable: true
|
||||
# },
|
||||
# {
|
||||
# value: 'ticket_articles.from'
|
||||
# name: 'From'
|
||||
# selected: true
|
||||
# disable: false
|
||||
# },
|
||||
# {
|
||||
# value: 'ticket_articles.to'
|
||||
# name: 'To'
|
||||
# selected: true
|
||||
# disable: false
|
||||
# },
|
||||
# {
|
||||
# value: 'ticket_articles.cc'
|
||||
# name: 'Cc'
|
||||
# selected: true
|
||||
# disable: false
|
||||
# },
|
||||
# {
|
||||
# value: 'ticket_articles.subject'
|
||||
# name: 'Subject'
|
||||
# selected: true
|
||||
# disable: false
|
||||
# },
|
||||
# {
|
||||
# value: 'ticket_articles.body'
|
||||
# name: 'Text'
|
||||
# selected: true
|
||||
# disable: false
|
||||
# },
|
||||
{
|
||||
value: '-c'
|
||||
name: '-- ' + App.i18n.translateInline('Customer') + ' --'
|
||||
selected: false
|
||||
disable: true
|
||||
},
|
||||
{
|
||||
value: 'customers.id'
|
||||
name: 'Customer'
|
||||
selected: true
|
||||
disable: false
|
||||
},
|
||||
{
|
||||
value: 'organization.id'
|
||||
name: 'Organization'
|
||||
selected: true
|
||||
disable: false
|
||||
},
|
||||
]
|
||||
default: ''
|
||||
translate: true
|
||||
class: 'medium'
|
||||
add: true
|
||||
}
|
||||
list = @formGenItem( attribute_config )
|
||||
list.find('.glyphicon-plus').bind('click', (e) ->
|
||||
e.preventDefault()
|
||||
value = $(e.target).closest('.controls').find('[name=ticket_attribute_list]').val()
|
||||
addShownAttribute( value, '' )
|
||||
)
|
||||
item.find('.ticket_attribute_list').prepend( list )
|
||||
|
||||
# list of shown attributes
|
||||
show = []
|
||||
if attribute.value
|
||||
for key, value of attribute.value
|
||||
addShownAttribute( key, value )
|
||||
|
||||
# ticket attribute selection
|
||||
else if attribute.tag is 'ticket_attribute_selection'
|
||||
|
||||
|
@ -948,92 +670,8 @@ class App.ControllerForm extends App.Controller
|
|||
for key, value of attribute.value
|
||||
addShownAttribute( key, value )
|
||||
|
||||
# timeplan
|
||||
else if attribute.tag is 'timeplan'
|
||||
item = $( App.view('generic/timeplan')( attribute: attribute ) )
|
||||
attribute_config = {
|
||||
name: "#{attribute.name}::days"
|
||||
tag: 'select'
|
||||
multiple: true
|
||||
null: false
|
||||
options: [
|
||||
{
|
||||
value: 'mon'
|
||||
name: 'Monday'
|
||||
selected: false
|
||||
disable: false
|
||||
},
|
||||
{
|
||||
value: 'tue'
|
||||
name: 'Tuesday'
|
||||
selected: false
|
||||
disable: false
|
||||
},
|
||||
{
|
||||
value: 'wed'
|
||||
name: 'Wednesday'
|
||||
selected: false
|
||||
disable: false
|
||||
},
|
||||
{
|
||||
value: 'thu'
|
||||
name: 'Thursday'
|
||||
selected: false
|
||||
disable: false
|
||||
},
|
||||
{
|
||||
value: 'fri'
|
||||
name: 'Friday'
|
||||
selected: false
|
||||
disable: false
|
||||
},
|
||||
{
|
||||
value: 'sat'
|
||||
name: 'Saturday'
|
||||
selected: false
|
||||
disable: false
|
||||
},
|
||||
{
|
||||
value: 'sun'
|
||||
name: 'Sunday'
|
||||
selected: false
|
||||
disable: false
|
||||
},
|
||||
]
|
||||
default: attribute.default?.days
|
||||
}
|
||||
item.find('.days').append( @formGenItem( attribute_config ) )
|
||||
|
||||
hours = {}
|
||||
for hour in [0..23]
|
||||
localHour = "0#{hour}"
|
||||
hours[hour] = localHour.substr(localHour.length-2,2)
|
||||
attribute_config = {
|
||||
name: "#{attribute.name}::hours"
|
||||
tag: 'select'
|
||||
multiple: true
|
||||
null: false
|
||||
options: hours
|
||||
default: attribute.default?.hours
|
||||
}
|
||||
item.find('.hours').append( @formGenItem( attribute_config ) )
|
||||
|
||||
minutes = {}
|
||||
for minute in [0..5]
|
||||
minutes["#{minute}0"] = "#{minute}0"
|
||||
attribute_config = {
|
||||
name: "#{attribute.name}::minutes"
|
||||
tag: 'select'
|
||||
multiple: true
|
||||
null: false
|
||||
options: minutes
|
||||
default: attribute.default?.miuntes
|
||||
}
|
||||
item.find('.minutes').append( @formGenItem( attribute_config ) )
|
||||
|
||||
# input
|
||||
else
|
||||
item = $( App.view('generic/input')( attribute: attribute ) )
|
||||
throw "Invalid UiElement.#{attribute.tag}"
|
||||
|
||||
if @handlers
|
||||
item.bind('change', (e) =>
|
||||
|
@ -1193,9 +831,9 @@ class App.ControllerForm extends App.Controller
|
|||
continue
|
||||
|
||||
# collect all params, push it to an array if already exists
|
||||
if param[key.name]
|
||||
if param[key.name] isnt undefined
|
||||
if typeof param[key.name] is 'string'
|
||||
param[key.name] = [ param[key.name], key.value]
|
||||
param[key.name] = [param[key.name], key.value]
|
||||
else
|
||||
param[key.name].push key.value
|
||||
else
|
||||
|
@ -1293,15 +931,13 @@ class App.ControllerForm extends App.Controller
|
|||
inputSelectObject = {}
|
||||
for key of param
|
||||
parts = key.split '::'
|
||||
if parts[0] && parts[1] && !parts[2]
|
||||
if !inputSelectObject[ parts[0] ]
|
||||
if parts[0] && parts[1]
|
||||
if !(parts[0] of inputSelectObject)
|
||||
inputSelectObject[ parts[0] ] = {}
|
||||
if !parts[2]
|
||||
inputSelectObject[ parts[0] ][ parts[1] ] = param[ key ]
|
||||
delete param[ key ]
|
||||
if parts[0] && parts[1] && parts[2]
|
||||
if !inputSelectObject[ parts[0] ]
|
||||
inputSelectObject[ parts[0] ] = {}
|
||||
if !inputSelectObject[ parts[0] ][ parts[1] ]
|
||||
else
|
||||
if !(parts[1] of inputSelectObject[ parts[0] ])
|
||||
inputSelectObject[ parts[0] ][ parts[1] ] = {}
|
||||
inputSelectObject[ parts[0] ][ parts[1] ][ parts[2] ] = param[ key ]
|
||||
delete param[ key ]
|
||||
|
@ -1311,7 +947,7 @@ class App.ControllerForm extends App.Controller
|
|||
param[ key ] = inputSelectObject[ key ]
|
||||
|
||||
#App.Log.notice 'ControllerForm', 'formParam', form, param
|
||||
return param
|
||||
param
|
||||
|
||||
@formId: ->
|
||||
formId = new Date().getTime() + Math.floor( Math.random() * 99999 )
|
||||
|
|
|
@ -196,6 +196,7 @@ class App.ControllerGenericIndex extends App.Controller
|
|||
pageData: @pageData
|
||||
genericObject: @genericObject
|
||||
container: @container
|
||||
large: @large
|
||||
)
|
||||
|
||||
new: (e) ->
|
||||
|
@ -204,6 +205,7 @@ class App.ControllerGenericIndex extends App.Controller
|
|||
pageData: @pageData
|
||||
genericObject: @genericObject
|
||||
container: @container
|
||||
large: @large
|
||||
)
|
||||
|
||||
description: (e) =>
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
class App.UiElement.autocompletion_ajax
|
||||
@render: (attribute, params = {}) ->
|
||||
if params[attribute.name]
|
||||
object = App[attribute.relation].find(params[attribute.name])
|
||||
valueName = object.displayName()
|
||||
|
||||
# selectable search
|
||||
searchableAjaxSelectObject = new App.SearchableAjaxSelect(
|
||||
attribute:
|
||||
value: params[attribute.name]
|
||||
valueName: valueName
|
||||
name: attribute.name
|
||||
id: params.organization_id
|
||||
placeholder: App.i18n.translateInline('Search...')
|
||||
limt: 10
|
||||
object: attribute.relation
|
||||
)
|
||||
searchableAjaxSelectObject.element()
|
|
@ -0,0 +1,3 @@
|
|||
class App.UiElement.input
|
||||
@render: (attribute) ->
|
||||
$( App.view('generic/input')( attribute: attribute ) )
|
|
@ -1,5 +1,5 @@
|
|||
class App.UiElement.select extends App.UiElement.ApplicationUiElement
|
||||
@render: (attribute, params, form_controller) ->
|
||||
@render: (attribute, params) ->
|
||||
|
||||
# set multiple option
|
||||
if attribute.multiple
|
||||
|
|
|
@ -1,4 +1,46 @@
|
|||
class App.UiElement.sla_times
|
||||
@render: (attribute) ->
|
||||
@render: (attribute, params = {}) ->
|
||||
|
||||
$( App.view('generic/sla_times')( attribute: attribute ) )
|
||||
item = $( App.view('generic/sla_times')(
|
||||
attribute: attribute
|
||||
first_response_time: params.first_response_time
|
||||
update_time: params.update_time
|
||||
close_time: params.close_time
|
||||
first_response_time_in_text: @toText(params.first_response_time)
|
||||
update_time_in_text: @toText(params.update_time)
|
||||
close_time_in_text: @toText(params.close_time)
|
||||
) )
|
||||
|
||||
item.find('.js-timeConvertFrom').bind('keyup', (e) =>
|
||||
inText = $(e.target).val()
|
||||
inMinutes = @toMinutes(inText)
|
||||
if !inMinutes
|
||||
$(e.target).addClass('has-error')
|
||||
else
|
||||
$(e.target).removeClass('has-error')
|
||||
dest = $(e.target).closest('td').find('.js-timeConvertTo')
|
||||
dest.val(inMinutes)
|
||||
)
|
||||
|
||||
item
|
||||
|
||||
@toMinutes: (hh) ->
|
||||
hh = hh.split(':')
|
||||
hour = parseInt(hh[0])
|
||||
minute = parseInt(hh[1])
|
||||
return if hour is NaN
|
||||
return if minute is NaN
|
||||
(hour * 60) + minute
|
||||
|
||||
@toText: (m) ->
|
||||
m = parseInt(m)
|
||||
return if !m
|
||||
minutes = m % 60
|
||||
hours = Math.floor(m / 60)
|
||||
|
||||
if minutes < 10
|
||||
minutes = "0#{minutes}"
|
||||
if hours < 10
|
||||
hours = "0#{hours}"
|
||||
|
||||
"#{hours}:#{minutes}"
|
||||
|
|
|
@ -0,0 +1,267 @@
|
|||
class App.UiElement.ticket_selector extends App.UiElement.ApplicationUiElement
|
||||
@render: (attribute, params = {}) ->
|
||||
|
||||
# list of attributes
|
||||
groups =
|
||||
tickets:
|
||||
name: 'Ticket'
|
||||
model: 'Ticket'
|
||||
users:
|
||||
name: 'Customer'
|
||||
model: 'User'
|
||||
organizations:
|
||||
name: 'Organization'
|
||||
model: 'Organization'
|
||||
|
||||
elements =
|
||||
tickets:
|
||||
title:
|
||||
tag: 'input'
|
||||
operator: ['contains', 'contains not']
|
||||
number:
|
||||
tag: 'input'
|
||||
operator: ['contains', 'contains not']
|
||||
group_id:
|
||||
relation: 'Group'
|
||||
tag: 'select'
|
||||
multible: true
|
||||
operator: ['is', 'is not']
|
||||
priority_id:
|
||||
relation: 'Priority'
|
||||
tag: 'select'
|
||||
multible: true
|
||||
operator: ['is', 'is not']
|
||||
state_id:
|
||||
relation: 'State'
|
||||
tag: 'select'
|
||||
multible: true
|
||||
operator: ['is', 'is not']
|
||||
owner_id:
|
||||
tag: 'user_selection'
|
||||
relation: 'User'
|
||||
operator: ['is', 'is not']
|
||||
customer_id:
|
||||
tag: 'user_selection'
|
||||
relation: 'User'
|
||||
operator: ['is', 'is not']
|
||||
organization_id:
|
||||
tag: ''
|
||||
relation: 'Organization'
|
||||
operator: ['is', 'is not']
|
||||
tag:
|
||||
tag: 'tag'
|
||||
multible: true
|
||||
operator: ['is', 'is not']
|
||||
created_at:
|
||||
tag: 'timestamp'
|
||||
operator: ['before', 'after']
|
||||
updated_at:
|
||||
tag: 'timestamp'
|
||||
operator: ['before', 'after']
|
||||
escalation_time:
|
||||
tag: 'timestamp'
|
||||
operator: ['before', 'after']
|
||||
users:
|
||||
firstname:
|
||||
tag: 'input'
|
||||
operator: ['contains', 'contains not']
|
||||
lastname:
|
||||
tag: 'input'
|
||||
operator: ['contains', 'contains not']
|
||||
email:
|
||||
tag: 'input'
|
||||
operator: ['contains', 'contains not']
|
||||
login:
|
||||
tag: 'input'
|
||||
operator: ['contains', 'contains not']
|
||||
created_at:
|
||||
tag: 'time_selector_enhanced'
|
||||
operator: ['before', 'after']
|
||||
updated_at:
|
||||
tag: 'time_selector_enhanced'
|
||||
operator: ['before', 'after']
|
||||
organizations:
|
||||
name:
|
||||
tag: 'input'
|
||||
operator: ['contains', 'contains not']
|
||||
shared:
|
||||
tag: 'boolean'
|
||||
operator: ['is', 'is not']
|
||||
created_at:
|
||||
tag: 'time_selector_enhanced'
|
||||
operator: ['before', 'after']
|
||||
updated_at:
|
||||
tag: 'time_selector_enhanced'
|
||||
operator: ['before', 'after']
|
||||
|
||||
# megre config
|
||||
for groupKey, groupMeta of groups
|
||||
for elementKey, elementGroup of elements
|
||||
if elementKey is groupKey
|
||||
configure_attributes = App[groupMeta.model].configure_attributes
|
||||
for attributeName, attributeConfig of elementGroup
|
||||
for attribute in configure_attributes
|
||||
if attribute.name is attributeName
|
||||
attributeConfig.config = attribute
|
||||
|
||||
selector = @buildAttributeSelector(groups, elements)
|
||||
|
||||
# return item
|
||||
item = $( App.view('generic/ticket_selector')( attribute: attribute ) )
|
||||
item.find('.js-attributeSelector').prepend(selector)
|
||||
|
||||
# add filter
|
||||
item.find('.js-add').bind('click', (e) =>
|
||||
element = $(e.target).closest('.js-filterElement')
|
||||
elementClone = element.clone(true)
|
||||
element.after(elementClone)
|
||||
elementClone.find('.js-attributeSelector select').trigger('change')
|
||||
)
|
||||
|
||||
# remove filter
|
||||
item.find('.js-remove').bind('click', (e) =>
|
||||
$(e.target).closest('.js-filterElement').remove()
|
||||
@rebuildAttributeSelectors(item)
|
||||
)
|
||||
|
||||
# change filter
|
||||
item.find('.js-attributeSelector select').bind('change', (e) =>
|
||||
groupAndAttribute = $(e.target).find('option:selected').attr('value')
|
||||
elementRow = $(e.target).closest('.js-filterElement')
|
||||
|
||||
console.log('CHANGE', groupAndAttribute, $(e.target))
|
||||
|
||||
@rebuildAttributeSelectors(item, elementRow, groupAndAttribute)
|
||||
@rebuildOperater(item, elementRow, groupAndAttribute, elements)
|
||||
@buildValue(item, elementRow, groupAndAttribute, elements)
|
||||
)
|
||||
|
||||
# build inital params
|
||||
console.log('P', params)
|
||||
if !_.isEmpty(params.condition)
|
||||
selectorExists = false
|
||||
for position of params.condition.attribute
|
||||
|
||||
# get stored params
|
||||
groupAndAttribute = params.condition.attribute[position]
|
||||
if params.condition[groupAndAttribute]
|
||||
selectorExists = true
|
||||
operator = params.condition[groupAndAttribute].operator
|
||||
value = params.condition[groupAndAttribute].value
|
||||
|
||||
# get selector rows
|
||||
elementFirst = item.find('.js-filterElement').first()
|
||||
elementLast = item.find('.js-filterElement').last()
|
||||
|
||||
# clone, rebuild and append
|
||||
elementClone = elementFirst.clone(true)
|
||||
@rebuildAttributeSelectors(item, elementClone, groupAndAttribute)
|
||||
@rebuildOperater(item, elementClone, groupAndAttribute, elements, operator)
|
||||
@buildValue(item, elementClone, groupAndAttribute, elements, value)
|
||||
elementLast.after(elementClone)
|
||||
|
||||
# remove first dummy row
|
||||
if selectorExists
|
||||
item.find('.js-filterElement').first().remove()
|
||||
item
|
||||
|
||||
@getElementConfig: (groupAndAttribute, elements) ->
|
||||
for elementGroup, elementConfig of elements
|
||||
for elementKey, elementItem of elementConfig
|
||||
if "#{elementGroup}.#{elementKey}" is groupAndAttribute
|
||||
return elementItem
|
||||
false
|
||||
|
||||
@buildValue: (elementFull, elementRow, groupAndAttribute, elements, value) ->
|
||||
|
||||
# do nothing if item already exists
|
||||
name = "condition::#{groupAndAttribute}::value"
|
||||
return if elementRow.find("[name=\"#{name}\"]").get(0)
|
||||
|
||||
# build new item
|
||||
attributeConfig = @getElementConfig(groupAndAttribute, elements)
|
||||
item = ''
|
||||
if attributeConfig && attributeConfig.config && App.UiElement[attributeConfig.config.tag]
|
||||
config = _.clone(attributeConfig.config)
|
||||
config['name'] = name
|
||||
config['value'] = value
|
||||
if 'multiple' of config
|
||||
config.multiple = true
|
||||
config.nulloption = false
|
||||
item = App.UiElement[attributeConfig.config.tag].render(config, {})
|
||||
elementRow.find('.js-value').html(item)
|
||||
|
||||
@buildAttributeSelector: (groups, elements) ->
|
||||
selection = $('<input type="hidden" name="condition::attribute"><select class="form-control"></select>')
|
||||
for groupKey, groupMeta of groups
|
||||
displayName = App.i18n.translateInline(groupMeta.name)
|
||||
selection.closest('select').append("<optgroup label=\"#{displayName}\" class=\"js-#{groupKey}\"></optgroup>")
|
||||
optgroup = selection.find("optgroup.js-#{groupKey}")
|
||||
for elementKey, elementGroup of elements
|
||||
if elementKey is groupKey
|
||||
for attributeName, attributeConfig of elementGroup
|
||||
if attributeConfig.config && attributeConfig.config.display
|
||||
displayName = App.i18n.translateInline(attributeConfig.config.display)
|
||||
else
|
||||
displayName = App.i18n.translateInline(attributeName)
|
||||
optgroup.append("<option value=\"#{groupKey}.#{attributeName}\">#{displayName}</option>")
|
||||
selection
|
||||
|
||||
@rebuildAttributeSelectors: (elementFull, elementRow, groupAndAttribute) ->
|
||||
|
||||
# enable all
|
||||
elementFull.find('.js-attributeSelector select option').removeAttr('disabled')
|
||||
|
||||
# disable all used attributes
|
||||
elementFull.find('.js-attributeSelector select').each(->
|
||||
keyLocal = $(@).val()
|
||||
elementFull.find('.js-attributeSelector select option[value="' + keyLocal + '"]').attr('disabled', true)
|
||||
elementFull.find('.js-hiddenAttribute').val(keyLocal)
|
||||
)
|
||||
|
||||
# disable - if we only have one attribute
|
||||
if elementFull.find('.js-attributeSelector select').length > 1
|
||||
elementFull.find('.js-remove').removeClass('is-disabled')
|
||||
else
|
||||
elementFull.find('.js-remove').addClass('is-disabled')
|
||||
|
||||
# set attribute
|
||||
if groupAndAttribute
|
||||
elementRow.find('.js-attributeSelector select').val(groupAndAttribute)
|
||||
elementRow.find('[name="condition::attribute"]').val("#{groupAndAttribute}")
|
||||
|
||||
@buildOperator: (elementFull, elementRow, groupAndAttribute, elements, current_operator) ->
|
||||
selection = $("<select class=\"form-control\" name=\"condition::#{groupAndAttribute}::operator\"></select>")
|
||||
attributeConfig = @getElementConfig(groupAndAttribute, elements)
|
||||
for operator in attributeConfig.operator
|
||||
operatorName = App.i18n.translateInline(operator)
|
||||
selected = ''
|
||||
if current_operator is operator
|
||||
selected = 'selected="selected"'
|
||||
selection.append("<option value=\"#{operator}\" #{selected}>#{operatorName}</option>")
|
||||
selection
|
||||
|
||||
@rebuildOperater: (elementFull, elementRow, groupAndAttribute, elements, current_operator) ->
|
||||
return if !groupAndAttribute
|
||||
|
||||
# do nothing if item already exists
|
||||
name = "condition::#{groupAndAttribute}::operator"
|
||||
return if elementRow.find("[name=\"#{name}\"]").get(0)
|
||||
|
||||
# render new operator
|
||||
operator = @buildOperator(elementFull, elementRow, groupAndAttribute, elements, current_operator)
|
||||
elementRow.find('.js-operator select').replaceWith(operator)
|
||||
|
||||
@humanText: (condition) ->
|
||||
return [] if _.isEmpty(condition)
|
||||
rules = []
|
||||
for position of condition.attribute
|
||||
|
||||
# get stored params
|
||||
groupAndAttribute = condition.attribute[position]
|
||||
if condition[groupAndAttribute]
|
||||
selectorExists = true
|
||||
operator = condition[groupAndAttribute].operator
|
||||
value = condition[groupAndAttribute].value
|
||||
rules.push "Where <b>#{groupAndAttribute}</b> #{operator} <b>#{value}</b>."
|
||||
rules
|
|
@ -1,27 +1,119 @@
|
|||
class Index extends App.ControllerContent
|
||||
events:
|
||||
'click .js-new': 'new'
|
||||
'click .js-edit': 'edit'
|
||||
'click .js-delete': 'delete'
|
||||
'click .js-description': 'description'
|
||||
|
||||
constructor: ->
|
||||
super
|
||||
|
||||
# check authentication
|
||||
return if !@authenticate()
|
||||
|
||||
new App.ControllerGenericIndex(
|
||||
el: @el
|
||||
id: @id
|
||||
genericObject: 'Sla'
|
||||
@load()
|
||||
#@subscribeId = App.Calendar.subscribe(@render)
|
||||
|
||||
load: =>
|
||||
@ajax(
|
||||
id: 'sla_index'
|
||||
type: 'GET'
|
||||
url: @apiPath + '/slas'
|
||||
processData: true
|
||||
success: (data, status, xhr) =>
|
||||
|
||||
# load assets
|
||||
App.Collection.loadAssets(data.assets)
|
||||
|
||||
@render(data)
|
||||
)
|
||||
|
||||
render: =>
|
||||
slas = App.Sla.search(
|
||||
sortBy: 'name'
|
||||
)
|
||||
for sla in slas
|
||||
if sla.first_response_time
|
||||
sla.first_response_time_in_text = @toText(sla.first_response_time)
|
||||
if sla.update_time
|
||||
sla.update_time_in_text = @toText(sla.update_time)
|
||||
if sla.solution_time
|
||||
sla.solution_time_in_text = @toText(sla.solution_time)
|
||||
sla.rules = App.UiElement.ticket_selector.humanText(sla.condition)
|
||||
|
||||
# show description button, only if content exists
|
||||
showDescription = false
|
||||
if App.Sla.description
|
||||
if !_.isEmpty(slas)
|
||||
showDescription = true
|
||||
else
|
||||
description = marked(App.Sla.description)
|
||||
|
||||
@html App.view('sla/index')(
|
||||
slas: slas
|
||||
showDescription: showDescription
|
||||
description: description
|
||||
)
|
||||
|
||||
release: =>
|
||||
if @subscribeId
|
||||
App.Calendar.unsubscribe(@subscribeId)
|
||||
|
||||
new: (e) ->
|
||||
e.preventDefault()
|
||||
new App.ControllerGenericNew(
|
||||
pageData:
|
||||
title: 'SLA'
|
||||
home: 'slas'
|
||||
object: 'SLA'
|
||||
title: 'SLAs'
|
||||
object: 'Sla'
|
||||
objects: 'SLAs'
|
||||
navupdate: '#slas'
|
||||
notes: [
|
||||
# 'SLA are ...'
|
||||
]
|
||||
buttons: [
|
||||
{ name: 'New SLA', 'data-type': 'new', class: 'btn--success' }
|
||||
]
|
||||
genericObject: 'Sla'
|
||||
container: @el.closest('.content')
|
||||
callback: @load
|
||||
large: true
|
||||
)
|
||||
|
||||
edit: (e) ->
|
||||
e.preventDefault()
|
||||
id = $(e.target).closest('.action').data('id')
|
||||
new App.ControllerGenericEdit(
|
||||
id: id
|
||||
pageData:
|
||||
title: 'SLAs'
|
||||
object: 'Sla'
|
||||
objects: 'SLAs'
|
||||
genericObject: 'Sla'
|
||||
callback: @load
|
||||
container: @el.closest('.content')
|
||||
large: true
|
||||
)
|
||||
|
||||
delete: (e) =>
|
||||
e.preventDefault()
|
||||
id = $(e.target).closest('.action').data('id')
|
||||
item = App.Sla.find(id)
|
||||
new App.ControllerGenericDestroyConfirm(
|
||||
item: item
|
||||
container: @el.closest('.content')
|
||||
callback: @load
|
||||
)
|
||||
|
||||
description: (e) =>
|
||||
new App.ControllerGenericDescription(
|
||||
description: App.Calendar.description
|
||||
container: @el.closest('.content')
|
||||
)
|
||||
|
||||
toText: (m) ->
|
||||
m = parseInt(m)
|
||||
return if !m
|
||||
minutes = m % 60
|
||||
hours = Math.floor(m / 60)
|
||||
|
||||
if minutes < 10
|
||||
minutes = "0#{minutes}"
|
||||
if hours < 10
|
||||
hours = "0#{hours}"
|
||||
|
||||
"#{hours}:#{minutes}"
|
||||
|
||||
App.Config.set( 'Sla', { prio: 2900, name: 'SLAs', parent: '#manage', target: '#manage/slas', controller: Index, role: ['Admin'] }, 'NavBarAdmin' )
|
|
@ -1,8 +1,8 @@
|
|||
class Index extends App.Controller
|
||||
elements:
|
||||
'.js-search' : 'searchInput'
|
||||
'.js-search': 'searchInput'
|
||||
events:
|
||||
'click [data-type="new"]': 'new'
|
||||
'click [data-type=new]': 'new'
|
||||
|
||||
constructor: ->
|
||||
super
|
||||
|
|
|
@ -6,8 +6,9 @@ class App.Organization extends App.Model
|
|||
{ 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: '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', tag: 'datetime', readonly: 1, info: false },
|
||||
{ name: 'active', display: 'Active', tag: 'active', default: true, info: false },
|
||||
{ name: 'updated_at', display: 'Updated', tag: 'datetime', readonly: 1, info: false },
|
||||
{ name: 'created_at', display: 'Created', tag: 'datetime', readonly: 1, info: false },
|
||||
]
|
||||
@configure_overview = [
|
||||
'name',
|
||||
|
|
|
@ -4,11 +4,9 @@ class App.Sla extends App.Model
|
|||
@url: @apiPath + '/slas'
|
||||
@configure_attributes = [
|
||||
{ name: 'name', display: 'Name', tag: 'input', type: 'text', limit: 100, null: false },
|
||||
{ name: 'first_response_time', display: 'First Response Time', tag: 'input', type: 'text', limit: 100, null: true, note: 'In minutes, only business times are counted.' },
|
||||
{ name: 'update_time', display: 'Update Time', tag: 'input', type: 'text', limit: 100, null: true, note: 'In minutes, only business times are counted.' },
|
||||
{ name: 'close_time', display: 'Solution Time', tag: 'input', type: 'text', limit: 100, null: true, note: 'In minutes, only business times are counted.' },
|
||||
{ name: 'condition', display: 'Selector', tag: 'ticket_selector', null: false, note: 'Create rules that single out the tickets for the Service Level Agreement.' },
|
||||
{ name: 'calendar_id', display: 'Calendar', tag: 'select', relation: 'Calendar', null: false },
|
||||
{ name: 'condition', display: 'Conditions where SLA is used', tag: 'ticket_attribute_selection', null: true },
|
||||
{ name: 'sla_times', display: 'SLA Times', tag: 'sla_times', null: true },
|
||||
{ name: 'created_by_id', display: 'Created by', relation: 'User', readonly: 1 },
|
||||
{ name: 'created_at', display: 'Created', tag: 'datetime', readonly: 1 },
|
||||
{ name: 'updated_by_id', display: 'Updated by', relation: 'User', readonly: 1 },
|
||||
|
|
|
@ -5,7 +5,7 @@ class App.Ticket extends App.Model
|
|||
@configure_attributes = [
|
||||
{ name: 'number', display: '#', tag: 'input', type: 'text', limit: 100, null: true, read_only: true, style: 'width: 60px' },
|
||||
{ name: 'customer_id', display: 'Customer', tag: 'input', type: 'text', limit: 100, null: false, autocapitalize: false, relation: 'User' },
|
||||
{ name: 'organization_id', display: 'Organization', relation: 'Organization', tagreadonly: 1 },
|
||||
{ name: 'organization_id', display: 'Organization', tag: 'select', relation: 'Organization', tagreadonly: 1 },
|
||||
{ name: 'group_id', display: 'Group', tag: 'select', multiple: false, limit: 100, null: false, relation: 'Group', style: 'width: 10%', edit: true },
|
||||
{ name: 'owner_id', display: 'Owner', tag: 'select', multiple: false, limit: 100, null: true, relation: 'User', style: 'width: 12%', edit: true },
|
||||
{ name: 'title', display: 'Title', tag: 'input', type: 'text', limit: 100, null: false, parentClass: 'noTruncate' },
|
||||
|
|
|
@ -23,6 +23,7 @@ class App.User extends App.Model
|
|||
{ 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', invite_agent: true },
|
||||
{ name: 'active', display: 'Active', tag: 'active', default: true },
|
||||
{ name: 'created_at', display: 'Created', tag: 'datetime', readonly: 1 },
|
||||
{ name: 'updated_at', display: 'Updated', tag: 'datetime', readonly: 1 },
|
||||
]
|
||||
@configure_overview = [
|
||||
|
|
|
@ -26,25 +26,25 @@
|
|||
<%- @T('Business Hours') %>:<br>
|
||||
<table class="table table-fluid">
|
||||
<tr>
|
||||
<td><%- @T('Monday') %></td><td><% if _.isEmpty(calendar.business_hours['mon']): %>-<% else: %><% for from, till of calendar.business_hours['mon']: %><%= from %>:<%= till %> </td><td><% end %><% end %></td>
|
||||
<td><%- @T('Monday') %></td><td><% if _.isEmpty(calendar.business_hours['mon']): %>-<% else: %><% for from, till of calendar.business_hours['mon']: %><%= from %>-<%= till %> </td><td><% end %><% end %></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><%- @T('Tuesday') %></td><td><% if _.isEmpty(calendar.business_hours['tue']): %>-<% else: %><% for from, till of calendar.business_hours['tue']: %><%= from %>:<%= till %> </td><td><% end %><% end %></td>
|
||||
<td><%- @T('Tuesday') %></td><td><% if _.isEmpty(calendar.business_hours['tue']): %>-<% else: %><% for from, till of calendar.business_hours['tue']: %><%= from %>-<%= till %> </td><td><% end %><% end %></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><%- @T('Wednesday') %></td><td><% if _.isEmpty(calendar.business_hours['wed']): %>-<% else: %><% for from, till of calendar.business_hours['wed']: %><%= from %>:<%= till %> </td><td><% end %><% end %></td>
|
||||
<td><%- @T('Wednesday') %></td><td><% if _.isEmpty(calendar.business_hours['wed']): %>-<% else: %><% for from, till of calendar.business_hours['wed']: %><%= from %>-<%= till %> </td><td><% end %><% end %></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><%- @T('Thursday') %></td><td><% if _.isEmpty(calendar.business_hours['thu']): %>-<% else: %><% for from, till of calendar.business_hours['thu']: %><%= from %>:<%= till %> </td><td><% end %><% end %></td>
|
||||
<td><%- @T('Thursday') %></td><td><% if _.isEmpty(calendar.business_hours['thu']): %>-<% else: %><% for from, till of calendar.business_hours['thu']: %><%= from %>-<%= till %> </td><td><% end %><% end %></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><%- @T('Friday') %></td><td><% if _.isEmpty(calendar.business_hours['fri']): %>-<% else: %><% for from, till of calendar.business_hours['fri']: %><%= from %>:<%= till %> </td><td><% end %><% end %></td>
|
||||
<td><%- @T('Friday') %></td><td><% if _.isEmpty(calendar.business_hours['fri']): %>-<% else: %><% for from, till of calendar.business_hours['fri']: %><%= from %>-<%= till %> </td><td><% end %><% end %></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><%- @T('Saturday') %></td><td><% if _.isEmpty(calendar.business_hours['sat']): %>-<% else: %><% for from, till of calendar.business_hours['sat']: %><%= from %>:<%= till %> </td><td><% end %><% end %></td>
|
||||
<td><%- @T('Saturday') %></td><td><% if _.isEmpty(calendar.business_hours['sat']): %>-<% else: %><% for from, till of calendar.business_hours['sat']: %><%= from %>-<%= till %> </td><td><% end %><% end %></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><%- @T('Sunday') %></td><td><% if _.isEmpty(calendar.business_hours['sun']): %>-<% else: %><% for from, till of calendar.business_hours['sun']: %><%= from %>:<%= till %> </td><td><% end %><% end %></td>
|
||||
<td><%- @T('Sunday') %></td><td><% if _.isEmpty(calendar.business_hours['sun']): %>-<% else: %><% for from, till of calendar.business_hours['sun']: %><%= from %>-<%= till %> </td><td><% end %><% end %></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
|
|
@ -48,7 +48,28 @@ curl http://localhost/api/v1/slas.json -v -u #{login}:#{password}
|
|||
|
||||
def index
|
||||
return if deny_if_not_role(Z_ROLENAME_ADMIN)
|
||||
model_index_render(Sla, params)
|
||||
|
||||
assets = {}
|
||||
|
||||
# calendars
|
||||
calendar_ids = []
|
||||
Calendar.all.each {|calendar|
|
||||
calendar_ids.push calendar.id
|
||||
assets = calendar.assets(assets)
|
||||
}
|
||||
|
||||
# slas
|
||||
sla_ids = []
|
||||
Sla.all.each {|sla|
|
||||
sla_ids.push sla.id
|
||||
assets = sla.assets(assets)
|
||||
}
|
||||
|
||||
render json: {
|
||||
calendar_ids: calendar_ids,
|
||||
sla_ids: sla_ids,
|
||||
assets: assets,
|
||||
}, status: :ok
|
||||
end
|
||||
|
||||
=begin
|
||||
|
|
|
@ -45,7 +45,7 @@ class ManageTest < TestCase
|
|||
sla_create(
|
||||
data: {
|
||||
name: 'some sla' + random,
|
||||
first_response_time: 61
|
||||
first_response_time_in_text: '1:01'
|
||||
}
|
||||
)
|
||||
watch_for(
|
||||
|
@ -54,7 +54,7 @@ class ManageTest < TestCase
|
|||
)
|
||||
sleep 1
|
||||
|
||||
click( css: '.table-overview tr:last-child td' )
|
||||
click( css: '.content:not(.hide) .action:last-child .js-edit' )
|
||||
sleep 1
|
||||
|
||||
set(
|
||||
|
@ -62,8 +62,8 @@ class ManageTest < TestCase
|
|||
value: 'some sla update ' + random,
|
||||
)
|
||||
set(
|
||||
css: '.modal input[name="first_response_time"]',
|
||||
value: 121,
|
||||
css: '.modal input[name="first_response_time_in_text"]',
|
||||
value: '2:01',
|
||||
)
|
||||
click( css: '.modal button.js-submit' )
|
||||
|
||||
|
@ -73,7 +73,7 @@ class ManageTest < TestCase
|
|||
)
|
||||
sleep 4
|
||||
|
||||
click( css: '[data-type="destroy"]:last-child' )
|
||||
click( css: '.content:not(.hide) .action:last-child .js-delete' )
|
||||
sleep 2
|
||||
|
||||
click( css: '.modal button.js-submit' )
|
||||
|
|
|
@ -1577,7 +1577,7 @@ wait untill text in selector disabppears
|
|||
:browser => browser2,
|
||||
:data => {
|
||||
:name => 'some sla' + random,
|
||||
:first_response_time => 61
|
||||
:first_response_time_in_text => 61
|
||||
},
|
||||
)
|
||||
|
||||
|
@ -1592,14 +1592,14 @@ wait untill text in selector disabppears
|
|||
instance.find_elements( { css: 'a[href="#manage"]' } )[0].click
|
||||
instance.find_elements( { css: 'a[href="#manage/slas"]' } )[0].click
|
||||
sleep 2
|
||||
instance.find_elements( { css: 'a[data-type="new"]' } )[0].click
|
||||
instance.find_elements( { css: 'a.js-new' } )[0].click
|
||||
sleep 2
|
||||
element = instance.find_elements( { css: '.modal input[name=name]' } )[0]
|
||||
element.clear
|
||||
element.send_keys( data[:name] )
|
||||
element = instance.find_elements( { css: '.modal input[name=first_response_time]' } )[0]
|
||||
element = instance.find_elements( { css: '.modal input[name=first_response_time_in_text]' } )[0]
|
||||
element.clear
|
||||
element.send_keys( data[:first_response_time] )
|
||||
element.send_keys( data[:first_response_time_in_text] )
|
||||
instance.find_elements( { css: '.modal button.js-submit' } )[0].click
|
||||
(1..8).each {
|
||||
element = instance.find_elements( { css: 'body' } )[0]
|
||||
|
|
Loading…
Reference in a new issue