Init sla management feature.

This commit is contained in:
Martin Edenhofer 2015-09-15 15:11:00 +02:00
parent 6aac51b633
commit c21f1ce93f
17 changed files with 516 additions and 435 deletions

View file

@ -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,7 +831,7 @@ 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]
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 )

View file

@ -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) =>

View file

@ -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()

View file

@ -0,0 +1,3 @@
class App.UiElement.input
@render: (attribute) ->
$( App.view('generic/input')( attribute: attribute ) )

View file

@ -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

View file

@ -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}"

View file

@ -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

View file

@ -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' )

View file

@ -2,7 +2,7 @@ class Index extends App.Controller
elements:
'.js-search': 'searchInput'
events:
'click [data-type="new"]': 'new'
'click [data-type=new]': 'new'
constructor: ->
super

View file

@ -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',

View file

@ -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 },

View file

@ -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' },

View file

@ -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 = [

View file

@ -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>

View file

@ -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

View file

@ -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' )

View file

@ -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]