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
|
||||
@render: (attribute, params, form_controller) ->
|
||||
addItem = (key, displayName, el, defaultValue = '') =>
|
||||
add = { name: key, display: displayName, tag: 'input', null: false, default: defaultValue }
|
||||
itemInput = $( form_controller.formGenItem( add ).append('<svg class="icon icon-diagonal-cross remove"><use xlink:href="#icon-diagonal-cross"></use></svg>' ) )
|
||||
@defaults: ->
|
||||
groups =
|
||||
general:
|
||||
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) ->
|
||||
e.preventDefault()
|
||||
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()
|
||||
)
|
||||
{
|
||||
value: 'organization'
|
||||
name: 'Organization'
|
||||
},
|
||||
|
||||
# add new item
|
||||
control = el.closest('.postmaster_match')
|
||||
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()
|
||||
{
|
||||
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'
|
||||
},
|
||||
]
|
||||
|
||||
groups
|
||||
|
||||
@render: (attribute, params = {}) ->
|
||||
|
||||
groups = @defaults()
|
||||
|
||||
selector = @buildAttributeSelector(groups, attribute)
|
||||
|
||||
# scaffold of match elements
|
||||
item = $('
|
||||
<div class="postmaster_match">
|
||||
<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>')
|
||||
item = $( App.view('generic/postmaster_match')( attribute: attribute ) )
|
||||
item.find('.js-attributeSelector').prepend(selector)
|
||||
|
||||
# select shown attributes
|
||||
loopData = [
|
||||
{
|
||||
value: 'from'
|
||||
name: 'From'
|
||||
},
|
||||
{
|
||||
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, $(@) )
|
||||
# add filter
|
||||
item.find('.js-add').bind('click', (e) =>
|
||||
element = $(e.target).closest('.js-filterElement')
|
||||
elementClone = element.clone(true)
|
||||
element.after(elementClone)
|
||||
elementClone.find('.js-attributeSelector select').trigger('change')
|
||||
)
|
||||
|
||||
# show default values
|
||||
loopDataValue = {}
|
||||
if attribute.value
|
||||
for key, value of attribute.value
|
||||
displayName = key
|
||||
for listItem in loopData
|
||||
if listItem.value is "#{ attribute.name }::#{key}"
|
||||
addItem( "#{ attribute.name }::#{key}", listItem.name, item.find('.add'), value )
|
||||
# remove filter
|
||||
item.find('.js-remove').bind('click', (e) =>
|
||||
$(e.target).closest('.js-filterElement').remove()
|
||||
@rebuildAttributeSelectors(item)
|
||||
)
|
||||
|
||||
# change attribute selector
|
||||
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
|
||||
|
||||
@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
|
||||
@render: (attribute, params, form_controller) ->
|
||||
addItem = (key, displayName, el, defaultValue = '') =>
|
||||
collection = undefined
|
||||
for listItem in loopData
|
||||
if listItem.value is key
|
||||
collection = listItem
|
||||
if collection.relation
|
||||
add = { name: key, display: displayName, tag: 'select', multiple: false, null: false, nulloption: true, relation: collection.relation, translate: true, default: defaultValue }
|
||||
else if collection.options
|
||||
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 }
|
||||
itemInput = $( form_controller.formGenItem( add ).append('<svg class="icon icon-diagonal-cross remove"><use xlink:href="#icon-diagonal-cross"></use></svg>' ) )
|
||||
@defaults: ->
|
||||
groups =
|
||||
general:
|
||||
name: 'Ticket'
|
||||
options: [
|
||||
{
|
||||
value: 'x-zammad-ticket-priority_id'
|
||||
name: 'Priority'
|
||||
relation: 'TicketPriority'
|
||||
},
|
||||
{
|
||||
value: 'x-zammad-ticket-state_id'
|
||||
name: 'State'
|
||||
relation: 'TicketState'
|
||||
},
|
||||
{
|
||||
value: 'x-zammad-ticket-customer'
|
||||
name: 'Customer'
|
||||
},
|
||||
{
|
||||
|
||||
# remove on click
|
||||
itemInput.find('.remove').bind('click', (e) ->
|
||||
e.preventDefault()
|
||||
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()
|
||||
)
|
||||
value: 'x-zammad-ticket-group_id'
|
||||
name: 'Group'
|
||||
relation: 'Group'
|
||||
},
|
||||
{
|
||||
value: 'x-zammad-ticket-owner'
|
||||
name: 'Owner'
|
||||
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
|
||||
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()
|
||||
groups
|
||||
|
||||
# scaffold of perform elements
|
||||
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>')
|
||||
@render: (attribute, params = {}) ->
|
||||
|
||||
groups = @defaults()
|
||||
|
||||
# select shown attributes
|
||||
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 ) )
|
||||
selector = @buildAttributeSelector(groups, attribute)
|
||||
|
||||
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, $(@) )
|
||||
# scaffold of match elements
|
||||
item = $( App.view('generic/postmaster_set')( attribute: attribute ) )
|
||||
item.find('.js-attributeSelector').prepend(selector)
|
||||
|
||||
# add filter
|
||||
item.find('.js-add').bind('click', (e) =>
|
||||
element = $(e.target).closest('.js-filterElement')
|
||||
elementClone = element.clone(true)
|
||||
element.after(elementClone)
|
||||
elementClone.find('.js-attributeSelector select').trigger('change')
|
||||
)
|
||||
|
||||
# show default values
|
||||
loopDataValue = {}
|
||||
if attribute.value
|
||||
for key, value of attribute.value
|
||||
displayName = key
|
||||
for listItem in loopData
|
||||
if listItem.value is "#{ attribute.name }::#{key}"
|
||||
addItem( "#{ attribute.name }::#{key}", listItem.name, item.find('.add'), value )
|
||||
# remove filter
|
||||
item.find('.js-remove').bind('click', (e) =>
|
||||
$(e.target).closest('.js-filterElement').remove()
|
||||
@rebuildAttributeSelectors(item)
|
||||
)
|
||||
|
||||
# change attribute selector
|
||||
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)
|
||||
|
||||
|
|
|
@ -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 )
|
||||
|
||||
# process postmaster filter
|
||||
filters = PostmasterFilter.where( active: true, channel: 'email' )
|
||||
filters = PostmasterFilter.where( active: true, channel: 'email' ).order(:name)
|
||||
filters.each {|filter|
|
||||
Rails.logger.info " proccess filter #{filter.name} ..."
|
||||
match = true
|
||||
looped = false
|
||||
filter[:match].each {|key, value|
|
||||
looped = true
|
||||
all_matches_ok = true
|
||||
min_one_rule_exists = false
|
||||
filter[:match].each {|key, meta|
|
||||
begin
|
||||
next if !meta || !meta['value'] || meta['value'].empty?
|
||||
min_one_rule_exists = true
|
||||
scan = []
|
||||
if mail
|
||||
scan = mail[ key.downcase.to_sym ].scan(/#{value}/i)
|
||||
scan = mail[ key.downcase.to_sym ].scan(/#{meta['value']}/i)
|
||||
end
|
||||
if match && scan[0]
|
||||
Rails.logger.info " matching #{key.downcase}:'#{mail[ key.downcase.to_sym ]}' on #{value}"
|
||||
match = true
|
||||
if scan[0]
|
||||
if meta[:operator] == 'contains not'
|
||||
all_matches_ok = false
|
||||
end
|
||||
Rails.logger.info " matching #{key.downcase}:'#{mail[ key.downcase.to_sym ]}' on #{meta['value']}"
|
||||
else
|
||||
Rails.logger.info " is not matching #{key.downcase}:'#{mail[ key.downcase.to_sym ]}' on #{value}"
|
||||
match = false
|
||||
if meta[:operator] == 'contains'
|
||||
all_matches_ok = false
|
||||
end
|
||||
Rails.logger.info " not matching #{key.downcase}:'#{mail[ key.downcase.to_sym ]}' on #{meta['value']}"
|
||||
end
|
||||
break if !all_matches_ok
|
||||
rescue => e
|
||||
match = false
|
||||
Rails.logger.error "can't use match rule #{value} on #{mail[ key.to_sym ]}"
|
||||
all_matches_ok = false
|
||||
Rails.logger.error "can't use match rule #{meta['value']} on #{mail[ key.to_sym ]}"
|
||||
Rails.logger.error e.inspect
|
||||
end
|
||||
}
|
||||
|
||||
next if !looped
|
||||
next if !match
|
||||
next if !min_one_rule_exists
|
||||
next if !all_matches_ok
|
||||
|
||||
filter[:perform].each {|key, value|
|
||||
Rails.logger.info " perform '#{key.downcase}' = '#{value}'"
|
||||
mail[ key.downcase.to_sym ] = value
|
||||
filter[:perform].each {|key, meta|
|
||||
Rails.logger.info " perform '#{key.downcase}' = '#{meta.inspect}'"
|
||||
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