diff --git a/app/assets/javascripts/app/controllers/_ui_element/autocompletion_ajax.coffee b/app/assets/javascripts/app/controllers/_ui_element/autocompletion_ajax.coffee
index a43d55579..31d3cf9a0 100644
--- a/app/assets/javascripts/app/controllers/_ui_element/autocompletion_ajax.coffee
+++ b/app/assets/javascripts/app/controllers/_ui_element/autocompletion_ajax.coffee
@@ -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()
diff --git a/app/assets/javascripts/app/controllers/_ui_element/autocompletion_ajax_search.coffee b/app/assets/javascripts/app/controllers/_ui_element/autocompletion_ajax_search.coffee
new file mode 100644
index 000000000..cd588e9b7
--- /dev/null
+++ b/app/assets/javascripts/app/controllers/_ui_element/autocompletion_ajax_search.coffee
@@ -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)
diff --git a/app/assets/javascripts/app/controllers/_ui_element/user_autocompletion_search.coffee b/app/assets/javascripts/app/controllers/_ui_element/user_autocompletion_search.coffee
index f6dfae836..404f51c7e 100644
--- a/app/assets/javascripts/app/controllers/_ui_element/user_autocompletion_search.coffee
+++ b/app/assets/javascripts/app/controllers/_ui_element/user_autocompletion_search.coffee
@@ -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()
diff --git a/app/assets/javascripts/app/controllers/overview.coffee b/app/assets/javascripts/app/controllers/overview.coffee
index cd0c773fc..ec3e3f07d 100644
--- a/app/assets/javascripts/app/controllers/overview.coffee
+++ b/app/assets/javascripts/app/controllers/overview.coffee
@@ -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 = []
diff --git a/app/assets/javascripts/app/lib/app_post/_object_organization_autocompletion.coffee b/app/assets/javascripts/app/lib/app_post/_object_organization_autocompletion.coffee
index 83b053959..e7d424c52 100644
--- a/app/assets/javascripts/app/lib/app_post/_object_organization_autocompletion.coffee
+++ b/app/assets/javascripts/app/lib/app_post/_object_organization_autocompletion.coffee
@@ -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("#{App.Utils.htmlEscape(@currentObject.name)} ")
+ @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
diff --git a/app/assets/javascripts/app/lib/app_post/searchable_select.coffee b/app/assets/javascripts/app/lib/app_post/searchable_select.coffee
index d3cb7225d..d4d54fa92 100644
--- a/app/assets/javascripts/app/lib/app_post/searchable_select.coffee
+++ b/app/assets/javascripts/app/lib/app_post/searchable_select.coffee
@@ -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($(' ').attr('selected', true).attr('value', @currentData.value).text(@currentData.name))
+ @shadowInput.trigger('change')
+
highlightFirst: (autocomplete) ->
@unhighlightCurrentItem()
@currentItem = @getCurrentOptions().not('.is-hidden').first()
diff --git a/app/assets/javascripts/app/views/generic/object_search/input.jst.eco b/app/assets/javascripts/app/views/generic/object_search/input.jst.eco
index a56584e7e..2fad2d74c 100644
--- a/app/assets/javascripts/app/views/generic/object_search/input.jst.eco
+++ b/app/assets/javascripts/app/views/generic/object_search/input.jst.eco
@@ -1,12 +1,20 @@
-
<% if @attribute.multiple: %>
+
+ <% if @attribute.value: %>
+ <% for option in @attribute.value: %>
+ <%= option.name %>
+ <% end %>
+ <% end %>
+
<%- @tokens %>
+ <% else: %>
+
<% end %>
- role="textbox" aria-autocomplete="list" value="<%= @name %>" aria-haspopup="true">
+ role="textbox" aria-autocomplete="list" value="<%= @name %>" aria-haspopup="true">
<% if @attribute.disableCreateObject isnt true: %><%- @Icon('arrow-down', 'dropdown-arrow') %><% end %>
\ No newline at end of file
+
diff --git a/app/assets/javascripts/app/views/generic/searchable_select.jst.eco b/app/assets/javascripts/app/views/generic/searchable_select.jst.eco
index e85c5980b..2c2cdc043 100644
--- a/app/assets/javascripts/app/views/generic/searchable_select.jst.eco
+++ b/app/assets/javascripts/app/views/generic/searchable_select.jst.eco
@@ -1,22 +1,42 @@
-
-
id="<%= @attribute.id %>"<% end %>
- name="<%= @attribute.name %>"
- <%= @attribute.required %>
- <%= @attribute.autofocus %>
- value="<%= @attribute.value %>"
- tabindex="-1"
- <% if @attribute.disabled: %> disabled<% end %>
- >
-
"
- placeholder="<%= @attribute.placeholder %>"
- value="<%= @attribute.valueName %>"
- autocomplete="off"
- <%= @attribute.required %>
- <% if @attribute.disabled: %> disabled<% end %>
- >
+
diff --git a/public/assets/tests/qunit/form_extended.js b/public/assets/tests/qunit/form_extended.js
index cd7026252..f06136cc1 100644
--- a/public/assets/tests/qunit/form_extended.js
+++ b/public/assets/tests/qunit/form_extended.js
@@ -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
',
+ 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 '
+ 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 ',
+ 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 '
+ 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 ',
+ 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 '
+ 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 ',
+ 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 '
+ 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('form condition check for multiple user and organisation selection ')
+ 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')
});
diff --git a/public/assets/tests/qunit/form_ticket_perform_action.js b/public/assets/tests/qunit/form_ticket_perform_action.js
index 737e31387..7e7d8b490 100644
--- a/public/assets/tests/qunit/form_ticket_perform_action.js
+++ b/public/assets/tests/qunit/form_ticket_perform_action.js
@@ -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: ''
}
}
diff --git a/spec/system/manage/overviews_spec.rb b/spec/system/manage/overviews_spec.rb
new file mode 100644
index 000000000..4b3ba5072
--- /dev/null
+++ b/spec/system/manage/overviews_spec.rb
@@ -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