Fixes #3900 - Be able to select more than one owner or organization in condition for overviews/triggers/schedulers like you can do it for state, priority or group
This commit is contained in:
parent
50e3b98955
commit
bf3067d908
11 changed files with 746 additions and 91 deletions
|
@ -3,7 +3,7 @@ class App.UiElement.autocompletion_ajax
|
|||
@render: (attribute, params = {}, form) ->
|
||||
if params[attribute.name] || attribute.value
|
||||
object = App[attribute.relation].find(params[attribute.name] || attribute.value)
|
||||
valueName = object.displayName()
|
||||
valueName = object.displayName() if object
|
||||
|
||||
# selectable search
|
||||
searchableAjaxSelectObject = new App.SearchableAjaxSelect(
|
||||
|
@ -17,5 +17,6 @@ class App.UiElement.autocompletion_ajax
|
|||
limit: 40
|
||||
object: attribute.relation
|
||||
ajax: true
|
||||
multiple: attribute.multiple
|
||||
)
|
||||
searchableAjaxSelectObject.element()
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
# coffeelint: disable=camel_case_classes
|
||||
class App.UiElement.autocompletion_ajax_search extends App.UiElement.autocompletion_ajax
|
||||
@render: (attributeOrig, params = {}, form) ->
|
||||
attribute = _.clone(attributeOrig)
|
||||
attribute.multiple = true
|
||||
super(attribute, params = {}, form)
|
|
@ -3,4 +3,5 @@ class App.UiElement.user_autocompletion_search
|
|||
@render: (attributeOrig, params = {}) ->
|
||||
attribute = _.clone(attributeOrig)
|
||||
attribute.disableCreateObject = true
|
||||
attribute.multiple = true
|
||||
new App.UserOrganizationAutocompletion(attribute: attribute, params: params).element()
|
||||
|
|
|
@ -27,7 +27,7 @@ class Overview extends App.ControllerSubContent
|
|||
{ name: __('New Overview'), 'data-type': 'new', class: 'btn--success' }
|
||||
]
|
||||
container: @el.closest('.content')
|
||||
large: true
|
||||
veryLarge: true
|
||||
dndCallback: (e, item) =>
|
||||
items = @el.find('table > tbody > tr')
|
||||
prios = []
|
||||
|
|
|
@ -80,10 +80,9 @@ class App.ObjectOrganizationAutocompletion extends App.Controller
|
|||
|
||||
onBlur: =>
|
||||
selectObject = @objectSelect.val()
|
||||
if _.isEmpty(selectObject)
|
||||
if _.isEmpty(selectObject) && !@attribute.multiple
|
||||
@objectId.val('')
|
||||
return
|
||||
if @attribute.guess is true
|
||||
else if @attribute.guess is true
|
||||
currentObjectId = @objectId.val()
|
||||
if _.isEmpty(currentObjectId) || currentObjectId.match(/^guess:/)
|
||||
if !_.isEmpty(selectObject)
|
||||
|
@ -95,31 +94,28 @@ class App.ObjectOrganizationAutocompletion extends App.Controller
|
|||
|
||||
onObjectClick: (e) =>
|
||||
objectId = $(e.currentTarget).data('object-id')
|
||||
@selectObject(objectId)
|
||||
objectName = $(e.currentTarget).find('.recipientList-name').text().trim()
|
||||
@selectObject(objectId, objectName)
|
||||
@close()
|
||||
|
||||
selectObject: (objectId) =>
|
||||
if @attribute.multiple and @objectId.val()
|
||||
# add objectId to end of comma separated list
|
||||
objectId = _.chain( @objectId.val().split(',') ).push(objectId).join(',').value()
|
||||
|
||||
@objectSelect.val('')
|
||||
@objectId.val(objectId).trigger('change')
|
||||
selectObject: (objectId, objectName) =>
|
||||
if @attribute.multiple
|
||||
@addValueToObjectInput(objectName, objectId)
|
||||
else
|
||||
@objectSelect.val('')
|
||||
@objectId.val(objectId).trigger('change')
|
||||
|
||||
executeCallback: =>
|
||||
# with @attribute.multiple this can be several objects ids.
|
||||
# Only work with the last one since its the newest one
|
||||
objectId = @objectId.val().split(',').pop()
|
||||
if @attribute.multiple
|
||||
# create token
|
||||
@createToken(@currentObject) if @currentObject
|
||||
@currentObject = null
|
||||
|
||||
if objectId && App[@objectSingle].exists(objectId)
|
||||
object = App[@objectSingle].find(objectId)
|
||||
name = object.displayName()
|
||||
|
||||
if @attribute.multiple
|
||||
|
||||
# create token
|
||||
@createToken(name, objectId)
|
||||
else
|
||||
else
|
||||
objectId = @objectId.val()
|
||||
if objectId && App[@objectSingle].exists(objectId)
|
||||
object = App[@objectSingle].find(objectId)
|
||||
name = object.displayName()
|
||||
if object.email
|
||||
|
||||
# quote name for special character
|
||||
|
@ -132,10 +128,10 @@ class App.ObjectOrganizationAutocompletion extends App.Controller
|
|||
if @callback
|
||||
@callback(objectId)
|
||||
|
||||
createToken: (name, objectId) =>
|
||||
createToken: ({name, value}) =>
|
||||
@objectSelect.before App.view('generic/token')(
|
||||
name: name
|
||||
value: objectId
|
||||
value: value
|
||||
)
|
||||
|
||||
removeThisToken: (e) =>
|
||||
|
@ -149,12 +145,9 @@ class App.ObjectOrganizationAutocompletion extends App.Controller
|
|||
else
|
||||
token = which
|
||||
|
||||
# remove objectId from input
|
||||
index = @$('.token').index(token)
|
||||
ids = @objectId.val().split(',')
|
||||
ids.splice(index, 1)
|
||||
@objectId.val ids.join(',')
|
||||
|
||||
id = token.data('value')
|
||||
@objectId.find("[value=#{id}]").remove()
|
||||
@objectId.trigger('change')
|
||||
token.remove()
|
||||
|
||||
navigateByKeyboard: (e) =>
|
||||
|
@ -170,7 +163,7 @@ class App.ObjectOrganizationAutocompletion extends App.Controller
|
|||
@objectSelect.val('').trigger('change')
|
||||
# remove last token on backspace
|
||||
when 8
|
||||
if @objectSelect.val() is ''
|
||||
if @objectSelect.val() is '' && @objectSelect.is(e.target)
|
||||
@removeToken('last')
|
||||
# close on tab
|
||||
when 9 then @close()
|
||||
|
@ -223,7 +216,8 @@ class App.ObjectOrganizationAutocompletion extends App.Controller
|
|||
return
|
||||
objectId = recipientListOrganizationMembers.find('li.is-active').data('object-id')
|
||||
return if !objectId
|
||||
@selectObject(objectId)
|
||||
objectName = recipientListOrganizationMembers.find('li.is-active .recipientList-name').text().trim()
|
||||
@selectObject(objectId, objectName)
|
||||
@close() if !@attribute.multiple
|
||||
return
|
||||
|
||||
|
@ -233,7 +227,8 @@ class App.ObjectOrganizationAutocompletion extends App.Controller
|
|||
if objectId is 'new'
|
||||
@newObject()
|
||||
else
|
||||
@selectObject(objectId)
|
||||
objectName = @recipientList.find('li.is-active .recipientList-name').text().trim()
|
||||
@selectObject(objectId, objectName)
|
||||
@close() if !@attribute.multiple
|
||||
return
|
||||
|
||||
|
@ -242,6 +237,14 @@ class App.ObjectOrganizationAutocompletion extends App.Controller
|
|||
@showOrganizationMembers(undefined, @recipientList.find('li.is-active'))
|
||||
|
||||
|
||||
addValueToObjectInput: (objectName, objectId) ->
|
||||
@objectSelect.val('')
|
||||
@currentObject = {name: objectName, value: objectId}
|
||||
if @objectId.val()
|
||||
return if @objectId.val().includes("#{objectId}") # cast objectId to string before check
|
||||
@objectId.append("<option value=#{App.Utils.htmlEscape(@currentObject.value)} selected>#{App.Utils.htmlEscape(@currentObject.name)}</option>")
|
||||
@objectId.trigger('change')
|
||||
|
||||
buildOrganizationItem: (organization) ->
|
||||
objectCount = 0
|
||||
if organization[@referenceAttribute]
|
||||
|
@ -315,17 +318,23 @@ class App.ObjectOrganizationAutocompletion extends App.Controller
|
|||
# fallback for if the value is not an array
|
||||
if typeof @attribute.value isnt 'object'
|
||||
@attribute.value = [@attribute.value]
|
||||
value = @attribute.value.join ','
|
||||
|
||||
# create tokens
|
||||
# create tokens and attribute values
|
||||
values = []
|
||||
for objectId in @attribute.value
|
||||
if App[@objectSingle].exists objectId
|
||||
objectName = App[@objectSingle].find(objectId).displayName()
|
||||
objectValue = objectId
|
||||
values.push({name: objectName, value: objectValue})
|
||||
tokens += App.view('generic/token')(
|
||||
name: App[@objectSingle].find(objectId).displayName()
|
||||
value: objectId
|
||||
name: objectName
|
||||
value: objectValue
|
||||
)
|
||||
else
|
||||
@log 'objectId doesn\'t exist', objectId
|
||||
|
||||
@attribute.value = values
|
||||
|
||||
else
|
||||
value = @attribute.value
|
||||
if value
|
||||
|
@ -369,7 +378,7 @@ class App.ObjectOrganizationAutocompletion extends App.Controller
|
|||
@recipientList.append(@buildObjectNew())
|
||||
|
||||
# reset object selection
|
||||
@resetObjectSelection()
|
||||
@resetObjectSelection() if !@attribute.multiple
|
||||
return
|
||||
|
||||
# show dropdown
|
||||
|
|
|
@ -15,6 +15,7 @@ class App.SearchableSelect extends Spine.Controller
|
|||
'shown.bs.dropdown': 'onDropdownShown'
|
||||
'hidden.bs.dropdown': 'onDropdownHidden'
|
||||
'keyup .js-input': 'onKeyUp'
|
||||
'click .js-remove': 'removeThisToken'
|
||||
|
||||
elements:
|
||||
'.js-dropdown': 'dropdown'
|
||||
|
@ -38,10 +39,33 @@ class App.SearchableSelect extends Spine.Controller
|
|||
render: ->
|
||||
@updateAttributeValueName()
|
||||
|
||||
tokens = ''
|
||||
if @attribute.multiple && @attribute.value
|
||||
object = @attribute.object
|
||||
|
||||
# fallback for if the value is not an array
|
||||
if typeof @attribute.value isnt 'object'
|
||||
@attribute.value = [@attribute.value]
|
||||
|
||||
# create tokens and attribute values
|
||||
values = []
|
||||
for dataId in @attribute.value
|
||||
if App[object].exists dataId
|
||||
name = App[object].find(dataId).displayName()
|
||||
value = dataId
|
||||
values.push({name: name, value: value})
|
||||
tokens += App.view('generic/token')(
|
||||
name: name
|
||||
value: value
|
||||
)
|
||||
|
||||
@attribute.value = values
|
||||
|
||||
@html App.view('generic/searchable_select')
|
||||
attribute: @attribute
|
||||
options: @renderAllOptions('', @attribute.options, 0)
|
||||
submenus: @renderSubmenus(@attribute.options)
|
||||
tokens: tokens
|
||||
|
||||
# initial data
|
||||
@currentMenu = @findMenuContainingValue(@attribute.value)
|
||||
|
@ -133,12 +157,12 @@ class App.SearchableSelect extends Spine.Controller
|
|||
@unhighlightCurrentItem()
|
||||
@isOpen = false
|
||||
|
||||
if !@input.val()
|
||||
if !@input.val() && !@attribute.multiple
|
||||
@updateAttributeValueName()
|
||||
@input.val(@attribute.valueName)
|
||||
|
||||
onKeyUp: =>
|
||||
return if @input.val().trim() isnt ''
|
||||
return if @input.val().trim() isnt '' || @attribute.multiple
|
||||
@shadowInput.val('')
|
||||
|
||||
toggle: =>
|
||||
|
@ -157,6 +181,9 @@ class App.SearchableSelect extends Spine.Controller
|
|||
when 13 then @onEnter(event)
|
||||
when 27 then @onEscape(event)
|
||||
when 9 then @onTab(event)
|
||||
when 8 # remove last token on backspace
|
||||
if @input.val() is '' && @input.is(event.target) && @attribute.multiple
|
||||
@removeToken('last')
|
||||
|
||||
onEscape: ->
|
||||
if @isOpen
|
||||
|
@ -192,7 +219,7 @@ class App.SearchableSelect extends Spine.Controller
|
|||
@clearAutocomplete()
|
||||
|
||||
autocompleteOrNavigateIn: (event) ->
|
||||
if @currentItem.hasClass('js-enter')
|
||||
if @currentItem && @currentItem.hasClass('js-enter')
|
||||
@navigateIn(event)
|
||||
else
|
||||
@fillWithAutocompleteSuggestion(event)
|
||||
|
@ -215,8 +242,11 @@ class App.SearchableSelect extends Spine.Controller
|
|||
# current position
|
||||
caretPosition = @invisiblePart.text().length + 1
|
||||
|
||||
@input.val(@suggestion)
|
||||
@shadowInput.val(@suggestionValue)
|
||||
if @attribute.multiple
|
||||
@addValueToShadowInput(@suggestion, @suggestionValue)
|
||||
else
|
||||
@input.val(@suggestion)
|
||||
@shadowInput.val(@suggestionValue)
|
||||
@clearAutocomplete()
|
||||
@toggle()
|
||||
|
||||
|
@ -242,10 +272,15 @@ class App.SearchableSelect extends Spine.Controller
|
|||
selectItem: (event) ->
|
||||
currentText = event.currentTarget.querySelector('span.searchableSelect-option-text').textContent.trim()
|
||||
return if !currentText
|
||||
@input.val currentText
|
||||
@input.trigger('change')
|
||||
@shadowInput.val event.currentTarget.getAttribute('data-value')
|
||||
@shadowInput.trigger('change')
|
||||
|
||||
dataId = event.currentTarget.getAttribute('data-value')
|
||||
if @attribute.multiple
|
||||
@addValueToShadowInput(currentText, dataId)
|
||||
else
|
||||
@input.val currentText
|
||||
@input.trigger('change')
|
||||
@shadowInput.val dataId
|
||||
@shadowInput.trigger('change')
|
||||
|
||||
navigateIn: (event) ->
|
||||
event.stopPropagation()
|
||||
|
@ -354,11 +389,14 @@ class App.SearchableSelect extends Spine.Controller
|
|||
if @currentItem || !@attribute.unknown
|
||||
valueName = @currentItem.children('span.searchableSelect-option-text').text().trim()
|
||||
value = @currentItem.attr('data-value')
|
||||
@input.val valueName
|
||||
@shadowInput.val value
|
||||
if @attribute.multiple
|
||||
@addValueToShadowInput(valueName, value)
|
||||
else
|
||||
@input.val valueName
|
||||
@shadowInput.val value
|
||||
@shadowInput.trigger('change')
|
||||
|
||||
@input.trigger('change')
|
||||
@shadowInput.trigger('change')
|
||||
|
||||
if @currentItem
|
||||
if @currentItem.hasClass('js-enter')
|
||||
|
@ -386,17 +424,44 @@ class App.SearchableSelect extends Spine.Controller
|
|||
onShadowChange: ->
|
||||
value = @shadowInput.val()
|
||||
|
||||
if @attribute.multiple and @currentData
|
||||
# create token
|
||||
@createToken(@currentData)
|
||||
@currentData = null
|
||||
|
||||
if Array.isArray(@attribute.options)
|
||||
for option in @attribute.options
|
||||
option.selected = (option.value + '') == value # makes sure option value is always a string
|
||||
|
||||
createToken: ({name, value}) =>
|
||||
@input.before App.view('generic/token')(
|
||||
name: name
|
||||
value: value
|
||||
)
|
||||
|
||||
removeThisToken: (e) =>
|
||||
@removeToken $(e.currentTarget).parents('.token')
|
||||
|
||||
removeToken: (which) =>
|
||||
switch which
|
||||
when 'last'
|
||||
token = @$('.token').last()
|
||||
return if not token.size()
|
||||
else
|
||||
token = which
|
||||
|
||||
id = token.data('value')
|
||||
@shadowInput.find("[value=#{id}]").remove()
|
||||
@shadowInput.trigger('change')
|
||||
token.remove()
|
||||
|
||||
onInput: (event) =>
|
||||
@toggle() if not @isOpen
|
||||
|
||||
@query = @input.val()
|
||||
@filterByQuery @query
|
||||
|
||||
if @attribute.unknown
|
||||
if @attribute.unknown && !@attribute.multiple
|
||||
@shadowInput.val @query
|
||||
|
||||
filterByQuery: (query) ->
|
||||
|
@ -422,6 +487,14 @@ class App.SearchableSelect extends Spine.Controller
|
|||
else
|
||||
@highlightFirst(true)
|
||||
|
||||
addValueToShadowInput: (currentText, dataId) ->
|
||||
@input.val('')
|
||||
@currentData = {name: currentText, value: dataId}
|
||||
if @shadowInput.val()
|
||||
return if @shadowInput.val().includes("#{dataId}") # cast dataId to string before check
|
||||
@shadowInput.append($('<option/>').attr('selected', true).attr('value', @currentData.value).text(@currentData.name))
|
||||
@shadowInput.trigger('change')
|
||||
|
||||
highlightFirst: (autocomplete) ->
|
||||
@unhighlightCurrentItem()
|
||||
@currentItem = @getCurrentOptions().not('.is-hidden').first()
|
||||
|
|
|
@ -1,12 +1,20 @@
|
|||
<div class="tokenfield form-control u-positionOrigin">
|
||||
<input class="js-objectId" type="hidden" value="<%= @value %>" name="<%- @attribute.name %>" tabindex="-1">
|
||||
<% if @attribute.multiple: %>
|
||||
<select multiple class="js-objectId hide" name="<%= @attribute.name %>" tabindex="-1">
|
||||
<% if @attribute.value: %>
|
||||
<% for option in @attribute.value: %>
|
||||
<option value="<%= option.value %>" selected><%= option.name %></option>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</select>
|
||||
<%- @tokens %>
|
||||
<% else: %>
|
||||
<input class="js-objectId" type="hidden" value="<%= @value %>" name="<%= @attribute.name %>" tabindex="-1">
|
||||
<% end %>
|
||||
<input name="<%- @attribute.name %>_completion" class="user-select token-input js-objectSelect" autocapitalize="off" placeholder="<%- @Ti(@attribute.placeholder) %>" autocomplete="off" <%= @attribute.autofocus %> role="textbox" aria-autocomplete="list" value="<%= @name %>" aria-haspopup="true">
|
||||
<input name="<%= @attribute.name %>_completion" class="user-select token-input js-objectSelect" autocapitalize="off" placeholder="<%- @Ti(@attribute.placeholder) %>" autocomplete="off" <%= @attribute.autofocus %> role="textbox" aria-autocomplete="list" value="<%= @name %>" aria-haspopup="true">
|
||||
<% if @attribute.disableCreateObject isnt true: %><%- @Icon('arrow-down', 'dropdown-arrow') %><% end %>
|
||||
</div>
|
||||
|
||||
<div class="dropdown-menu" aria-labelledby="customer_id">
|
||||
<ul class="recipientList" role="menu"></ul>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,22 +1,42 @@
|
|||
<div class="dropdown-toggle" data-toggle="dropdown">
|
||||
<input
|
||||
class="searchableSelect-shadow form-control js-shadow"
|
||||
<% if @attribute.id: %>id="<%= @attribute.id %>"<% end %>
|
||||
name="<%= @attribute.name %>"
|
||||
<%= @attribute.required %>
|
||||
<%= @attribute.autofocus %>
|
||||
value="<%= @attribute.value %>"
|
||||
tabindex="-1"
|
||||
<% if @attribute.disabled: %> disabled<% end %>
|
||||
>
|
||||
<input
|
||||
class="searchableSelect-main form-control js-input<%= " #{ @attribute.class }" if @attribute.class %>"
|
||||
placeholder="<%= @attribute.placeholder %>"
|
||||
value="<%= @attribute.valueName %>"
|
||||
autocomplete="off"
|
||||
<%= @attribute.required %>
|
||||
<% if @attribute.disabled: %> disabled<% end %>
|
||||
>
|
||||
<div class="dropdown-toggle<%=" tokenfield form-control" if @attribute.multiple %>" data-toggle="dropdown">
|
||||
<% if @attribute.multiple: %>
|
||||
<select multiple class="js-shadow hide" name="<%- @attribute.name %>" tabindex="-1">
|
||||
<% if @attribute.value: %>
|
||||
<% for option in @attribute.value: %>
|
||||
<option value="<%= option.value %>" selected><%= option.name %></option>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</select>
|
||||
<%- @tokens %>
|
||||
<input
|
||||
class="searchableSelect-main token-input form-control js-input<%= " #{ @attribute.class }" if @attribute.class %>"
|
||||
placeholder="<%= @attribute.placeholder %>"
|
||||
value
|
||||
name="<%- @attribute.name %>_completion"
|
||||
autocomplete="off"
|
||||
<%= @attribute.required %>
|
||||
<% if @attribute.disabled: %> disabled<% end %>
|
||||
>
|
||||
<% else: %>
|
||||
<input
|
||||
class="searchableSelect-shadow form-control js-shadow"
|
||||
<% if @attribute.id: %>id="<%= @attribute.id %>"<% end %>
|
||||
name="<%= @attribute.name %>"
|
||||
<%= @attribute.required %>
|
||||
<%= @attribute.autofocus %>
|
||||
value="<%= @attribute.value %>"
|
||||
tabindex="-1"
|
||||
<% if @attribute.disabled: %> disabled<% end %>
|
||||
>
|
||||
<input
|
||||
class="searchableSelect-main form-control js-input<%= " #{ @attribute.class }" if @attribute.class %>"
|
||||
placeholder="<%= @attribute.placeholder %>"
|
||||
value="<%= @attribute.valueName %>"
|
||||
autocomplete="off"
|
||||
<%= @attribute.required %>
|
||||
<% if @attribute.disabled: %> disabled<% end %>
|
||||
>
|
||||
<% end %>
|
||||
<div class="searchableSelect-autocomplete">
|
||||
<span class="searchableSelect-autocomplete-invisible js-autocomplete-invisible"></span>
|
||||
<span class="searchableSelect-autocomplete-visible js-autocomplete-visible"></span>
|
||||
|
@ -29,4 +49,4 @@
|
|||
<%- @options %>
|
||||
</ul>
|
||||
<%- @submenus %>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -531,17 +531,18 @@ QUnit.test('form checks', assert => {
|
|||
operator: 'is not',
|
||||
pre_condition: 'specific',
|
||||
value: '12',
|
||||
value_completion: '',
|
||||
},
|
||||
'ticket.owner_id': {
|
||||
operator: 'is',
|
||||
pre_condition: 'specific',
|
||||
value: '47',
|
||||
value_completion: 'Bob Smith <bod@example.com>',
|
||||
value_completion: '',
|
||||
},
|
||||
'ticket.created_by_id': {
|
||||
operator: 'is',
|
||||
pre_condition: 'current_user.id',
|
||||
value: '',
|
||||
value: null,
|
||||
value_completion: ''
|
||||
},
|
||||
},
|
||||
|
@ -552,7 +553,7 @@ QUnit.test('form checks', assert => {
|
|||
'ticket.owner_id': {
|
||||
pre_condition: 'specific',
|
||||
value: '47',
|
||||
value_completion: 'Bob Smith <bod@example.com>'
|
||||
value_completion: ''
|
||||
},
|
||||
'ticket.priority_id': {
|
||||
value: '3',
|
||||
|
@ -596,17 +597,18 @@ QUnit.test('form checks', assert => {
|
|||
operator: 'is not',
|
||||
pre_condition: 'specific',
|
||||
value: '12',
|
||||
value_completion: '',
|
||||
},
|
||||
'ticket.owner_id': {
|
||||
operator: 'is',
|
||||
pre_condition: 'specific',
|
||||
value: '47',
|
||||
value_completion: 'Bob Smith <bod@example.com>',
|
||||
value_completion: '',
|
||||
},
|
||||
'ticket.created_by_id': {
|
||||
operator: 'is',
|
||||
pre_condition: 'current_user.id',
|
||||
value: '',
|
||||
value: null,
|
||||
value_completion: ''
|
||||
},
|
||||
},
|
||||
|
@ -617,7 +619,7 @@ QUnit.test('form checks', assert => {
|
|||
'ticket.owner_id': {
|
||||
pre_condition: 'specific',
|
||||
value: '47',
|
||||
value_completion: 'Bob Smith <bod@example.com>'
|
||||
value_completion: ''
|
||||
},
|
||||
'ticket.tags': {
|
||||
operator: 'remove',
|
||||
|
@ -657,17 +659,18 @@ QUnit.test('form checks', assert => {
|
|||
operator: 'is not',
|
||||
pre_condition: 'specific',
|
||||
value: '12',
|
||||
value_completion: '',
|
||||
},
|
||||
'ticket.owner_id': {
|
||||
operator: 'is',
|
||||
pre_condition: 'specific',
|
||||
value: '47',
|
||||
value_completion: 'Bob Smith <bod@example.com>',
|
||||
value_completion: '',
|
||||
},
|
||||
'ticket.created_by_id': {
|
||||
operator: 'is',
|
||||
pre_condition: 'current_user.id',
|
||||
value: '',
|
||||
value: null,
|
||||
value_completion: ''
|
||||
},
|
||||
},
|
||||
|
@ -678,7 +681,7 @@ QUnit.test('form checks', assert => {
|
|||
'ticket.owner_id': {
|
||||
pre_condition: 'specific',
|
||||
value: '47',
|
||||
value_completion: 'Bob Smith <bod@example.com>'
|
||||
value_completion: ''
|
||||
},
|
||||
'ticket.tags': {
|
||||
operator: 'remove',
|
||||
|
@ -714,17 +717,18 @@ QUnit.test('form checks', assert => {
|
|||
operator: 'is not',
|
||||
pre_condition: 'specific',
|
||||
value: '12',
|
||||
value_completion: '',
|
||||
},
|
||||
'ticket.owner_id': {
|
||||
operator: 'is',
|
||||
pre_condition: 'specific',
|
||||
value: '47',
|
||||
value_completion: 'Bob Smith <bod@example.com>',
|
||||
value_completion: '',
|
||||
},
|
||||
'ticket.created_by_id': {
|
||||
operator: 'is',
|
||||
pre_condition: 'current_user.id',
|
||||
value: '',
|
||||
value: null,
|
||||
value_completion: ''
|
||||
},
|
||||
},
|
||||
|
@ -735,7 +739,7 @@ QUnit.test('form checks', assert => {
|
|||
'ticket.owner_id': {
|
||||
pre_condition: 'specific',
|
||||
value: '47',
|
||||
value_completion: 'Bob Smith <bod@example.com>'
|
||||
value_completion: ''
|
||||
},
|
||||
'notification.email': {
|
||||
recipient: 'ticket_owner',
|
||||
|
@ -849,4 +853,167 @@ QUnit.test('form checks', assert => {
|
|||
},
|
||||
}
|
||||
assert.deepEqual(params, test_params, 'form article body param check')
|
||||
|
||||
App.User.refresh([
|
||||
{
|
||||
id: 44,
|
||||
login: 'bod@example.com',
|
||||
email: 'bod@example.com',
|
||||
firstname: 'Bob',
|
||||
lastname: 'Smith',
|
||||
active: true,
|
||||
created_at: '2014-06-10T11:17:34.000Z',
|
||||
},
|
||||
{
|
||||
id: 45,
|
||||
login: 'john@example.com',
|
||||
email: 'john@example.com',
|
||||
firstname: 'John',
|
||||
lastname: 'Doe',
|
||||
active: true,
|
||||
created_at: '2014-07-10T11:17:34.000Z',
|
||||
},
|
||||
{
|
||||
id: 46,
|
||||
login: 'sam@example.com',
|
||||
email: 'sam@example.com',
|
||||
firstname: 'Sam',
|
||||
lastname: 'Bond',
|
||||
active: true,
|
||||
created_at: '2014-08-10T11:17:34.000Z',
|
||||
},
|
||||
{
|
||||
id: 30,
|
||||
login: 'clark@example.com',
|
||||
email: 'clark@example.com',
|
||||
firstname: 'Clark',
|
||||
lastname: 'Olsen',
|
||||
active: true,
|
||||
created_at: '2016-02-10T11:17:34.000Z',
|
||||
},
|
||||
{
|
||||
id: 31,
|
||||
login: 'james@example.com',
|
||||
email: 'james@example.com',
|
||||
firstname: 'James',
|
||||
lastname: 'Puth',
|
||||
active: true,
|
||||
created_at: '2016-03-10T11:17:34.000Z',
|
||||
},
|
||||
{
|
||||
id: 32,
|
||||
login: 'charles@example.com',
|
||||
email: 'charles@example.com',
|
||||
firstname: 'Charles',
|
||||
lastname: 'Kent',
|
||||
active: true,
|
||||
created_at: '2016-04-10T11:17:34.000Z',
|
||||
},
|
||||
])
|
||||
|
||||
App.Organization.refresh([
|
||||
{
|
||||
id: 9,
|
||||
name: 'Org 1',
|
||||
active: true,
|
||||
created_at: '2018-06-10T11:19:34.000Z',
|
||||
},
|
||||
{
|
||||
id: 10,
|
||||
name: 'Org 2',
|
||||
active: true,
|
||||
created_at: '2018-06-10T11:19:34.000Z',
|
||||
},
|
||||
{
|
||||
id: 11,
|
||||
name: 'Org 3',
|
||||
active: true,
|
||||
created_at: '2018-06-10T11:19:34.000Z',
|
||||
},
|
||||
])
|
||||
|
||||
/* with params or defaults */
|
||||
$('#forms').append('<hr><h1>form condition check for multiple user and organisation selection</h1><form id="form6"></form>')
|
||||
var el = $('#form6')
|
||||
var defaults = {
|
||||
condition: {
|
||||
'ticket.title': {
|
||||
operator: 'contains',
|
||||
value: 'some title',
|
||||
},
|
||||
'ticket.organization_id': {
|
||||
operator: 'is',
|
||||
pre_condition: 'specific',
|
||||
value: [9, 10, 11],
|
||||
},
|
||||
'ticket.owner_id': {
|
||||
operator: 'is not',
|
||||
pre_condition: 'specific',
|
||||
value: [44, 45, 46],
|
||||
},
|
||||
'ticket.customer_id': {
|
||||
operator: 'is',
|
||||
pre_condition: 'specific',
|
||||
value: [30, 31, 32],
|
||||
},
|
||||
},
|
||||
executions: {
|
||||
'ticket.title': {
|
||||
value: 'some title new',
|
||||
},
|
||||
'ticket.owner_id': {
|
||||
pre_condition: 'specific',
|
||||
value: [44, 46],
|
||||
},
|
||||
},
|
||||
}
|
||||
new App.ControllerForm({
|
||||
el: el,
|
||||
model: {
|
||||
configure_attributes: [
|
||||
{ name: 'condition', display: 'Conditions', tag: 'ticket_selector', null: true },
|
||||
{ name: 'executions', display: 'Executions', tag: 'ticket_perform_action', null: true, notification: true },
|
||||
]
|
||||
},
|
||||
params: defaults,
|
||||
autofocus: true
|
||||
})
|
||||
var params = App.ControllerForm.params(el)
|
||||
var test_params = {
|
||||
condition: {
|
||||
'ticket.title': {
|
||||
operator: 'contains',
|
||||
value: 'some title',
|
||||
},
|
||||
'ticket.organization_id': {
|
||||
operator: 'is',
|
||||
pre_condition: 'specific',
|
||||
value: ['9', '10', '11'],
|
||||
value_completion: ''
|
||||
},
|
||||
'ticket.owner_id': {
|
||||
operator: 'is not',
|
||||
pre_condition: 'specific',
|
||||
value: ['44', '45', '46'],
|
||||
value_completion: ''
|
||||
},
|
||||
'ticket.customer_id': {
|
||||
operator: 'is',
|
||||
pre_condition: 'specific',
|
||||
value: ['30', '31', '32'],
|
||||
value_completion: ''
|
||||
},
|
||||
},
|
||||
executions: {
|
||||
'ticket.title': {
|
||||
value: 'some title new',
|
||||
},
|
||||
'ticket.owner_id': {
|
||||
pre_condition: 'specific',
|
||||
value: ['44', '46'],
|
||||
value_completion: ''
|
||||
},
|
||||
},
|
||||
}
|
||||
assert.deepEqual(params, test_params, 'form param condition check for multiple users and organisation')
|
||||
});
|
||||
|
|
|
@ -595,7 +595,7 @@ QUnit.test( "ticket_perform_action check possible owner selection", assert => {
|
|||
ticket_perform_action5: {
|
||||
'ticket.owner_id': {
|
||||
pre_condition: 'not_set',
|
||||
value: '',
|
||||
value: null,
|
||||
value_completion: ''
|
||||
}
|
||||
}
|
||||
|
@ -610,7 +610,7 @@ QUnit.test( "ticket_perform_action check possible owner selection", assert => {
|
|||
ticket_perform_action5: {
|
||||
'ticket.owner_id': {
|
||||
pre_condition: 'specific',
|
||||
value: '',
|
||||
value: null,
|
||||
value_completion: ''
|
||||
}
|
||||
}
|
||||
|
|
370
spec/system/manage/overviews_spec.rb
Normal file
370
spec/system/manage/overviews_spec.rb
Normal file
|
@ -0,0 +1,370 @@
|
|||
# Copyright (C) 2012-2022 Zammad Foundation, https://zammad-foundation.org/
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe 'Manage > Overviews', type: :system do
|
||||
let(:group) { create(:group) }
|
||||
|
||||
let(:owner_one) { create(:agent, groups: [group]) }
|
||||
let(:owner_two) { create(:agent, groups: [group]) }
|
||||
let(:owner_three) { create(:agent, groups: [group]) }
|
||||
|
||||
let(:customer_one) { create(:customer, organization_id: organization_one.id, groups: [group]) }
|
||||
let(:customer_two) { create(:customer, organization_id: organization_two.id, groups: [group]) }
|
||||
let(:customer_three) { create(:customer, organization_id: organization_three.id, groups: [group]) }
|
||||
|
||||
let(:organization_one) { create(:organization, name: 'Test Org One') }
|
||||
let(:organization_two) { create(:organization, name: 'Test Org Two') }
|
||||
let(:organization_three) { create(:organization, name: 'Test Org Three') }
|
||||
|
||||
let!(:ticket_one) do
|
||||
create(:ticket,
|
||||
title: 'Test Ticket One',
|
||||
group: group,
|
||||
owner_id: owner_one.id,
|
||||
customer_id: customer_one.id)
|
||||
end
|
||||
|
||||
let!(:ticket_two) do
|
||||
create(:ticket,
|
||||
title: 'Test Ticket Two',
|
||||
group: group,
|
||||
owner_id: owner_two.id,
|
||||
customer_id: customer_two.id)
|
||||
end
|
||||
|
||||
let!(:ticket_three) do
|
||||
create(:ticket,
|
||||
title: 'Test Ticket Three',
|
||||
group: group,
|
||||
owner_id: owner_three.id,
|
||||
customer_id: customer_three.id)
|
||||
end
|
||||
|
||||
let(:overview) { create(:overview, condition: condition) }
|
||||
|
||||
shared_examples 'previewing the correct ticket for single selected object' do
|
||||
context "with 'is' operator" do
|
||||
let(:operator) { 'is' }
|
||||
|
||||
it 'shows selected customer ticket' do
|
||||
within '.js-preview .js-tableBody' do
|
||||
expect(page).to have_selector('tr.item', text: ticket_one.title)
|
||||
end
|
||||
end
|
||||
|
||||
it 'does not show customer ticket that is not selected' do
|
||||
within '.js-preview .js-tableBody' do
|
||||
expect(page).to have_no_selector('tr.item', text: ticket_two.title)
|
||||
expect(page).to have_no_selector('tr.item', text: ticket_three.title)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "with 'is not' operator" do
|
||||
let(:operator) { 'is not' }
|
||||
|
||||
it 'does not show selected customer ticket' do
|
||||
within '.js-preview .js-tableBody' do
|
||||
expect(page).to have_no_selector('tr.item', text: ticket_one.title)
|
||||
end
|
||||
end
|
||||
|
||||
it 'does not show customer ticket that is not selected' do
|
||||
within '.js-preview .js-tableBody' do
|
||||
expect(page).to have_selector('tr.item', text: ticket_two.title)
|
||||
expect(page).to have_selector('tr.item', text: ticket_three.title)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples 'previewing the correct ticket for multiple selected objects' do
|
||||
context "with 'is' operator" do
|
||||
let(:operator) { 'is' }
|
||||
|
||||
it 'shows selected customer ticket' do
|
||||
within '.js-preview .js-tableBody' do
|
||||
expect(page).to have_selector('tr.item', text: ticket_one.title)
|
||||
expect(page).to have_selector('tr.item', text: ticket_two.title)
|
||||
end
|
||||
end
|
||||
|
||||
it 'does not show customer ticket that is not selected' do
|
||||
within '.js-preview .js-tableBody' do
|
||||
expect(page).to have_no_selector('tr.item', text: ticket_three.title)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "with 'is not' operator" do
|
||||
let(:operator) { 'is not' }
|
||||
|
||||
it 'does not show selected customer ticket' do
|
||||
within '.js-preview .js-tableBody' do
|
||||
expect(page).to have_no_selector('tr.item', text: ticket_one.title)
|
||||
expect(page).to have_no_selector('tr.item', text: ticket_two.title)
|
||||
end
|
||||
end
|
||||
|
||||
it 'does not show customer ticket that is not selected' do
|
||||
within '.js-preview .js-tableBody' do
|
||||
expect(page).to have_selector('tr.item', text: ticket_three.title)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'conditions for shown tickets' do
|
||||
context 'for customer' do
|
||||
context 'for new overview' do
|
||||
before do
|
||||
visit '/#manage/overviews'
|
||||
click_on 'New Overview'
|
||||
|
||||
modal_ready
|
||||
|
||||
within '.ticket_selector' do
|
||||
ticket_select = find('.js-attributeSelector select .js-ticket')
|
||||
ticket_select.select 'Customer'
|
||||
select operator, from: 'condition::ticket.customer_id::operator'
|
||||
select 'specific', from: 'condition::ticket.customer_id::pre_condition'
|
||||
end
|
||||
end
|
||||
|
||||
context 'when single customer is selected' do
|
||||
before do
|
||||
within '.ticket_selector' do
|
||||
fill_in 'condition::ticket.customer_id::value_completion', with: customer_one.firstname
|
||||
|
||||
find("[data-object-id='#{customer_one.id}'].js-object").click
|
||||
end
|
||||
end
|
||||
|
||||
it_behaves_like 'previewing the correct ticket for single selected object'
|
||||
end
|
||||
|
||||
context 'when multiple customer is selected' do
|
||||
before do
|
||||
within '.ticket_selector' do
|
||||
fill_in 'condition::ticket.customer_id::value_completion', with: customer_one.firstname
|
||||
find("[data-object-id='#{customer_one.id}'].js-object").click
|
||||
|
||||
fill_in 'condition::ticket.customer_id::value_completion', with: customer_two.firstname
|
||||
find("[data-object-id='#{customer_two.id}'].js-object").click
|
||||
end
|
||||
end
|
||||
|
||||
it_behaves_like 'previewing the correct ticket for multiple selected objects'
|
||||
end
|
||||
end
|
||||
|
||||
context 'for existing overview' do
|
||||
let(:condition) do
|
||||
{ 'ticket.customer_id' => {
|
||||
operator: operator,
|
||||
pre_condition: 'specific',
|
||||
value: condition_value
|
||||
} }
|
||||
end
|
||||
|
||||
before do
|
||||
overview
|
||||
|
||||
visit '/#manage/overviews'
|
||||
|
||||
within '.table-overview .js-tableBody' do
|
||||
find("tr[data-id='#{overview.id}'] td.table-draggable").click
|
||||
end
|
||||
|
||||
within '.ticket_selector' do
|
||||
# trigger the preview
|
||||
fill_in 'condition::ticket.customer_id::value_completion', with: customer_one.firstname
|
||||
end
|
||||
end
|
||||
|
||||
context 'when single customer exists' do
|
||||
let(:condition_value) { customer_one.id }
|
||||
|
||||
it_behaves_like 'previewing the correct ticket for single selected object'
|
||||
end
|
||||
|
||||
context 'when multiple customer exists' do
|
||||
let(:condition_value) { [customer_one.id, customer_two.id] }
|
||||
|
||||
it_behaves_like 'previewing the correct ticket for multiple selected objects'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'for owner' do
|
||||
context 'for new overview' do
|
||||
before do
|
||||
visit '/#manage/overviews'
|
||||
click_on 'New Overview'
|
||||
|
||||
modal_ready
|
||||
|
||||
within '.ticket_selector' do
|
||||
ticket_select = find('.js-attributeSelector select .js-ticket')
|
||||
ticket_select.select 'Owner'
|
||||
select operator, from: 'condition::ticket.owner_id::operator'
|
||||
select 'specific', from: 'condition::ticket.owner_id::pre_condition'
|
||||
end
|
||||
end
|
||||
|
||||
context 'when single owner is selected' do
|
||||
before do
|
||||
within '.ticket_selector' do
|
||||
fill_in 'condition::ticket.owner_id::value_completion', with: owner_one.firstname
|
||||
|
||||
first('.recipientList-entry.js-object').click
|
||||
end
|
||||
end
|
||||
|
||||
it_behaves_like 'previewing the correct ticket for single selected object'
|
||||
end
|
||||
|
||||
context 'when multiple owner is selected' do
|
||||
before do
|
||||
within '.ticket_selector' do
|
||||
fill_in 'condition::ticket.owner_id::value_completion', with: owner_one.firstname
|
||||
find("[data-object-id='#{owner_one.id}'].js-object").click
|
||||
|
||||
fill_in 'condition::ticket.owner_id::value_completion', with: owner_two.firstname
|
||||
find("[data-object-id='#{owner_two.id}'].js-object").click
|
||||
end
|
||||
end
|
||||
|
||||
it_behaves_like 'previewing the correct ticket for multiple selected objects'
|
||||
end
|
||||
end
|
||||
|
||||
context 'for existing overview' do
|
||||
let(:condition) do
|
||||
{ 'ticket.owner_id' => {
|
||||
operator: operator,
|
||||
pre_condition: 'specific',
|
||||
value: condition_value
|
||||
} }
|
||||
end
|
||||
|
||||
before do
|
||||
overview
|
||||
|
||||
visit '/#manage/overviews'
|
||||
|
||||
within '.table-overview .js-tableBody' do
|
||||
find("tr[data-id='#{overview.id}'] td.table-draggable").click
|
||||
end
|
||||
|
||||
within '.ticket_selector' do
|
||||
# trigger the preview
|
||||
fill_in 'condition::ticket.owner_id::value_completion', with: owner_one.firstname
|
||||
end
|
||||
end
|
||||
|
||||
context 'when single owner exists' do
|
||||
let(:condition_value) { owner_one.id }
|
||||
|
||||
it_behaves_like 'previewing the correct ticket for single selected object'
|
||||
end
|
||||
|
||||
context 'when multiple owner exists' do
|
||||
let(:condition_value) { [owner_one.id, owner_two.id] }
|
||||
|
||||
it_behaves_like 'previewing the correct ticket for multiple selected objects'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'for organization' do
|
||||
# let(:condition) do
|
||||
# { 'ticket.organization_id' => {
|
||||
# operator: operator,
|
||||
# pre_condition: 'specific',
|
||||
# value: [101, 102, 103]
|
||||
# } }
|
||||
# end
|
||||
|
||||
context 'for new overview' do
|
||||
before do
|
||||
visit '/#manage/overviews'
|
||||
click_on 'New Overview'
|
||||
|
||||
modal_ready
|
||||
|
||||
within '.ticket_selector' do
|
||||
ticket_select = find('.js-attributeSelector select .js-ticket')
|
||||
ticket_select.select 'Organization'
|
||||
select operator, from: 'condition::ticket.organization_id::operator'
|
||||
select 'specific', from: 'condition::ticket.organization_id::pre_condition'
|
||||
end
|
||||
end
|
||||
|
||||
context 'when single organization is selected' do
|
||||
before do
|
||||
within '.ticket_selector' do
|
||||
fill_in 'condition::ticket.organization_id::value_completion', with: organization_one.name
|
||||
|
||||
find(".js-optionsList [data-value='#{organization_one.id}'].js-option").click
|
||||
end
|
||||
end
|
||||
|
||||
it_behaves_like 'previewing the correct ticket for single selected object'
|
||||
end
|
||||
|
||||
context 'when multiple organization is selected' do
|
||||
before do
|
||||
within '.ticket_selector' do
|
||||
fill_in 'condition::ticket.organization_id::value_completion', with: organization_one.name
|
||||
find(".js-optionsList [data-value='#{organization_one.id}'].js-option").click
|
||||
|
||||
fill_in 'condition::ticket.organization_id::value_completion', with: organization_two.name
|
||||
find(".js-optionsList [data-value='#{organization_two.id}'].js-option").click
|
||||
end
|
||||
end
|
||||
|
||||
it_behaves_like 'previewing the correct ticket for multiple selected objects'
|
||||
end
|
||||
end
|
||||
|
||||
context 'for existing overview' do
|
||||
let(:condition) do
|
||||
{ 'ticket.organization_id' => {
|
||||
operator: operator,
|
||||
pre_condition: 'specific',
|
||||
value: condition_value
|
||||
} }
|
||||
end
|
||||
|
||||
before do
|
||||
overview
|
||||
|
||||
visit '/#manage/overviews'
|
||||
|
||||
within '.table-overview .js-tableBody' do
|
||||
find("tr[data-id='#{overview.id}'] td.table-draggable").click
|
||||
end
|
||||
|
||||
within '.ticket_selector' do
|
||||
# trigger the preview
|
||||
fill_in 'condition::ticket.organization_id::value_completion', with: organization_one.name
|
||||
end
|
||||
end
|
||||
|
||||
context 'when single organization exists' do
|
||||
let(:condition_value) { organization_one.id }
|
||||
|
||||
it_behaves_like 'previewing the correct ticket for single selected object'
|
||||
end
|
||||
|
||||
context 'when multiple organization exists' do
|
||||
let(:condition_value) { [organization_one.id, organization_two.id] }
|
||||
|
||||
it_behaves_like 'previewing the correct ticket for multiple selected objects'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in a new issue