Moved to new postmaster filter ui with contains and contains not feature.
This commit is contained in:
parent
5d871673be
commit
89acbb933b
6 changed files with 668 additions and 298 deletions
|
@ -1,176 +1,242 @@
|
||||||
class App.UiElement.postmaster_match
|
class App.UiElement.postmaster_match
|
||||||
@render: (attribute, params, form_controller) ->
|
@defaults: ->
|
||||||
addItem = (key, displayName, el, defaultValue = '') =>
|
groups =
|
||||||
add = { name: key, display: displayName, tag: 'input', null: false, default: defaultValue }
|
general:
|
||||||
itemInput = $( form_controller.formGenItem( add ).append('<svg class="icon icon-diagonal-cross remove"><use xlink:href="#icon-diagonal-cross"></use></svg>' ) )
|
name: 'Basic Settings'
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
value: 'from'
|
||||||
|
name: 'From'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'to'
|
||||||
|
name: 'To'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'cc'
|
||||||
|
name: 'Cc'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'x-any-recipient'
|
||||||
|
name: 'Any Recipient'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'subject'
|
||||||
|
name: 'Subject'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'body'
|
||||||
|
name: 'Body'
|
||||||
|
},
|
||||||
|
]
|
||||||
|
expert:
|
||||||
|
name: 'Expert Settings'
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
value: 'x-spam-flag'
|
||||||
|
name: 'X-Spam-Flag'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'x-spam-level'
|
||||||
|
name: 'X-Spam-Level'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'x-spam-score'
|
||||||
|
name: 'X-Spam-Score'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'x-spam-status'
|
||||||
|
name: 'X-Spam-Status'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'importance'
|
||||||
|
name: 'Importance'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'x-priority'
|
||||||
|
name: 'X-Priority'
|
||||||
|
},
|
||||||
|
|
||||||
# remove on click
|
{
|
||||||
itemInput.find('.remove').bind('click', (e) ->
|
value: 'organization'
|
||||||
e.preventDefault()
|
name: 'Organization'
|
||||||
key = $(e.target).closest('.form-group').find('select, input').attr('name')
|
},
|
||||||
return if !key
|
|
||||||
$(e.target).closest('.controls').find('.addSelection select option[value="' + key + '"]').show()
|
|
||||||
$(e.target).closest('.controls').find('.addSelection select option[value="' + key + '"]').prop('disabled', false)
|
|
||||||
$(e.target).closest('.form-group').remove()
|
|
||||||
)
|
|
||||||
|
|
||||||
# add new item
|
{
|
||||||
control = el.closest('.postmaster_match')
|
value: 'x-original-to'
|
||||||
control.find('.list').append(itemInput)
|
name: 'X-Original-To'
|
||||||
control.find('.addSelection select').val('')
|
},
|
||||||
control.find('.addSelection select option[value="' + key + '"]').prop('disabled', true)
|
{
|
||||||
control.find('.addSelection select option[value="' + key + '"]').hide()
|
value: 'delivered-to'
|
||||||
|
name: 'Delivered-To'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'envelope-to'
|
||||||
|
name: 'Envelope-To'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'return-path'
|
||||||
|
name: 'Return-Path'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'mailing-list'
|
||||||
|
name: 'Mailing-List'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'list-id'
|
||||||
|
name: 'List-Id'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'list-archive'
|
||||||
|
name: 'List-Archive'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'mailing-list'
|
||||||
|
name: 'Mailing-List'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'auto-submitted'
|
||||||
|
name: 'Auto-Submitted'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'x-loop'
|
||||||
|
name: 'X-Loop'
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
groups
|
||||||
|
|
||||||
|
@render: (attribute, params = {}) ->
|
||||||
|
|
||||||
|
groups = @defaults()
|
||||||
|
|
||||||
|
selector = @buildAttributeSelector(groups, attribute)
|
||||||
|
|
||||||
# scaffold of match elements
|
# scaffold of match elements
|
||||||
item = $('
|
item = $( App.view('generic/postmaster_match')( attribute: attribute ) )
|
||||||
<div class="postmaster_match">
|
item.find('.js-attributeSelector').prepend(selector)
|
||||||
<hr>
|
|
||||||
<div class="list"></div>
|
|
||||||
<hr>
|
|
||||||
<div>
|
|
||||||
<div class="addSelection"></div>
|
|
||||||
<svg class="icon icon-plus add"><use xlink:href="#icon-plus"></use></svg>
|
|
||||||
</div>
|
|
||||||
</div>')
|
|
||||||
|
|
||||||
# select shown attributes
|
# add filter
|
||||||
loopData = [
|
item.find('.js-add').bind('click', (e) =>
|
||||||
{
|
element = $(e.target).closest('.js-filterElement')
|
||||||
value: 'from'
|
elementClone = element.clone(true)
|
||||||
name: 'From'
|
element.after(elementClone)
|
||||||
},
|
elementClone.find('.js-attributeSelector select').trigger('change')
|
||||||
{
|
|
||||||
value: 'to'
|
|
||||||
name: 'To'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'cc'
|
|
||||||
name: 'Cc'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'subject'
|
|
||||||
name: 'Subject'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'body'
|
|
||||||
name: 'Body'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: ''
|
|
||||||
name: '-'
|
|
||||||
disable: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'x-any-recipient'
|
|
||||||
name: 'Any Recipient'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: ''
|
|
||||||
name: '-'
|
|
||||||
disable: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: ''
|
|
||||||
name: '- ' + App.i18n.translateInline('expert settings') + ' -'
|
|
||||||
disable: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: ''
|
|
||||||
name: '-'
|
|
||||||
disable: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'x-spam-flag'
|
|
||||||
name: 'X-Spam-Flag'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'x-spam-level'
|
|
||||||
name: 'X-Spam-Level'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'x-spam-score'
|
|
||||||
name: 'X-Spam-Score'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'x-spam-status'
|
|
||||||
name: 'X-Spam-Status'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'importance'
|
|
||||||
name: 'Importance'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'x-priority'
|
|
||||||
name: 'X-Priority'
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
value: 'organization'
|
|
||||||
name: 'Organization'
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
value: 'x-original-to'
|
|
||||||
name: 'X-Original-To'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'delivered-to'
|
|
||||||
name: 'Delivered-To'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'envelope-to'
|
|
||||||
name: 'Envelope-To'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'return-path'
|
|
||||||
name: 'Return-Path'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'mailing-list'
|
|
||||||
name: 'Mailing-List'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'list-id'
|
|
||||||
name: 'List-Id'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'list-archive'
|
|
||||||
name: 'List-Archive'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'mailing-list'
|
|
||||||
name: 'Mailing-List'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'auto-submitted'
|
|
||||||
name: 'Auto-Submitted'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'x-loop'
|
|
||||||
name: 'X-Loop'
|
|
||||||
},
|
|
||||||
]
|
|
||||||
for listItem in loopData
|
|
||||||
listItem.value = "#{ attribute.name }::#{listItem.value}"
|
|
||||||
add = { name: '', display: '', tag: 'select', multiple: false, null: false, nulloption: true, options: loopData, translate: true, required: false }
|
|
||||||
item.find('.addSelection').append( form_controller.formGenItem( add ) )
|
|
||||||
|
|
||||||
# bind add click
|
|
||||||
item.find('.add').bind('click', (e) ->
|
|
||||||
e.preventDefault()
|
|
||||||
name = $(@).closest('.controls').find('.addSelection').find('select').val()
|
|
||||||
displayName = $(@).closest('.controls').find('.addSelection').find('select option:selected').html()
|
|
||||||
return if !name
|
|
||||||
addItem( name, displayName, $(@) )
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# show default values
|
# remove filter
|
||||||
loopDataValue = {}
|
item.find('.js-remove').bind('click', (e) =>
|
||||||
if attribute.value
|
$(e.target).closest('.js-filterElement').remove()
|
||||||
for key, value of attribute.value
|
@rebuildAttributeSelectors(item)
|
||||||
displayName = key
|
)
|
||||||
for listItem in loopData
|
|
||||||
if listItem.value is "#{ attribute.name }::#{key}"
|
# change attribute selector
|
||||||
addItem( "#{ attribute.name }::#{key}", listItem.name, item.find('.add'), value )
|
item.find('.js-attributeSelector select').bind('change', (e) =>
|
||||||
|
key = $(e.target).find('option:selected').attr('value')
|
||||||
|
elementRow = $(e.target).closest('.js-filterElement')
|
||||||
|
|
||||||
|
@rebuildAttributeSelectors(item, elementRow, key, attribute)
|
||||||
|
@rebuildOperater(item, elementRow, key, groups, undefined, attribute)
|
||||||
|
@buildValue(item, elementRow, key, groups, undefined, undefined, attribute)
|
||||||
|
)
|
||||||
|
|
||||||
|
# change operator
|
||||||
|
item.find('.js-operator select').bind('change', (e) =>
|
||||||
|
key = $(e.target).find('.js-attributeSelector option:selected').attr('value')
|
||||||
|
operator = $(e.target).find('option:selected').attr('value')
|
||||||
|
elementRow = $(e.target).closest('.js-filterElement')
|
||||||
|
@buildValue(item, elementRow, key, groups, undefined, operator, attribute)
|
||||||
|
)
|
||||||
|
|
||||||
|
# build inital params
|
||||||
|
if !_.isEmpty(params[attribute.name])
|
||||||
|
|
||||||
|
selectorExists = false
|
||||||
|
for key, meta of params[attribute.name]
|
||||||
|
selectorExists = true
|
||||||
|
operator = meta.operator
|
||||||
|
value = meta.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, key, attribute)
|
||||||
|
@rebuildOperater(item, elementClone, key, groups, operator, attribute)
|
||||||
|
@buildValue(item, elementClone, key, groups, value, operator, attribute)
|
||||||
|
elementLast.after(elementClone)
|
||||||
|
|
||||||
|
# remove first dummy row
|
||||||
|
if selectorExists
|
||||||
|
item.find('.js-filterElement').first().remove()
|
||||||
|
|
||||||
item
|
item
|
||||||
|
|
||||||
|
@buildValue: (elementFull, elementRow, key, groups, value, operator, attribute) ->
|
||||||
|
|
||||||
|
# do nothing if item already exists
|
||||||
|
name = "#{attribute.name}::#{key}::value"
|
||||||
|
return if elementRow.find("[name=\"#{name}\"]").get(0)
|
||||||
|
config =
|
||||||
|
name: name
|
||||||
|
tag: 'input'
|
||||||
|
type: 'text'
|
||||||
|
value: value
|
||||||
|
item = App.UiElement[config.tag].render(config, {})
|
||||||
|
elementRow.find('.js-value').html(item)
|
||||||
|
|
||||||
|
@buildAttributeSelector: (groups, attribute) ->
|
||||||
|
selection = $('<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 entry in groupMeta.options
|
||||||
|
displayName = App.i18n.translateInline(entry.name)
|
||||||
|
optgroup.append("<option value=\"#{entry.value}\">#{displayName}</option>")
|
||||||
|
selection
|
||||||
|
|
||||||
|
@rebuildAttributeSelectors: (elementFull, elementRow, key, attribute) ->
|
||||||
|
|
||||||
|
# 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)
|
||||||
|
)
|
||||||
|
|
||||||
|
# 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 key
|
||||||
|
elementRow.find('.js-attributeSelector select').val(key)
|
||||||
|
|
||||||
|
@buildOperator: (elementFull, elementRow, key, groups, current_operator, attribute) ->
|
||||||
|
selection = $("<select class=\"form-control\" name=\"#{attribute.name}::#{key}::operator\"></select>")
|
||||||
|
|
||||||
|
for operator in ['contains', 'contains not']
|
||||||
|
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, key, groups, current_operator, attribute) ->
|
||||||
|
return if !key
|
||||||
|
|
||||||
|
# do nothing if item already exists
|
||||||
|
name = "#{attribute.name}::#{key}::operator"
|
||||||
|
return if elementRow.find("[name=\"#{name}\"]").get(0)
|
||||||
|
|
||||||
|
# render new operator
|
||||||
|
operator = @buildOperator(elementFull, elementRow, key, groups, current_operator, attribute)
|
||||||
|
elementRow.find('.js-operator select').replaceWith(operator)
|
||||||
|
|
|
@ -1,124 +1,171 @@
|
||||||
class App.UiElement.postmaster_set
|
class App.UiElement.postmaster_set
|
||||||
@render: (attribute, params, form_controller) ->
|
@defaults: ->
|
||||||
addItem = (key, displayName, el, defaultValue = '') =>
|
groups =
|
||||||
collection = undefined
|
general:
|
||||||
for listItem in loopData
|
name: 'Ticket'
|
||||||
if listItem.value is key
|
options: [
|
||||||
collection = listItem
|
{
|
||||||
if collection.relation
|
value: 'x-zammad-ticket-priority_id'
|
||||||
add = { name: key, display: displayName, tag: 'select', multiple: false, null: false, nulloption: true, relation: collection.relation, translate: true, default: defaultValue }
|
name: 'Priority'
|
||||||
else if collection.options
|
relation: 'TicketPriority'
|
||||||
add = { name: key, display: displayName, tag: 'select', multiple: false, null: false, nulloption: true, options: collection.options, translate: true, default: defaultValue }
|
},
|
||||||
else
|
{
|
||||||
add = { name: key, display: displayName, tag: 'input', null: false, default: defaultValue }
|
value: 'x-zammad-ticket-state_id'
|
||||||
itemInput = $( form_controller.formGenItem( add ).append('<svg class="icon icon-diagonal-cross remove"><use xlink:href="#icon-diagonal-cross"></use></svg>' ) )
|
name: 'State'
|
||||||
|
relation: 'TicketState'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'x-zammad-ticket-customer'
|
||||||
|
name: 'Customer'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
|
||||||
# remove on click
|
value: 'x-zammad-ticket-group_id'
|
||||||
itemInput.find('.remove').bind('click', (e) ->
|
name: 'Group'
|
||||||
e.preventDefault()
|
relation: 'Group'
|
||||||
key = $(e.target).closest('.form-group').find('select, input').attr('name')
|
},
|
||||||
return if !key
|
{
|
||||||
$(e.target).closest('.controls').find('.addSelection select option[value="' + key + '"]').show()
|
value: 'x-zammad-ticket-owner'
|
||||||
$(e.target).closest('.controls').find('.addSelection select option[value="' + key + '"]').prop('disabled', false)
|
name: 'Owner'
|
||||||
$(e.target).closest('.form-group').remove()
|
relation: 'User',
|
||||||
)
|
tag: 'user_autocompletion',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'x-zammad-ignore'
|
||||||
|
name: 'Ignore Message'
|
||||||
|
options: { true: 'Yes', false: 'No'}
|
||||||
|
},
|
||||||
|
]
|
||||||
|
expert:
|
||||||
|
name: 'Article'
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
value: 'x-zammad-article-internal'
|
||||||
|
name: 'Internal'
|
||||||
|
options: { true: 'Yes', false: 'No'}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'x-zammad-article-type_id'
|
||||||
|
name: 'Type'
|
||||||
|
relation: 'TicketArticleType'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'x-zammad-article-sender_id'
|
||||||
|
name: 'Sender'
|
||||||
|
relation: 'TicketArticleSender'
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
# add new item
|
groups
|
||||||
control = el.closest('.perform_set')
|
|
||||||
control.find('.list').append(itemInput)
|
|
||||||
control.find('.addSelection select').val('')
|
|
||||||
control.find('.addSelection select option[value="' + key + '"]').prop('disabled', true)
|
|
||||||
control.find('.addSelection select option[value="' + key + '"]').hide()
|
|
||||||
|
|
||||||
# scaffold of perform elements
|
@render: (attribute, params = {}) ->
|
||||||
item = $('
|
|
||||||
<div class="perform_set">
|
|
||||||
<hr>
|
|
||||||
<div class="list"></div>
|
|
||||||
<hr>
|
|
||||||
<div>
|
|
||||||
<div class="addSelection"></div>
|
|
||||||
<svg class="icon icon-plus add"><use xlink:href="#icon-plus"></use></svg>
|
|
||||||
</div>
|
|
||||||
</div>')
|
|
||||||
|
|
||||||
|
groups = @defaults()
|
||||||
|
|
||||||
# select shown attributes
|
selector = @buildAttributeSelector(groups, attribute)
|
||||||
loopData = [
|
|
||||||
{
|
|
||||||
value: 'x-zammad-ticket-priority_id'
|
|
||||||
name: 'Ticket Priority'
|
|
||||||
relation: 'TicketPriority'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'x-zammad-ticket-state_id'
|
|
||||||
name: 'Ticket State'
|
|
||||||
relation: 'TicketState'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'x-zammad-ticket-customer'
|
|
||||||
name: 'Ticket Customer'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'x-zammad-ticket-group_id'
|
|
||||||
name: 'Ticket Group'
|
|
||||||
relation: 'Group'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'x-zammad-ticket-owner'
|
|
||||||
name: 'Ticket Owner'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: ''
|
|
||||||
name: '-'
|
|
||||||
disable: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'x-zammad-article-internal'
|
|
||||||
name: 'Article Internal'
|
|
||||||
options: { true: 'Yes', false: 'No'}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'x-zammad-article-type_id'
|
|
||||||
name: 'Article Type'
|
|
||||||
relation: 'TicketArticleType'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'x-zammad-article-sender_id'
|
|
||||||
name: 'Article Sender'
|
|
||||||
relation: 'TicketArticleSender'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: ''
|
|
||||||
name: '-'
|
|
||||||
disable: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'x-zammad-ignore'
|
|
||||||
name: 'Ignore Message'
|
|
||||||
options: { true: 'Yes', false: 'No'}
|
|
||||||
},
|
|
||||||
]
|
|
||||||
for listItem in loopData
|
|
||||||
listItem.value = "#{ attribute.name }::#{listItem.value}"
|
|
||||||
add = { name: '', display: '', tag: 'select', multiple: false, null: false, nulloption: true, options: loopData, translate: true, required: false }
|
|
||||||
item.find('.addSelection').append( form_controller.formGenItem( add ) )
|
|
||||||
|
|
||||||
item.find('.add').bind('click', (e) ->
|
# scaffold of match elements
|
||||||
e.preventDefault()
|
item = $( App.view('generic/postmaster_set')( attribute: attribute ) )
|
||||||
name = $(@).closest('.controls').find('.addSelection').find('select').val()
|
item.find('.js-attributeSelector').prepend(selector)
|
||||||
displayName = $(@).closest('.controls').find('.addSelection').find('select option:selected').html()
|
|
||||||
return if !name
|
# add filter
|
||||||
addItem( name, displayName, $(@) )
|
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')
|
||||||
)
|
)
|
||||||
|
|
||||||
# show default values
|
# remove filter
|
||||||
loopDataValue = {}
|
item.find('.js-remove').bind('click', (e) =>
|
||||||
if attribute.value
|
$(e.target).closest('.js-filterElement').remove()
|
||||||
for key, value of attribute.value
|
@rebuildAttributeSelectors(item)
|
||||||
displayName = key
|
)
|
||||||
for listItem in loopData
|
|
||||||
if listItem.value is "#{ attribute.name }::#{key}"
|
# change attribute selector
|
||||||
addItem( "#{ attribute.name }::#{key}", listItem.name, item.find('.add'), value )
|
item.find('.js-attributeSelector select').bind('change', (e) =>
|
||||||
|
key = $(e.target).find('option:selected').attr('value')
|
||||||
|
elementRow = $(e.target).closest('.js-filterElement')
|
||||||
|
|
||||||
|
@rebuildAttributeSelectors(item, elementRow, key, attribute)
|
||||||
|
@buildValue(item, elementRow, key, groups, undefined, undefined, attribute)
|
||||||
|
)
|
||||||
|
|
||||||
|
# build inital params
|
||||||
|
if !_.isEmpty(params[attribute.name])
|
||||||
|
|
||||||
|
selectorExists = false
|
||||||
|
for key, meta of params[attribute.name]
|
||||||
|
selectorExists = true
|
||||||
|
operator = meta.operator
|
||||||
|
value = meta.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, key, attribute)
|
||||||
|
@buildValue(item, elementClone, key, groups, value, operator, attribute)
|
||||||
|
elementLast.after(elementClone)
|
||||||
|
|
||||||
|
# remove first dummy row
|
||||||
|
if selectorExists
|
||||||
|
item.find('.js-filterElement').first().remove()
|
||||||
|
|
||||||
|
item
|
||||||
|
|
||||||
|
@buildValue: (elementFull, elementRow, key, groups, value, operator, attribute) ->
|
||||||
|
|
||||||
|
# do nothing if item already exists
|
||||||
|
name = "#{attribute.name}::#{key}::value"
|
||||||
|
return if elementRow.find("[name=\"#{name}\"]").get(0)
|
||||||
|
config = {}
|
||||||
|
for groupName, meta of groups
|
||||||
|
for entry in meta.options
|
||||||
|
if entry.value is key
|
||||||
|
config = entry
|
||||||
|
if !config.tag
|
||||||
|
if config.relation || config.options
|
||||||
|
config['tag'] = 'select'
|
||||||
|
else
|
||||||
|
config['tag'] = 'input'
|
||||||
|
config['type'] = 'text'
|
||||||
|
config['name'] = name
|
||||||
|
config['value'] = value
|
||||||
|
item = App.UiElement[config.tag].render(config, {})
|
||||||
|
elementRow.find('.js-value').html(item)
|
||||||
|
|
||||||
|
@buildAttributeSelector: (groups, attribute) ->
|
||||||
|
selection = $('<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 entry in groupMeta.options
|
||||||
|
displayName = App.i18n.translateInline(entry.name)
|
||||||
|
optgroup.append("<option value=\"#{entry.value}\">#{displayName}</option>")
|
||||||
|
selection
|
||||||
|
|
||||||
|
@rebuildAttributeSelectors: (elementFull, elementRow, key, attribute) ->
|
||||||
|
|
||||||
|
# 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)
|
||||||
|
)
|
||||||
|
|
||||||
|
# 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 key
|
||||||
|
elementRow.find('.js-attributeSelector select').val(key)
|
||||||
|
|
||||||
item
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
<div class="horizontal-filters">
|
||||||
|
<div class="horizontal-filter js-filterElement">
|
||||||
|
<div class="horizontal-filter-body">
|
||||||
|
<div class="controls">
|
||||||
|
<div class="u-positionOrigin js-attributeSelector">
|
||||||
|
<%- @Icon('arrow-down', 'dropdown-arrow') %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="controls">
|
||||||
|
<div class="u-positionOrigin js-operator">
|
||||||
|
<select></select>
|
||||||
|
<%- @Icon('arrow-down', 'dropdown-arrow') %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="controls js-value"></div>
|
||||||
|
</div>
|
||||||
|
<div class="filter-controls">
|
||||||
|
<div class="filter-control filter-control-remove js-remove" title="<%- @T('Remove rule') %>">
|
||||||
|
<%- @Icon('minus') %>
|
||||||
|
</div>
|
||||||
|
<div class="filter-control filter-control-add js-add" title="<%- @T('Add new rule') %>">
|
||||||
|
<%- @Icon('plus') %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
|
@ -0,0 +1,20 @@
|
||||||
|
<div class="horizontal-filters">
|
||||||
|
<div class="horizontal-filter js-filterElement">
|
||||||
|
<div class="horizontal-filter-body">
|
||||||
|
<div class="controls">
|
||||||
|
<div class="u-positionOrigin js-attributeSelector">
|
||||||
|
<%- @Icon('arrow-down', 'dropdown-arrow') %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="controls js-value"></div>
|
||||||
|
</div>
|
||||||
|
<div class="filter-controls">
|
||||||
|
<div class="filter-control filter-control-remove js-remove" title="<%- @T('Remove rule') %>">
|
||||||
|
<%- @Icon('minus') %>
|
||||||
|
</div>
|
||||||
|
<div class="filter-control filter-control-add js-add" title="<%- @T('Add new rule') %>">
|
||||||
|
<%- @Icon('plus') %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
|
@ -6,38 +6,44 @@ module Channel::Filter::Database
|
||||||
def self.run( _channel, mail )
|
def self.run( _channel, mail )
|
||||||
|
|
||||||
# process postmaster filter
|
# process postmaster filter
|
||||||
filters = PostmasterFilter.where( active: true, channel: 'email' )
|
filters = PostmasterFilter.where( active: true, channel: 'email' ).order(:name)
|
||||||
filters.each {|filter|
|
filters.each {|filter|
|
||||||
Rails.logger.info " proccess filter #{filter.name} ..."
|
Rails.logger.info " proccess filter #{filter.name} ..."
|
||||||
match = true
|
all_matches_ok = true
|
||||||
looped = false
|
min_one_rule_exists = false
|
||||||
filter[:match].each {|key, value|
|
filter[:match].each {|key, meta|
|
||||||
looped = true
|
|
||||||
begin
|
begin
|
||||||
|
next if !meta || !meta['value'] || meta['value'].empty?
|
||||||
|
min_one_rule_exists = true
|
||||||
scan = []
|
scan = []
|
||||||
if mail
|
if mail
|
||||||
scan = mail[ key.downcase.to_sym ].scan(/#{value}/i)
|
scan = mail[ key.downcase.to_sym ].scan(/#{meta['value']}/i)
|
||||||
end
|
end
|
||||||
if match && scan[0]
|
if scan[0]
|
||||||
Rails.logger.info " matching #{key.downcase}:'#{mail[ key.downcase.to_sym ]}' on #{value}"
|
if meta[:operator] == 'contains not'
|
||||||
match = true
|
all_matches_ok = false
|
||||||
|
end
|
||||||
|
Rails.logger.info " matching #{key.downcase}:'#{mail[ key.downcase.to_sym ]}' on #{meta['value']}"
|
||||||
else
|
else
|
||||||
Rails.logger.info " is not matching #{key.downcase}:'#{mail[ key.downcase.to_sym ]}' on #{value}"
|
if meta[:operator] == 'contains'
|
||||||
match = false
|
all_matches_ok = false
|
||||||
|
end
|
||||||
|
Rails.logger.info " not matching #{key.downcase}:'#{mail[ key.downcase.to_sym ]}' on #{meta['value']}"
|
||||||
end
|
end
|
||||||
|
break if !all_matches_ok
|
||||||
rescue => e
|
rescue => e
|
||||||
match = false
|
all_matches_ok = false
|
||||||
Rails.logger.error "can't use match rule #{value} on #{mail[ key.to_sym ]}"
|
Rails.logger.error "can't use match rule #{meta['value']} on #{mail[ key.to_sym ]}"
|
||||||
Rails.logger.error e.inspect
|
Rails.logger.error e.inspect
|
||||||
end
|
end
|
||||||
}
|
}
|
||||||
|
|
||||||
next if !looped
|
next if !min_one_rule_exists
|
||||||
next if !match
|
next if !all_matches_ok
|
||||||
|
|
||||||
filter[:perform].each {|key, value|
|
filter[:perform].each {|key, meta|
|
||||||
Rails.logger.info " perform '#{key.downcase}' = '#{value}'"
|
Rails.logger.info " perform '#{key.downcase}' = '#{meta.inspect}'"
|
||||||
mail[ key.downcase.to_sym ] = value
|
mail[ key.downcase.to_sym ] = meta['value']
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
205
test/unit/email_postmaster_test.rb
Normal file
205
test/unit/email_postmaster_test.rb
Normal file
|
@ -0,0 +1,205 @@
|
||||||
|
# encoding: utf-8
|
||||||
|
# rubocop:disable all
|
||||||
|
require 'test_helper'
|
||||||
|
|
||||||
|
class EmailPostmasterTest < ActiveSupport::TestCase
|
||||||
|
test 'process with postmaster filter' do
|
||||||
|
group1 = Group.create_if_not_exists(
|
||||||
|
name: 'Test Group1',
|
||||||
|
created_by_id: 1,
|
||||||
|
updated_by_id: 1,
|
||||||
|
)
|
||||||
|
group2 = Group.create_if_not_exists(
|
||||||
|
name: 'Test Group2',
|
||||||
|
created_by_id: 1,
|
||||||
|
updated_by_id: 1,
|
||||||
|
)
|
||||||
|
PostmasterFilter.destroy_all
|
||||||
|
PostmasterFilter.create(
|
||||||
|
name: 'not used',
|
||||||
|
match: {
|
||||||
|
from: {
|
||||||
|
operator: 'contains',
|
||||||
|
value: 'nobody@example.com',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
perform: {
|
||||||
|
'X-Zammad-Ticket-priority' => {
|
||||||
|
value: '3 high',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
channel: 'email',
|
||||||
|
active: true,
|
||||||
|
created_by_id: 1,
|
||||||
|
updated_by_id: 1,
|
||||||
|
)
|
||||||
|
PostmasterFilter.create(
|
||||||
|
name: 'used',
|
||||||
|
match: {
|
||||||
|
from: {
|
||||||
|
operator: 'contains',
|
||||||
|
value: 'me@example.com',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
perform: {
|
||||||
|
'X-Zammad-Ticket-group_id' => {
|
||||||
|
value: group1.id,
|
||||||
|
},
|
||||||
|
'x-Zammad-Article-Internal' => {
|
||||||
|
value: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
channel: 'email',
|
||||||
|
active: true,
|
||||||
|
created_by_id: 1,
|
||||||
|
updated_by_id: 1,
|
||||||
|
)
|
||||||
|
PostmasterFilter.create(
|
||||||
|
name: 'used x-any-recipient',
|
||||||
|
match: {
|
||||||
|
'x-any-recipient' => {
|
||||||
|
operator: 'contains',
|
||||||
|
value: 'any@example.com',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
perform: {
|
||||||
|
'X-Zammad-Ticket-group_id' => {
|
||||||
|
value: group2.id,
|
||||||
|
},
|
||||||
|
'x-Zammad-Article-Internal' => {
|
||||||
|
value: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
channel: 'email',
|
||||||
|
active: true,
|
||||||
|
created_by_id: 1,
|
||||||
|
updated_by_id: 1,
|
||||||
|
)
|
||||||
|
|
||||||
|
data = 'From: me@example.com
|
||||||
|
To: customer@example.com
|
||||||
|
Subject: some subject
|
||||||
|
|
||||||
|
Some Text'
|
||||||
|
|
||||||
|
parser = Channel::EmailParser.new
|
||||||
|
ticket, article, user = parser.process( { trusted: false }, data )
|
||||||
|
assert_equal('Test Group1', ticket.group.name)
|
||||||
|
assert_equal('2 normal', ticket.priority.name)
|
||||||
|
assert_equal('some subject', ticket.title)
|
||||||
|
|
||||||
|
assert_equal('Customer', article.sender.name)
|
||||||
|
assert_equal('email', article.type.name)
|
||||||
|
assert_equal(true, article.internal)
|
||||||
|
|
||||||
|
data = 'From: Some Body <somebody@example.com>
|
||||||
|
To: Bob <bod@example.com>
|
||||||
|
Cc: any@example.com
|
||||||
|
Subject: some subject
|
||||||
|
|
||||||
|
Some Text'
|
||||||
|
|
||||||
|
parser = Channel::EmailParser.new
|
||||||
|
ticket, article, user = parser.process( { trusted: false }, data )
|
||||||
|
|
||||||
|
assert_equal('Test Group2', ticket.group.name)
|
||||||
|
assert_equal('2 normal', ticket.priority.name)
|
||||||
|
assert_equal('some subject', ticket.title)
|
||||||
|
|
||||||
|
assert_equal('Customer', article.sender.name)
|
||||||
|
assert_equal('email', article.type.name)
|
||||||
|
assert_equal(true, article.internal)
|
||||||
|
|
||||||
|
|
||||||
|
PostmasterFilter.create(
|
||||||
|
name: 'used x-any-recipient',
|
||||||
|
match: {
|
||||||
|
'x-any-recipient' => {
|
||||||
|
operator: 'contains not',
|
||||||
|
value: 'any_not@example.com',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
perform: {
|
||||||
|
'X-Zammad-Ticket-group_id' => {
|
||||||
|
value: group2.id,
|
||||||
|
},
|
||||||
|
'X-Zammad-Ticket-priority_id' => {
|
||||||
|
value: '1',
|
||||||
|
},
|
||||||
|
'x-Zammad-Article-Internal' => {
|
||||||
|
value: 'false',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
channel: 'email',
|
||||||
|
active: true,
|
||||||
|
created_by_id: 1,
|
||||||
|
updated_by_id: 1,
|
||||||
|
)
|
||||||
|
|
||||||
|
data = 'From: Some Body <somebody@example.com>
|
||||||
|
To: Bob <bod@example.com>
|
||||||
|
Cc: any@example.com
|
||||||
|
Subject: some subject2
|
||||||
|
|
||||||
|
Some Text'
|
||||||
|
|
||||||
|
parser = Channel::EmailParser.new
|
||||||
|
ticket, article, user = parser.process( { trusted: false }, data )
|
||||||
|
|
||||||
|
assert_equal('Test Group2', ticket.group.name)
|
||||||
|
assert_equal('1 low', ticket.priority.name)
|
||||||
|
assert_equal('some subject2', ticket.title)
|
||||||
|
|
||||||
|
assert_equal('Customer', article.sender.name)
|
||||||
|
assert_equal('email', article.type.name)
|
||||||
|
assert_equal(false, article.internal)
|
||||||
|
|
||||||
|
PostmasterFilter.destroy_all
|
||||||
|
|
||||||
|
PostmasterFilter.create(
|
||||||
|
name: 'used - empty selector',
|
||||||
|
match: {
|
||||||
|
from: {
|
||||||
|
operator: 'contains',
|
||||||
|
value: '',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
perform: {
|
||||||
|
'X-Zammad-Ticket-group_id' => {
|
||||||
|
value: group2.id,
|
||||||
|
},
|
||||||
|
'X-Zammad-Ticket-priority_id' => {
|
||||||
|
value: '1',
|
||||||
|
},
|
||||||
|
'x-Zammad-Article-Internal' => {
|
||||||
|
value: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
channel: 'email',
|
||||||
|
active: true,
|
||||||
|
created_by_id: 1,
|
||||||
|
updated_by_id: 1,
|
||||||
|
)
|
||||||
|
|
||||||
|
data = 'From: Some Body <somebody@example.com>
|
||||||
|
To: Bob <bod@example.com>
|
||||||
|
Cc: any@example.com
|
||||||
|
Subject: some subject - no selector
|
||||||
|
|
||||||
|
Some Text'
|
||||||
|
|
||||||
|
parser = Channel::EmailParser.new
|
||||||
|
ticket, article, user = parser.process( { trusted: false }, data )
|
||||||
|
|
||||||
|
assert_equal('Users', ticket.group.name)
|
||||||
|
assert_equal('2 normal', ticket.priority.name)
|
||||||
|
assert_equal('some subject - no selector', ticket.title)
|
||||||
|
|
||||||
|
assert_equal('Customer', article.sender.name)
|
||||||
|
assert_equal('email', article.type.name)
|
||||||
|
assert_equal(false, article.internal)
|
||||||
|
|
||||||
|
PostmasterFilter.destroy_all
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
Loading…
Reference in a new issue