improve email filters by adding a tag option - closes #1991
This commit is contained in:
parent
db82816f87
commit
9f559276e4
18 changed files with 1050 additions and 26 deletions
|
@ -306,7 +306,7 @@ GEM
|
||||||
slop (~> 3.0)
|
slop (~> 3.0)
|
||||||
public_suffix (3.0.1)
|
public_suffix (3.0.1)
|
||||||
puma (3.11.0)
|
puma (3.11.0)
|
||||||
rack (2.0.4)
|
rack (2.0.5)
|
||||||
rack-livereload (0.3.16)
|
rack-livereload (0.3.16)
|
||||||
rack
|
rack
|
||||||
rack-test (1.0.0)
|
rack-test (1.0.0)
|
||||||
|
@ -405,7 +405,7 @@ GEM
|
||||||
simplecov (>= 0.4.1)
|
simplecov (>= 0.4.1)
|
||||||
slack-notifier (2.3.1)
|
slack-notifier (2.3.1)
|
||||||
slop (3.6.0)
|
slop (3.6.0)
|
||||||
sprockets (3.7.1)
|
sprockets (3.7.2)
|
||||||
concurrent-ruby (~> 1.0)
|
concurrent-ruby (~> 1.0)
|
||||||
rack (> 1, < 3)
|
rack (> 1, < 3)
|
||||||
sprockets-rails (3.2.1)
|
sprockets-rails (3.2.1)
|
||||||
|
|
|
@ -4,6 +4,7 @@ class App.UiElement.postmaster_set
|
||||||
groups =
|
groups =
|
||||||
ticket:
|
ticket:
|
||||||
name: 'Ticket'
|
name: 'Ticket'
|
||||||
|
model: 'Ticket'
|
||||||
options: [
|
options: [
|
||||||
{
|
{
|
||||||
value: 'priority_id'
|
value: 'priority_id'
|
||||||
|
@ -15,6 +16,11 @@ class App.UiElement.postmaster_set
|
||||||
name: 'State'
|
name: 'State'
|
||||||
relation: 'TicketState'
|
relation: 'TicketState'
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
value: 'tags'
|
||||||
|
name: 'Tag'
|
||||||
|
tag: 'tag'
|
||||||
|
}
|
||||||
{
|
{
|
||||||
value: 'customer_id'
|
value: 'customer_id'
|
||||||
name: 'Customer'
|
name: 'Customer'
|
||||||
|
@ -64,6 +70,24 @@ class App.UiElement.postmaster_set
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
elements = {}
|
||||||
|
for groupKey, groupMeta of groups
|
||||||
|
if !App[groupMeta.model]
|
||||||
|
elements["#{groupKey}.email"] = { name: 'email', display: 'Email' }
|
||||||
|
else
|
||||||
|
|
||||||
|
for row in App[groupMeta.model].configure_attributes
|
||||||
|
|
||||||
|
# ignore passwords and relations
|
||||||
|
if row.type isnt 'password' && row.name.substr(row.name.length-4,4) isnt '_ids'
|
||||||
|
|
||||||
|
# ignore readonly attributes
|
||||||
|
if !row.readonly
|
||||||
|
config = _.clone(row)
|
||||||
|
if config.tag is 'tag'
|
||||||
|
config.operator = ['add', 'remove']
|
||||||
|
elements["x-zammad-ticket-#{config.name}"] = config
|
||||||
|
|
||||||
# add additional ticket attributes
|
# add additional ticket attributes
|
||||||
for row in App.Ticket.configure_attributes
|
for row in App.Ticket.configure_attributes
|
||||||
exists = false
|
exists = false
|
||||||
|
@ -91,11 +115,11 @@ class App.UiElement.postmaster_set
|
||||||
for item in groups.ticket.options
|
for item in groups.ticket.options
|
||||||
item.value = "x-zammad-ticket-#{item.value}"
|
item.value = "x-zammad-ticket-#{item.value}"
|
||||||
|
|
||||||
groups
|
[elements, groups]
|
||||||
|
|
||||||
@render: (attribute, params = {}) ->
|
@render: (attribute, params = {}) ->
|
||||||
|
|
||||||
groups = @defaults()
|
[elements, groups] = @defaults()
|
||||||
|
|
||||||
selector = @buildAttributeSelector(groups, attribute)
|
selector = @buildAttributeSelector(groups, attribute)
|
||||||
|
|
||||||
|
@ -121,7 +145,9 @@ class App.UiElement.postmaster_set
|
||||||
item.find('.js-attributeSelector select').bind('change', (e) =>
|
item.find('.js-attributeSelector select').bind('change', (e) =>
|
||||||
key = $(e.target).find('option:selected').attr('value')
|
key = $(e.target).find('option:selected').attr('value')
|
||||||
elementRow = $(e.target).closest('.js-filterElement')
|
elementRow = $(e.target).closest('.js-filterElement')
|
||||||
|
groupAndAttribute = elementRow.find('.js-attributeSelector option:selected').attr('value')
|
||||||
@rebuildAttributeSelectors(item, elementRow, key, attribute)
|
@rebuildAttributeSelectors(item, elementRow, key, attribute)
|
||||||
|
@buildOperator(item, elementRow, groupAndAttribute, elements, {}, attribute)
|
||||||
@buildValue(item, elementRow, key, groups, undefined, undefined, attribute)
|
@buildValue(item, elementRow, key, groups, undefined, undefined, attribute)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -203,3 +229,28 @@ class App.UiElement.postmaster_set
|
||||||
if key
|
if key
|
||||||
elementRow.find('.js-attributeSelector select').val(key)
|
elementRow.find('.js-attributeSelector select').val(key)
|
||||||
|
|
||||||
|
@buildOperator: (elementFull, elementRow, groupAndAttribute, elements, meta, attribute) ->
|
||||||
|
currentOperator = elementRow.find('.js-operator option:selected').attr('value')
|
||||||
|
|
||||||
|
if !meta.operator
|
||||||
|
meta.operator = currentOperator
|
||||||
|
|
||||||
|
name = "#{attribute.name}::#{groupAndAttribute}::operator"
|
||||||
|
|
||||||
|
selection = $("<select class=\"form-control\" name=\"#{name}\"></select>")
|
||||||
|
attributeConfig = elements[groupAndAttribute]
|
||||||
|
|
||||||
|
if !attributeConfig.operator
|
||||||
|
elementRow.find('.js-operator').addClass('hide')
|
||||||
|
else
|
||||||
|
elementRow.find('.js-operator').removeClass('hide')
|
||||||
|
if attributeConfig.operator
|
||||||
|
for operator in attributeConfig.operator
|
||||||
|
operatorName = App.i18n.translateInline(operator)
|
||||||
|
selected = ''
|
||||||
|
if meta.operator is operator
|
||||||
|
selected = 'selected="selected"'
|
||||||
|
selection.append("<option value=\"#{operator}\" #{selected}>#{operatorName}</option>")
|
||||||
|
selection
|
||||||
|
|
||||||
|
elementRow.find('.js-operator select').replaceWith(selection)
|
||||||
|
|
|
@ -143,12 +143,17 @@ class Items extends App.ControllerSubContent
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
id = $(e.target).closest('tr').data('id')
|
id = $(e.target).closest('tr').data('id')
|
||||||
item = App.ObjectManagerAttribute.find(id)
|
item = App.ObjectManagerAttribute.find(id)
|
||||||
|
ui = @
|
||||||
@ajax(
|
@ajax(
|
||||||
id: "object_manager_attributes/#{id}"
|
id: "object_manager_attributes/#{id}"
|
||||||
type: 'DELETE'
|
type: 'DELETE'
|
||||||
url: "#{@apiPath}/object_manager_attributes/#{id}"
|
url: "#{@apiPath}/object_manager_attributes/#{id}"
|
||||||
success: (data) =>
|
success: (data) =>
|
||||||
@render()
|
@render()
|
||||||
|
error: (jqXHR, textStatus, errorThrown) ->
|
||||||
|
ui.log 'errors'
|
||||||
|
# this code is unreachable so alert will do fine
|
||||||
|
alert(jqXHR.responseJSON.error)
|
||||||
)
|
)
|
||||||
|
|
||||||
discard: (e) ->
|
discard: (e) ->
|
||||||
|
|
|
@ -6,6 +6,12 @@
|
||||||
<%- @Icon('arrow-down', 'dropdown-arrow') %>
|
<%- @Icon('arrow-down', 'dropdown-arrow') %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="controls">
|
||||||
|
<div class="u-positionOrigin js-operator">
|
||||||
|
<select></select>
|
||||||
|
<%- @Icon('arrow-down') %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="controls js-value"></div>
|
<div class="controls js-value"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="filter-controls">
|
<div class="filter-controls">
|
||||||
|
|
|
@ -71,8 +71,12 @@
|
||||||
<%- @T('will be deleted') %>
|
<%- @T('will be deleted') %>
|
||||||
<% else if item.to_migrate is true || item.to_config is true: %>
|
<% else if item.to_migrate is true || item.to_config is true: %>
|
||||||
<%- @T('has changed') %>
|
<%- @T('has changed') %>
|
||||||
<% else if item.editable isnt false: %>
|
<% else if item.editable: %>
|
||||||
<a href="#" class="js-delete" title="<%- @Ti('Delete') %>"><%- @Icon('trash') %></a>
|
<% if item.deletable: %>
|
||||||
|
<a href="#" class="js-delete" title="<%- @Ti('Delete') %>"><%- @Icon('trash') %></a>
|
||||||
|
<% else: %>
|
||||||
|
<span class="is-disabled" title="<%= item.not_deletable_reason %>"><%- @Icon('trash') %></span>
|
||||||
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
|
@ -9610,3 +9610,8 @@ body.fit {
|
||||||
.flex-spacer {
|
.flex-spacer {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
span.is-disabled {
|
||||||
|
cursor: not-allowed;
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
|
@ -77,6 +77,9 @@ class ObjectManagerAttributesController < ApplicationController
|
||||||
name: object_manager_attribute.name,
|
name: object_manager_attribute.name,
|
||||||
)
|
)
|
||||||
model_destroy_render_item
|
model_destroy_render_item
|
||||||
|
rescue => e
|
||||||
|
logger.error e
|
||||||
|
raise Exceptions::UnprocessableEntity, e
|
||||||
end
|
end
|
||||||
|
|
||||||
# POST /object_manager_attributes_discard_changes
|
# POST /object_manager_attributes_discard_changes
|
||||||
|
|
|
@ -238,6 +238,14 @@ returns
|
||||||
|
|
||||||
# create ticket
|
# create ticket
|
||||||
ticket.save!
|
ticket.save!
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
# apply tags to ticket
|
||||||
|
if mail['x-zammad-ticket-tags'.to_sym].present?
|
||||||
|
mail['x-zammad-ticket-tags'.to_sym].each do |tag|
|
||||||
|
ticket.tag_add(tag)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# set attributes
|
# set attributes
|
||||||
|
@ -309,6 +317,8 @@ returns
|
||||||
def self.check_attributes_by_x_headers(header_name, value)
|
def self.check_attributes_by_x_headers(header_name, value)
|
||||||
class_name = nil
|
class_name = nil
|
||||||
attribute = nil
|
attribute = nil
|
||||||
|
# skip check attributes if it is tags
|
||||||
|
return true if header_name == 'x-zammad-ticket-tags'
|
||||||
if header_name =~ /^x-zammad-(.+?)-(followup-|)(.*)$/i
|
if header_name =~ /^x-zammad-(.+?)-(followup-|)(.*)$/i
|
||||||
class_name = $1
|
class_name = $1
|
||||||
attribute = $3
|
attribute = $3
|
||||||
|
|
|
@ -45,6 +45,29 @@ module Channel::Filter::Database
|
||||||
filter[:perform].each do |key, meta|
|
filter[:perform].each do |key, meta|
|
||||||
next if !Channel::EmailParser.check_attributes_by_x_headers(key, meta['value'])
|
next if !Channel::EmailParser.check_attributes_by_x_headers(key, meta['value'])
|
||||||
Rails.logger.info " perform '#{key.downcase}' = '#{meta.inspect}'"
|
Rails.logger.info " perform '#{key.downcase}' = '#{meta.inspect}'"
|
||||||
|
|
||||||
|
if key.downcase == 'x-zammad-ticket-tags' && meta['value'].present? && meta['operator'].present?
|
||||||
|
mail[ 'x-zammad-ticket-tags'.downcase.to_sym ] ||= []
|
||||||
|
tags = meta['value'].split(',')
|
||||||
|
|
||||||
|
case meta['operator']
|
||||||
|
when 'add'
|
||||||
|
tags.each do |tag|
|
||||||
|
next if tag.blank?
|
||||||
|
tag.strip!
|
||||||
|
next if mail[ 'x-zammad-ticket-tags'.downcase.to_sym ].include?(tag)
|
||||||
|
mail[ 'x-zammad-ticket-tags'.downcase.to_sym ].push tag
|
||||||
|
end
|
||||||
|
when 'remove'
|
||||||
|
tags.each do |tag|
|
||||||
|
next if tag.blank?
|
||||||
|
tag.strip!
|
||||||
|
mail[ 'x-zammad-ticket-tags'.downcase.to_sym ] -= [tag]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
next
|
||||||
|
end
|
||||||
|
|
||||||
mail[ key.downcase.to_sym ] = meta['value']
|
mail[ key.downcase.to_sym ] = meta['value']
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -29,12 +29,25 @@ list of all attributes
|
||||||
|
|
||||||
def self.list_full
|
def self.list_full
|
||||||
result = ObjectManager::Attribute.all.order('position ASC, name ASC')
|
result = ObjectManager::Attribute.all.order('position ASC, name ASC')
|
||||||
|
references = ObjectManager::Attribute.attribute_to_references_hash
|
||||||
attributes = []
|
attributes = []
|
||||||
assets = {}
|
assets = {}
|
||||||
result.each do |item|
|
result.each do |item|
|
||||||
attribute = item.attributes
|
attribute = item.attributes
|
||||||
attribute[:object] = ObjectLookup.by_id(item.object_lookup_id)
|
attribute[:object] = ObjectLookup.by_id(item.object_lookup_id)
|
||||||
attribute.delete('object_lookup_id')
|
attribute.delete('object_lookup_id')
|
||||||
|
|
||||||
|
# an attribute is deletable if it is both editable and not referenced by other Objects (Triggers, Overviews, Schedulers)
|
||||||
|
deletable = true
|
||||||
|
not_deletable_reason = ''
|
||||||
|
if ObjectManager::Attribute.attribute_used_by_references?(attribute[:object], attribute['name'], references)
|
||||||
|
deletable = false
|
||||||
|
not_deletable_reason = ObjectManager::Attribute.attribute_used_by_references_humaniced(attribute[:object], attribute['name'], references)
|
||||||
|
end
|
||||||
|
attribute[:deletable] = attribute['editable'] && deletable == true
|
||||||
|
if not_deletable_reason.present?
|
||||||
|
attribute[:not_deletable_reason] = "This attribute is referenced by #{not_deletable_reason} and thus cannot be deleted!"
|
||||||
|
end
|
||||||
attributes.push attribute
|
attributes.push attribute
|
||||||
end
|
end
|
||||||
attributes
|
attributes
|
||||||
|
@ -354,6 +367,10 @@ use "force: true" to delete also not editable fields
|
||||||
# lookups
|
# lookups
|
||||||
if data[:object]
|
if data[:object]
|
||||||
data[:object_lookup_id] = ObjectLookup.by_name(data[:object])
|
data[:object_lookup_id] = ObjectLookup.by_name(data[:object])
|
||||||
|
elsif data[:object_lookup_id]
|
||||||
|
data[:object] = ObjectLookup.by_id(data[:object_lookup_id])
|
||||||
|
else
|
||||||
|
raise 'ERROR: need object or object_lookup_id param!'
|
||||||
end
|
end
|
||||||
|
|
||||||
data[:name].downcase!
|
data[:name].downcase!
|
||||||
|
@ -371,6 +388,12 @@ use "force: true" to delete also not editable fields
|
||||||
raise "ERROR: #{data[:object]}.#{data[:name]} can't be removed!"
|
raise "ERROR: #{data[:object]}.#{data[:name]} can't be removed!"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# check to make sure that no triggers, overviews, or schedulers references this attribute
|
||||||
|
if ObjectManager::Attribute.attribute_used_by_references?(data[:object], data[:name])
|
||||||
|
text = ObjectManager::Attribute.attribute_used_by_references_humaniced(data[:object], data[:name])
|
||||||
|
raise "ERROR: #{data[:object]}.#{data[:name]} is referenced by #{text} and thus cannot be deleted!"
|
||||||
|
end
|
||||||
|
|
||||||
# if record is to create, just destroy it
|
# if record is to create, just destroy it
|
||||||
if record.to_create
|
if record.to_create
|
||||||
record.destroy
|
record.destroy
|
||||||
|
@ -721,6 +744,109 @@ to send no browser reload event, pass false
|
||||||
true
|
true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
=begin
|
||||||
|
|
||||||
|
where attributes are used by triggers, overviews or schedulers
|
||||||
|
|
||||||
|
result = ObjectManager::Attribute.attribute_to_references_hash
|
||||||
|
|
||||||
|
result = {
|
||||||
|
ticket.category: {
|
||||||
|
Trigger: ['abc', 'xyz'],
|
||||||
|
Overview: ['abc1', 'abc2'],
|
||||||
|
},
|
||||||
|
ticket.field_b: {
|
||||||
|
Trigger: ['abc'],
|
||||||
|
Overview: ['abc1', 'abc2'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
=end
|
||||||
|
|
||||||
|
def self.attribute_to_references_hash
|
||||||
|
objects = Trigger.select(:name, :condition) + Overview.select(:name, :condition) + Job.select(:name, :condition)
|
||||||
|
attribute_list = {}
|
||||||
|
objects.each do |item|
|
||||||
|
item.condition.each do |condition_key, _condition_attributes|
|
||||||
|
attribute_list[condition_key] ||= {}
|
||||||
|
attribute_list[condition_key][item.class.name] ||= []
|
||||||
|
next if attribute_list[condition_key][item.class.name].include?(item.name)
|
||||||
|
attribute_list[condition_key][item.class.name].push item.name
|
||||||
|
end
|
||||||
|
end
|
||||||
|
attribute_list
|
||||||
|
end
|
||||||
|
|
||||||
|
=begin
|
||||||
|
|
||||||
|
is certain attribute used by triggers, overviews or schedulers
|
||||||
|
|
||||||
|
ObjectManager::Attribute.attribute_used_by_references?('Ticket', 'attribute_name')
|
||||||
|
|
||||||
|
=end
|
||||||
|
|
||||||
|
def self.attribute_used_by_references?(object_name, attribute_name, references = attribute_to_references_hash)
|
||||||
|
references.each do |reference_key, _relations|
|
||||||
|
local_object, local_attribute = reference_key.split('.')
|
||||||
|
next if local_object != object_name.downcase
|
||||||
|
next if local_attribute != attribute_name
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
false
|
||||||
|
end
|
||||||
|
|
||||||
|
=begin
|
||||||
|
|
||||||
|
is certain attribute used by triggers, overviews or schedulers
|
||||||
|
|
||||||
|
result = ObjectManager::Attribute.attribute_used_by_references('Ticket', 'attribute_name')
|
||||||
|
|
||||||
|
result = {
|
||||||
|
Trigger: ['abc', 'xyz'],
|
||||||
|
Overview: ['abc1', 'abc2'],
|
||||||
|
}
|
||||||
|
|
||||||
|
=end
|
||||||
|
|
||||||
|
def self.attribute_used_by_references(object_name, attribute_name, references = attribute_to_references_hash)
|
||||||
|
result = {}
|
||||||
|
references.each do |reference_key, relations|
|
||||||
|
local_object, local_attribute = reference_key.split('.')
|
||||||
|
next if local_object != object_name.downcase
|
||||||
|
next if local_attribute != attribute_name
|
||||||
|
relations.each do |relation, relation_names|
|
||||||
|
result[relation] ||= []
|
||||||
|
result[relation].push relation_names.sort
|
||||||
|
end
|
||||||
|
break
|
||||||
|
end
|
||||||
|
result
|
||||||
|
end
|
||||||
|
|
||||||
|
=begin
|
||||||
|
|
||||||
|
is certain attribute used by triggers, overviews or schedulers
|
||||||
|
|
||||||
|
text = ObjectManager::Attribute.attribute_used_by_references_humaniced('Ticket', 'attribute_name', references)
|
||||||
|
|
||||||
|
=end
|
||||||
|
|
||||||
|
def self.attribute_used_by_references_humaniced(object_name, attribute_name, references = nil)
|
||||||
|
result = if references.present?
|
||||||
|
ObjectManager::Attribute.attribute_used_by_references(object_name, attribute_name, references)
|
||||||
|
else
|
||||||
|
ObjectManager::Attribute.attribute_used_by_references(object_name, attribute_name)
|
||||||
|
end
|
||||||
|
not_deletable_reason = ''
|
||||||
|
result.each do |relation, relation_names|
|
||||||
|
if not_deletable_reason.present?
|
||||||
|
not_deletable_reason += '; '
|
||||||
|
end
|
||||||
|
not_deletable_reason += "#{relation}: #{relation_names.sort.join(',')}"
|
||||||
|
end
|
||||||
|
not_deletable_reason
|
||||||
|
end
|
||||||
|
|
||||||
def self.reset_database_info(model)
|
def self.reset_database_info(model)
|
||||||
model.connection.schema_cache.clear!
|
model.connection.schema_cache.clear!
|
||||||
model.reset_column_information
|
model.reset_column_information
|
||||||
|
|
|
@ -557,11 +557,11 @@ condition example
|
||||||
|
|
||||||
# get attributes
|
# get attributes
|
||||||
attributes = attribute.split(/\./)
|
attributes = attribute.split(/\./)
|
||||||
attribute = "#{attributes[0]}s.#{attributes[1]}"
|
attribute = "#{ActiveRecord::Base.connection.quote_table_name("#{attributes[0]}s")}.#{ActiveRecord::Base.connection.quote_column_name(attributes[1])}"
|
||||||
|
|
||||||
# magic selectors
|
# magic selectors
|
||||||
if attributes[0] == 'ticket' && attributes[1] == 'out_of_office_replacement_id'
|
if attributes[0] == 'ticket' && attributes[1] == 'out_of_office_replacement_id'
|
||||||
attribute = "#{attributes[0]}s.owner_id"
|
attribute = "#{ActiveRecord::Base.connection.quote_table_name("#{attributes[0]}s")}.#{ActiveRecord::Base.connection.quote_column_name('owner_id')}"
|
||||||
end
|
end
|
||||||
|
|
||||||
if attributes[0] == 'ticket' && attributes[1] == 'tags'
|
if attributes[0] == 'ticket' && attributes[1] == 'tags'
|
||||||
|
|
|
@ -769,6 +769,10 @@ test("form postmaster filter", function() {
|
||||||
},
|
},
|
||||||
'x-zammad-ticket-priority_id': {
|
'x-zammad-ticket-priority_id': {
|
||||||
value: '1'
|
value: '1'
|
||||||
|
},
|
||||||
|
'x-zammad-ticket-tags': {
|
||||||
|
operator: 'add',
|
||||||
|
value: 'test, test1'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -812,6 +816,10 @@ test("form postmaster filter", function() {
|
||||||
},
|
},
|
||||||
'x-zammad-ticket-priority_id': {
|
'x-zammad-ticket-priority_id': {
|
||||||
value: '1'
|
value: '1'
|
||||||
|
},
|
||||||
|
'x-zammad-ticket-tags': {
|
||||||
|
operator: 'add',
|
||||||
|
value: 'test, test1'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -842,6 +850,10 @@ test("form postmaster filter", function() {
|
||||||
'x-zammad-ticket-group_id': {
|
'x-zammad-ticket-group_id': {
|
||||||
value: '1'
|
value: '1'
|
||||||
},
|
},
|
||||||
|
'x-zammad-ticket-tags': {
|
||||||
|
operator: 'add',
|
||||||
|
value: "test, test1"
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
deepEqual(params, test_params, 'form param check')
|
deepEqual(params, test_params, 'form param check')
|
||||||
|
@ -849,6 +861,43 @@ test("form postmaster filter", function() {
|
||||||
},
|
},
|
||||||
1000
|
1000
|
||||||
);
|
);
|
||||||
|
App.Delay.set(function() {
|
||||||
|
el.find('.postmaster_set .js-filterElement').last().find('.filter-controls .js-add').click()
|
||||||
|
test("form param check click add after tag option", function() {
|
||||||
|
params = App.ControllerForm.params(el)
|
||||||
|
test_params = {
|
||||||
|
input1: 'some not used default',
|
||||||
|
input2: 'some name',
|
||||||
|
match: {
|
||||||
|
from: {
|
||||||
|
operator: 'contains',
|
||||||
|
value: 'some@address'
|
||||||
|
},
|
||||||
|
subject: {
|
||||||
|
operator: 'contains',
|
||||||
|
value: 'some subject'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
set: {
|
||||||
|
'x-zammad-ticket-owner_id': {
|
||||||
|
value: 'owner',
|
||||||
|
value_completion: ''
|
||||||
|
},
|
||||||
|
'x-zammad-ticket-group_id': {
|
||||||
|
value: '1'
|
||||||
|
},
|
||||||
|
'x-zammad-ticket-tags': {
|
||||||
|
operator: 'add',
|
||||||
|
value: "test, test1"
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
deepEqual(params, test_params, 'form param check')
|
||||||
|
});
|
||||||
|
},
|
||||||
|
1000
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -499,4 +499,99 @@ class AdminObjectManagerTest < TestCase
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_that_attributes_with_references_should_have_a_disabled_delete_button
|
||||||
|
@browser = instance = browser_instance
|
||||||
|
login(
|
||||||
|
username: 'master@example.com',
|
||||||
|
password: 'test',
|
||||||
|
url: browser_url,
|
||||||
|
)
|
||||||
|
|
||||||
|
tasks_close_all()
|
||||||
|
|
||||||
|
# create two new attributes
|
||||||
|
object_manager_attribute_create(
|
||||||
|
data: {
|
||||||
|
name: 'deletable_attribute',
|
||||||
|
display: 'Deletable Attribute',
|
||||||
|
data_type: 'Text',
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
object_manager_attribute_create(
|
||||||
|
data: {
|
||||||
|
name: 'undeletable_attribute',
|
||||||
|
display: 'Undeletable Attribute',
|
||||||
|
data_type: 'Text',
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
watch_for(
|
||||||
|
css: '.content.active',
|
||||||
|
value: 'Database Update required',
|
||||||
|
)
|
||||||
|
click(css: '.content.active .tab-pane.active div.js-execute')
|
||||||
|
watch_for(
|
||||||
|
css: '.modal',
|
||||||
|
value: 'restart',
|
||||||
|
)
|
||||||
|
watch_for_disappear(
|
||||||
|
css: '.modal',
|
||||||
|
timeout: 120,
|
||||||
|
)
|
||||||
|
sleep 5
|
||||||
|
watch_for(
|
||||||
|
css: '.content.active',
|
||||||
|
)
|
||||||
|
match_not(
|
||||||
|
css: '.content.active',
|
||||||
|
value: 'Database Update required',
|
||||||
|
)
|
||||||
|
|
||||||
|
# create a new overview that references the undeletable_attribute
|
||||||
|
overview_create(
|
||||||
|
browser: instance,
|
||||||
|
data: {
|
||||||
|
name: 'test_overview',
|
||||||
|
roles: ['Agent'],
|
||||||
|
selector: {
|
||||||
|
'Undeletable Attribute' => 'DUMMY',
|
||||||
|
},
|
||||||
|
'order::direction' => 'down',
|
||||||
|
'text_input' => true,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
click(
|
||||||
|
browser: instance,
|
||||||
|
css: 'a[href="#manage"]',
|
||||||
|
mute_log: true,
|
||||||
|
)
|
||||||
|
click(
|
||||||
|
browser: instance,
|
||||||
|
css: '.content.active a[href="#system/object_manager"]',
|
||||||
|
mute_log: true,
|
||||||
|
)
|
||||||
|
|
||||||
|
30.times do
|
||||||
|
deletable_attribute = instance.find_elements(xpath: '//td[text()="deletable_attribute"]/following-sibling::*[2]')[0]
|
||||||
|
break if deletable_attribute
|
||||||
|
sleep 1
|
||||||
|
end
|
||||||
|
|
||||||
|
sleep 1
|
||||||
|
deletable_attribute = instance.find_elements(xpath: '//td[text()="deletable_attribute"]/following-sibling::*[2]')[0]
|
||||||
|
assert_not_nil(deletable_attribute)
|
||||||
|
deletable_attribute_html = deletable_attribute.attribute('innerHTML')
|
||||||
|
assert(deletable_attribute_html.include?('title="Delete"'))
|
||||||
|
assert(deletable_attribute_html.include?('href="#"'))
|
||||||
|
assert(deletable_attribute_html.exclude?('cannot be deleted'))
|
||||||
|
|
||||||
|
undeletable_attribute = instance.find_elements(xpath: '//td[text()="undeletable_attribute"]/following-sibling::*[2]')[0]
|
||||||
|
assert_not_nil(undeletable_attribute)
|
||||||
|
undeletable_attribute_html = undeletable_attribute.attribute('innerHTML')
|
||||||
|
assert(undeletable_attribute_html.include?('Overview'))
|
||||||
|
assert(undeletable_attribute_html.include?('test_overview'))
|
||||||
|
assert(undeletable_attribute_html.include?('cannot be deleted'))
|
||||||
|
assert(undeletable_attribute_html.exclude?('href="#"'))
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1659,13 +1659,22 @@ wait untill text in selector disabppears
|
||||||
mute_log: true,
|
mute_log: true,
|
||||||
)
|
)
|
||||||
sleep 0.5
|
sleep 0.5
|
||||||
select(
|
if data.key?('text_input')
|
||||||
browser: instance,
|
set(
|
||||||
css: '.modal .ticket_selector .js-value select',
|
browser: instance,
|
||||||
value: value,
|
css: '.modal .ticket_selector .js-value input',
|
||||||
deselect_all: true,
|
value: value,
|
||||||
mute_log: true,
|
mute_log: true,
|
||||||
)
|
)
|
||||||
|
else
|
||||||
|
select(
|
||||||
|
browser: instance,
|
||||||
|
css: '.modal .ticket_selector .js-value select',
|
||||||
|
value: value,
|
||||||
|
deselect_all: true,
|
||||||
|
mute_log: true,
|
||||||
|
)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if data['order::direction']
|
if data['order::direction']
|
||||||
|
|
|
@ -536,7 +536,6 @@ class ObjectManagerAttributesControllerTest < ActionDispatch::IntegrationTest
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
'id': 'c-202'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
post '/api/v1/object_manager_attributes', params: params.to_json, headers: @headers.merge('Authorization' => credentials)
|
post '/api/v1/object_manager_attributes', params: params.to_json, headers: @headers.merge('Authorization' => credentials)
|
||||||
|
@ -553,4 +552,467 @@ class ObjectManagerAttributesControllerTest < ActionDispatch::IntegrationTest
|
||||||
assert_equal(result['data_type'], 'boolean')
|
assert_equal(result['data_type'], 'boolean')
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test '03 ticket attributes cannot be removed when it is referenced by an overview' do
|
||||||
|
credentials = ActionController::HttpAuthentication::Basic.encode_credentials('tickets-admin@example.com', 'adminpw')
|
||||||
|
|
||||||
|
# 1. create a new ticket attribute and execute migration
|
||||||
|
migration = ObjectManager::Attribute.migration_execute
|
||||||
|
|
||||||
|
params = {
|
||||||
|
'name': 'test_attribute_referenced_by_an_overview',
|
||||||
|
'object': 'Ticket',
|
||||||
|
'display': 'Test Attribute',
|
||||||
|
'active': true,
|
||||||
|
'data_type': 'input',
|
||||||
|
'data_option': {
|
||||||
|
'default': '',
|
||||||
|
'type': 'text',
|
||||||
|
'maxlength': 120,
|
||||||
|
'null': true,
|
||||||
|
'options': {},
|
||||||
|
'relation': ''
|
||||||
|
},
|
||||||
|
'screens': {
|
||||||
|
'create_middle': {
|
||||||
|
'ticket.customer': {
|
||||||
|
'shown': true,
|
||||||
|
'item_class': 'column'
|
||||||
|
},
|
||||||
|
'ticket.agent': {
|
||||||
|
'shown': true,
|
||||||
|
'item_class': 'column'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'edit': {
|
||||||
|
'ticket.customer': {
|
||||||
|
'shown': true
|
||||||
|
},
|
||||||
|
'ticket.agent': {
|
||||||
|
'shown': true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
post '/api/v1/object_manager_attributes', params: params.to_json, headers: @headers.merge('Authorization' => credentials)
|
||||||
|
|
||||||
|
migration = ObjectManager::Attribute.migration_execute
|
||||||
|
assert_equal(migration, true)
|
||||||
|
|
||||||
|
# 2. create an overview that uses the attribute
|
||||||
|
params = {
|
||||||
|
name: 'test_overview',
|
||||||
|
roles: Role.where(name: 'Agent').pluck(:name),
|
||||||
|
condition: {
|
||||||
|
'ticket.state_id': {
|
||||||
|
'operator': 'is',
|
||||||
|
'value': Ticket::State.all.pluck(:id),
|
||||||
|
},
|
||||||
|
'ticket.test_attribute_referenced_by_an_overview': {
|
||||||
|
'operator': 'contains',
|
||||||
|
'value': 'DUMMY'
|
||||||
|
},
|
||||||
|
},
|
||||||
|
order: {
|
||||||
|
by: 'created_at',
|
||||||
|
direction: 'DESC',
|
||||||
|
},
|
||||||
|
view: {
|
||||||
|
d: %w[title customer state created_at],
|
||||||
|
s: %w[number title customer state created_at],
|
||||||
|
m: %w[number title customer state created_at],
|
||||||
|
view_mode_default: 's',
|
||||||
|
},
|
||||||
|
user_ids: [ '1' ],
|
||||||
|
}
|
||||||
|
|
||||||
|
if Overview.where('name like ?', '%test%').empty?
|
||||||
|
post '/api/v1/overviews', params: params.to_json, headers: @headers.merge('Authorization' => credentials)
|
||||||
|
assert_response(201)
|
||||||
|
result = JSON.parse(@response.body)
|
||||||
|
assert_equal(Hash, result.class)
|
||||||
|
assert_equal('test_overview', result['name'])
|
||||||
|
end
|
||||||
|
|
||||||
|
# 3. attempt to delete the ticket attribute
|
||||||
|
get '/api/v1/object_manager_attributes', headers: @headers.merge('Authorization' => credentials)
|
||||||
|
assert_response(200)
|
||||||
|
result = JSON.parse(@response.body)
|
||||||
|
target_attribute = result.select { |x| x['name'] == 'test_attribute_referenced_by_an_overview' && x['object'] == 'Ticket' }
|
||||||
|
assert_equal target_attribute.size, 1
|
||||||
|
target_id = target_attribute[0]['id']
|
||||||
|
|
||||||
|
delete "/api/v1/object_manager_attributes/#{target_id}", headers: @headers.merge('Authorization' => credentials)
|
||||||
|
assert_response(422)
|
||||||
|
assert @response.body.include?('Overview')
|
||||||
|
assert @response.body.include?('test_overview')
|
||||||
|
assert @response.body.include?('cannot be deleted!')
|
||||||
|
end
|
||||||
|
|
||||||
|
test '04 ticket attributes cannot be removed when it is referenced by a trigger' do
|
||||||
|
credentials = ActionController::HttpAuthentication::Basic.encode_credentials('tickets-admin@example.com', 'adminpw')
|
||||||
|
|
||||||
|
# 1. create a new ticket attribute and execute migration
|
||||||
|
migration = ObjectManager::Attribute.migration_execute
|
||||||
|
|
||||||
|
params = {
|
||||||
|
'name': 'test_attribute_referenced_by_a_trigger',
|
||||||
|
'object': 'Ticket',
|
||||||
|
'display': 'Test Attribute',
|
||||||
|
'active': true,
|
||||||
|
'data_type': 'input',
|
||||||
|
'data_option': {
|
||||||
|
'default': '',
|
||||||
|
'type': 'text',
|
||||||
|
'maxlength': 120,
|
||||||
|
'null': true,
|
||||||
|
'options': {},
|
||||||
|
'relation': ''
|
||||||
|
},
|
||||||
|
'screens': {
|
||||||
|
'create_middle': {
|
||||||
|
'ticket.customer': {
|
||||||
|
'shown': true,
|
||||||
|
'item_class': 'column'
|
||||||
|
},
|
||||||
|
'ticket.agent': {
|
||||||
|
'shown': true,
|
||||||
|
'item_class': 'column'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'edit': {
|
||||||
|
'ticket.customer': {
|
||||||
|
'shown': true
|
||||||
|
},
|
||||||
|
'ticket.agent': {
|
||||||
|
'shown': true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
post '/api/v1/object_manager_attributes', params: params.to_json, headers: @headers.merge('Authorization' => credentials)
|
||||||
|
|
||||||
|
migration = ObjectManager::Attribute.migration_execute
|
||||||
|
assert_equal(migration, true)
|
||||||
|
|
||||||
|
# 2. create an trigger that uses the attribute
|
||||||
|
params = {
|
||||||
|
name: 'test_trigger',
|
||||||
|
condition: {
|
||||||
|
'ticket.test_attribute_referenced_by_a_trigger': {
|
||||||
|
'operator': 'contains',
|
||||||
|
'value': 'DUMMY'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'perform': {
|
||||||
|
'ticket.state_id': {
|
||||||
|
'value': '2'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'active': true,
|
||||||
|
'id': 'c-3'
|
||||||
|
}
|
||||||
|
|
||||||
|
if Trigger.where('name like ?', '%test%').empty?
|
||||||
|
post '/api/v1/triggers', params: params.to_json, headers: @headers.merge('Authorization' => credentials)
|
||||||
|
assert_response(201)
|
||||||
|
result = JSON.parse(@response.body)
|
||||||
|
assert_equal(Hash, result.class)
|
||||||
|
assert_equal('test_trigger', result['name'])
|
||||||
|
end
|
||||||
|
|
||||||
|
# 3. attempt to delete the ticket attribute
|
||||||
|
get '/api/v1/object_manager_attributes', headers: @headers.merge('Authorization' => credentials)
|
||||||
|
assert_response(200)
|
||||||
|
result = JSON.parse(@response.body)
|
||||||
|
target_attribute = result.select { |x| x['name'] == 'test_attribute_referenced_by_a_trigger' && x['object'] == 'Ticket' }
|
||||||
|
assert_equal target_attribute.size, 1
|
||||||
|
target_id = target_attribute[0]['id']
|
||||||
|
|
||||||
|
delete "/api/v1/object_manager_attributes/#{target_id}", headers: @headers.merge('Authorization' => credentials)
|
||||||
|
assert_response(422)
|
||||||
|
assert @response.body.include?('Trigger')
|
||||||
|
assert @response.body.include?('test_trigger')
|
||||||
|
assert @response.body.include?('cannot be deleted!')
|
||||||
|
end
|
||||||
|
|
||||||
|
test '05 ticket attributes cannot be removed when it is referenced by a scheduler' do
|
||||||
|
credentials = ActionController::HttpAuthentication::Basic.encode_credentials('tickets-admin@example.com', 'adminpw')
|
||||||
|
|
||||||
|
# 1. create a new ticket attribute and execute migration
|
||||||
|
migration = ObjectManager::Attribute.migration_execute
|
||||||
|
|
||||||
|
params = {
|
||||||
|
'name': 'test_attribute_referenced_by_a_scheduler',
|
||||||
|
'object': 'Ticket',
|
||||||
|
'display': 'Test Attribute',
|
||||||
|
'active': true,
|
||||||
|
'data_type': 'input',
|
||||||
|
'data_option': {
|
||||||
|
'default': '',
|
||||||
|
'type': 'text',
|
||||||
|
'maxlength': 120,
|
||||||
|
'null': true,
|
||||||
|
'options': {},
|
||||||
|
'relation': ''
|
||||||
|
},
|
||||||
|
'screens': {
|
||||||
|
'create_middle': {
|
||||||
|
'ticket.customer': {
|
||||||
|
'shown': true,
|
||||||
|
'item_class': 'column'
|
||||||
|
},
|
||||||
|
'ticket.agent': {
|
||||||
|
'shown': true,
|
||||||
|
'item_class': 'column'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'edit': {
|
||||||
|
'ticket.customer': {
|
||||||
|
'shown': true
|
||||||
|
},
|
||||||
|
'ticket.agent': {
|
||||||
|
'shown': true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
post '/api/v1/object_manager_attributes', params: params.to_json, headers: @headers.merge('Authorization' => credentials)
|
||||||
|
|
||||||
|
migration = ObjectManager::Attribute.migration_execute
|
||||||
|
assert_equal(migration, true)
|
||||||
|
|
||||||
|
# 2. create a scheduler that uses the attribute
|
||||||
|
params = {
|
||||||
|
name: 'test_scheduler',
|
||||||
|
'timeplan': {
|
||||||
|
'days': {
|
||||||
|
'Mon': true,
|
||||||
|
'Tue': false,
|
||||||
|
'Wed': false,
|
||||||
|
'Thu': false,
|
||||||
|
'Fri': false,
|
||||||
|
'Sat': false,
|
||||||
|
'Sun': false
|
||||||
|
},
|
||||||
|
'hours': {
|
||||||
|
'0': true,
|
||||||
|
'1': false,
|
||||||
|
'2': false,
|
||||||
|
'3': false,
|
||||||
|
'4': false,
|
||||||
|
'5': false,
|
||||||
|
'6': false,
|
||||||
|
'7': false,
|
||||||
|
'8': false,
|
||||||
|
'9': false,
|
||||||
|
'10': false,
|
||||||
|
'11': false,
|
||||||
|
'12': false,
|
||||||
|
'13': false,
|
||||||
|
'14': false,
|
||||||
|
'15': false,
|
||||||
|
'16': false,
|
||||||
|
'17': false,
|
||||||
|
'18': false,
|
||||||
|
'19': false,
|
||||||
|
'20': false,
|
||||||
|
'21': false,
|
||||||
|
'22': false,
|
||||||
|
'23': false
|
||||||
|
},
|
||||||
|
'minutes': {
|
||||||
|
'0': true,
|
||||||
|
'10': false,
|
||||||
|
'20': false,
|
||||||
|
'30': false,
|
||||||
|
'40': false,
|
||||||
|
'50': false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'condition': {
|
||||||
|
'ticket.test_attribute_referenced_by_a_scheduler': {
|
||||||
|
'operator': 'contains',
|
||||||
|
'value': 'DUMMY'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'perform': {
|
||||||
|
'ticket.state_id': {
|
||||||
|
'value': '2'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'disable_notification': true,
|
||||||
|
'note': '',
|
||||||
|
'active': true,
|
||||||
|
'id': 'c-0'
|
||||||
|
}
|
||||||
|
|
||||||
|
if Job.where('name like ?', '%test%').empty?
|
||||||
|
post '/api/v1/jobs', params: params.to_json, headers: @headers.merge('Authorization' => credentials)
|
||||||
|
assert_response(201)
|
||||||
|
result = JSON.parse(@response.body)
|
||||||
|
assert_equal(Hash, result.class)
|
||||||
|
assert_equal('test_scheduler', result['name'])
|
||||||
|
end
|
||||||
|
|
||||||
|
# 3. attempt to delete the ticket attribute
|
||||||
|
get '/api/v1/object_manager_attributes', headers: @headers.merge('Authorization' => credentials)
|
||||||
|
assert_response(200)
|
||||||
|
result = JSON.parse(@response.body)
|
||||||
|
target_attribute = result.select { |x| x['name'] == 'test_attribute_referenced_by_a_scheduler' && x['object'] == 'Ticket' }
|
||||||
|
assert_equal target_attribute.size, 1
|
||||||
|
target_id = target_attribute[0]['id']
|
||||||
|
|
||||||
|
delete "/api/v1/object_manager_attributes/#{target_id}", headers: @headers.merge('Authorization' => credentials)
|
||||||
|
assert_response(422)
|
||||||
|
assert @response.body.include?('Job')
|
||||||
|
assert @response.body.include?('test_scheduler')
|
||||||
|
assert @response.body.include?('cannot be deleted!')
|
||||||
|
end
|
||||||
|
|
||||||
|
test '06 ticket attributes can be removed when it is referenced by an overview but by user object' do
|
||||||
|
credentials = ActionController::HttpAuthentication::Basic.encode_credentials('tickets-admin@example.com', 'adminpw')
|
||||||
|
|
||||||
|
# 1. create a new ticket attribute and execute migration
|
||||||
|
migration = ObjectManager::Attribute.migration_execute
|
||||||
|
|
||||||
|
params = {
|
||||||
|
'name': 'test_attribute_referenced_by_an_overview',
|
||||||
|
'object': 'Ticket',
|
||||||
|
'display': 'Test Attribute',
|
||||||
|
'active': true,
|
||||||
|
'data_type': 'input',
|
||||||
|
'data_option': {
|
||||||
|
'default': '',
|
||||||
|
'type': 'text',
|
||||||
|
'maxlength': 120,
|
||||||
|
'null': true,
|
||||||
|
'options': {},
|
||||||
|
'relation': ''
|
||||||
|
},
|
||||||
|
'screens': {
|
||||||
|
'create_middle': {
|
||||||
|
'ticket.customer': {
|
||||||
|
'shown': true,
|
||||||
|
'item_class': 'column'
|
||||||
|
},
|
||||||
|
'ticket.agent': {
|
||||||
|
'shown': true,
|
||||||
|
'item_class': 'column'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'edit': {
|
||||||
|
'ticket.customer': {
|
||||||
|
'shown': true
|
||||||
|
},
|
||||||
|
'ticket.agent': {
|
||||||
|
'shown': true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
post '/api/v1/object_manager_attributes', params: params.to_json, headers: @headers.merge('Authorization' => credentials)
|
||||||
|
|
||||||
|
params = {
|
||||||
|
'name': 'test_attribute_referenced_by_an_overview',
|
||||||
|
'object': 'User',
|
||||||
|
'display': 'Test Attribute',
|
||||||
|
'active': true,
|
||||||
|
'data_type': 'input',
|
||||||
|
'data_option': {
|
||||||
|
'default': '',
|
||||||
|
'type': 'text',
|
||||||
|
'maxlength': 120,
|
||||||
|
'null': true,
|
||||||
|
'options': {},
|
||||||
|
'relation': ''
|
||||||
|
},
|
||||||
|
'screens': {
|
||||||
|
'create_middle': {
|
||||||
|
'ticket.customer': {
|
||||||
|
'shown': true,
|
||||||
|
'item_class': 'column'
|
||||||
|
},
|
||||||
|
'ticket.agent': {
|
||||||
|
'shown': true,
|
||||||
|
'item_class': 'column'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'edit': {
|
||||||
|
'ticket.customer': {
|
||||||
|
'shown': true
|
||||||
|
},
|
||||||
|
'ticket.agent': {
|
||||||
|
'shown': true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
post '/api/v1/object_manager_attributes', params: params.to_json, headers: @headers.merge('Authorization' => credentials)
|
||||||
|
|
||||||
|
migration = ObjectManager::Attribute.migration_execute
|
||||||
|
assert_equal(migration, true)
|
||||||
|
|
||||||
|
# 2. create an overview that uses the attribute
|
||||||
|
params = {
|
||||||
|
name: 'test_overview',
|
||||||
|
roles: Role.where(name: 'Agent').pluck(:name),
|
||||||
|
condition: {
|
||||||
|
'ticket.state_id': {
|
||||||
|
'operator': 'is',
|
||||||
|
'value': Ticket::State.all.pluck(:id),
|
||||||
|
},
|
||||||
|
'ticket.test_attribute_referenced_by_an_overview': {
|
||||||
|
'operator': 'contains',
|
||||||
|
'value': 'DUMMY'
|
||||||
|
},
|
||||||
|
},
|
||||||
|
order: {
|
||||||
|
by: 'created_at',
|
||||||
|
direction: 'DESC',
|
||||||
|
},
|
||||||
|
view: {
|
||||||
|
d: %w[title customer state created_at],
|
||||||
|
s: %w[number title customer state created_at],
|
||||||
|
m: %w[number title customer state created_at],
|
||||||
|
view_mode_default: 's',
|
||||||
|
},
|
||||||
|
user_ids: [ '1' ],
|
||||||
|
}
|
||||||
|
|
||||||
|
if Overview.where('name like ?', '%test%').empty?
|
||||||
|
post '/api/v1/overviews', params: params.to_json, headers: @headers.merge('Authorization' => credentials)
|
||||||
|
assert_response(201)
|
||||||
|
result = JSON.parse(@response.body)
|
||||||
|
assert_equal(Hash, result.class)
|
||||||
|
assert_equal('test_overview', result['name'])
|
||||||
|
end
|
||||||
|
|
||||||
|
# 3. attempt to delete the ticket attribute
|
||||||
|
get '/api/v1/object_manager_attributes', headers: @headers.merge('Authorization' => credentials)
|
||||||
|
assert_response(200)
|
||||||
|
result = JSON.parse(@response.body)
|
||||||
|
|
||||||
|
target_attribute = result.select { |x| x['name'] == 'test_attribute_referenced_by_an_overview' && x['object'] == 'User' }
|
||||||
|
assert_equal target_attribute.size, 1
|
||||||
|
target_id = target_attribute[0]['id']
|
||||||
|
|
||||||
|
delete "/api/v1/object_manager_attributes/#{target_id}", headers: @headers.merge('Authorization' => credentials)
|
||||||
|
assert_response(200)
|
||||||
|
|
||||||
|
target_attribute = result.select { |x| x['name'] == 'test_attribute_referenced_by_an_overview' && x['object'] == 'Ticket' }
|
||||||
|
assert_equal target_attribute.size, 1
|
||||||
|
target_id = target_attribute[0]['id']
|
||||||
|
|
||||||
|
delete "/api/v1/object_manager_attributes/#{target_id}", headers: @headers.merge('Authorization' => credentials)
|
||||||
|
assert_response(422)
|
||||||
|
assert @response.body.include?('Overview')
|
||||||
|
assert @response.body.include?('test_overview')
|
||||||
|
assert @response.body.include?('cannot be deleted!')
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -663,4 +663,78 @@ class ObjectManagerTest < ActiveSupport::TestCase
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test 'c object manager attribute - certain names' do
|
||||||
|
|
||||||
|
assert_equal(false, ObjectManager::Attribute.pending_migration?)
|
||||||
|
assert_equal(0, ObjectManager::Attribute.where(to_migrate: true).count)
|
||||||
|
assert_equal(0, ObjectManager::Attribute.migrations.count)
|
||||||
|
|
||||||
|
attribute1 = ObjectManager::Attribute.add(
|
||||||
|
object: 'Ticket',
|
||||||
|
name: '1_a_anfrage_status',
|
||||||
|
display: '1_a_anfrage_status',
|
||||||
|
data_type: 'input',
|
||||||
|
data_option: {
|
||||||
|
maxlength: 200,
|
||||||
|
type: 'text',
|
||||||
|
null: true,
|
||||||
|
},
|
||||||
|
active: true,
|
||||||
|
screens: {},
|
||||||
|
position: 20,
|
||||||
|
created_by_id: 1,
|
||||||
|
updated_by_id: 1,
|
||||||
|
)
|
||||||
|
assert(attribute1)
|
||||||
|
|
||||||
|
assert_equal(true, ObjectManager::Attribute.pending_migration?)
|
||||||
|
assert_equal(1, ObjectManager::Attribute.where(to_migrate: true).count)
|
||||||
|
assert_equal(1, ObjectManager::Attribute.migrations.count)
|
||||||
|
|
||||||
|
# execute migrations
|
||||||
|
assert(ObjectManager::Attribute.migration_execute)
|
||||||
|
|
||||||
|
assert_equal(false, ObjectManager::Attribute.pending_migration?)
|
||||||
|
assert_equal(0, ObjectManager::Attribute.where(to_migrate: true).count)
|
||||||
|
assert_equal(0, ObjectManager::Attribute.migrations.count)
|
||||||
|
|
||||||
|
# create example ticket
|
||||||
|
ticket1 = Ticket.create(
|
||||||
|
title: 'some attribute test3',
|
||||||
|
group: Group.lookup(name: 'Users'),
|
||||||
|
customer_id: 2,
|
||||||
|
state: Ticket::State.lookup(name: 'new'),
|
||||||
|
priority: Ticket::Priority.lookup(name: '2 normal'),
|
||||||
|
'1_a_anfrage_status': 'some attribute text',
|
||||||
|
updated_by_id: 1,
|
||||||
|
created_by_id: 1,
|
||||||
|
)
|
||||||
|
assert('ticket1 created', ticket1)
|
||||||
|
|
||||||
|
assert_equal('some attribute test3', ticket1.title)
|
||||||
|
assert_equal('Users', ticket1.group.name)
|
||||||
|
assert_equal('new', ticket1.state.name)
|
||||||
|
assert_equal('some attribute text', ticket1['1_a_anfrage_status'])
|
||||||
|
|
||||||
|
condition = {
|
||||||
|
'ticket.title' => {
|
||||||
|
operator: 'is',
|
||||||
|
value: 'some attribute test3',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
ticket_count, tickets = Ticket.selectors(condition, 10)
|
||||||
|
assert_equal(ticket_count, 1)
|
||||||
|
assert_equal(tickets[0].id, ticket1.id)
|
||||||
|
|
||||||
|
condition = {
|
||||||
|
'ticket.1_a_anfrage_status' => {
|
||||||
|
operator: 'is',
|
||||||
|
value: 'some attribute text',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
ticket_count, tickets = Ticket.selectors(condition, 10)
|
||||||
|
assert_equal(ticket_count, 1)
|
||||||
|
assert_equal(tickets[0].id, ticket1.id)
|
||||||
|
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -961,4 +961,106 @@ Some Text'
|
||||||
assert_equal(false, article.internal)
|
assert_equal(false, article.internal)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test 'tags in postmaster filter' do
|
||||||
|
group_default = Group.lookup(name: 'Users')
|
||||||
|
|
||||||
|
PostmasterFilter.create!(
|
||||||
|
name: '01 set tag for email',
|
||||||
|
match: {
|
||||||
|
from: {
|
||||||
|
operator: 'contains',
|
||||||
|
value: 'nobody@example.com',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
perform: {
|
||||||
|
'x-zammad-ticket-tags' => {
|
||||||
|
operator: 'add',
|
||||||
|
value: 'test1, test2, test3',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
channel: 'email',
|
||||||
|
active: true,
|
||||||
|
created_by_id: 1,
|
||||||
|
updated_by_id: 1,
|
||||||
|
)
|
||||||
|
|
||||||
|
PostmasterFilter.create!(
|
||||||
|
name: '02 set tag for email',
|
||||||
|
match: {
|
||||||
|
from: {
|
||||||
|
operator: 'contains',
|
||||||
|
value: 'nobody@example.com',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
perform: {
|
||||||
|
'x-zammad-ticket-tags' => {
|
||||||
|
operator: 'remove',
|
||||||
|
value: 'test2, test3',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
channel: 'email',
|
||||||
|
active: true,
|
||||||
|
created_by_id: 1,
|
||||||
|
updated_by_id: 1,
|
||||||
|
)
|
||||||
|
|
||||||
|
PostmasterFilter.create!(
|
||||||
|
name: '03 set tag for email',
|
||||||
|
match: {
|
||||||
|
from: {
|
||||||
|
operator: 'contains',
|
||||||
|
value: 'nobody@example.com',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
perform: {
|
||||||
|
'x-zammad-ticket-tags' => {
|
||||||
|
operator: 'add',
|
||||||
|
value: 'test3',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
channel: 'email',
|
||||||
|
active: true,
|
||||||
|
created_by_id: 1,
|
||||||
|
updated_by_id: 1,
|
||||||
|
)
|
||||||
|
|
||||||
|
PostmasterFilter.create!(
|
||||||
|
name: '04 set tag for email',
|
||||||
|
match: {
|
||||||
|
from: {
|
||||||
|
operator: 'contains',
|
||||||
|
value: 'nobody@example.com',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
perform: {
|
||||||
|
'x-zammad-ticket-tags' => {
|
||||||
|
operator: 'add',
|
||||||
|
value: 'abc1 , abc2 ',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
channel: 'email',
|
||||||
|
active: true,
|
||||||
|
created_by_id: 1,
|
||||||
|
updated_by_id: 1,
|
||||||
|
)
|
||||||
|
|
||||||
|
data = 'From: ME Bob <nobody@example.com>
|
||||||
|
To: customer@example.com
|
||||||
|
Subject: some subject
|
||||||
|
|
||||||
|
Some Text'
|
||||||
|
|
||||||
|
parser = Channel::EmailParser.new
|
||||||
|
ticket, article, user = parser.process({ group_id: group_default.id, trusted: false }, data)
|
||||||
|
tags = Tag.tag_list(object: 'Ticket', o_id: ticket.id)
|
||||||
|
assert_equal('Users', ticket.group.name)
|
||||||
|
assert_equal('2 normal', ticket.priority.name)
|
||||||
|
assert_equal('some subject', ticket.title)
|
||||||
|
assert_equal('nobody@example.com', ticket.customer.email)
|
||||||
|
assert_equal(4, tags.count)
|
||||||
|
assert(tags.include?('test1'))
|
||||||
|
assert(tags.include?('test3'))
|
||||||
|
assert(tags.include?('abc1'))
|
||||||
|
assert(tags.include?('abc2'))
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -115,7 +115,7 @@ class TicketEscalationTest < ActiveSupport::TestCase
|
||||||
ticket.save!
|
ticket.save!
|
||||||
assert_not(ticket.has_changes_to_save?)
|
assert_not(ticket.has_changes_to_save?)
|
||||||
assert(ticket.escalation_at)
|
assert(ticket.escalation_at)
|
||||||
assert_equal((ticket_escalation_at - 30.minutes).to_s, ticket.escalation_at.to_s)
|
assert_in_delta((ticket_escalation_at - 30.minutes).to_i, ticket.escalation_at.to_i, 90)
|
||||||
|
|
||||||
sla.destroy!
|
sla.destroy!
|
||||||
calendar.destroy!
|
calendar.destroy!
|
||||||
|
@ -192,10 +192,10 @@ Some Text"
|
||||||
ticket_p, article_p, user_p, mail = Channel::EmailParser.new.process({}, email)
|
ticket_p, article_p, user_p, mail = Channel::EmailParser.new.process({}, email)
|
||||||
ticket_p.reload
|
ticket_p.reload
|
||||||
assert(ticket_p.escalation_at)
|
assert(ticket_p.escalation_at)
|
||||||
assert_in_delta(ticket_p.first_response_escalation_at.to_i, (ticket_p.created_at + 1.hour).to_i, 120)
|
assert_in_delta(ticket_p.first_response_escalation_at.to_i, (ticket_p.created_at + 1.hour).to_i, 90)
|
||||||
assert_in_delta(ticket_p.update_escalation_at.to_i, (ticket_p.created_at + 3.hours).to_i, 120)
|
assert_in_delta(ticket_p.update_escalation_at.to_i, (ticket_p.created_at + 3.hours).to_i, 90)
|
||||||
assert_in_delta(ticket_p.close_escalation_at.to_i, (ticket_p.created_at + 4.hours).to_i, 120)
|
assert_in_delta(ticket_p.close_escalation_at.to_i, (ticket_p.created_at + 4.hours).to_i, 90)
|
||||||
assert_in_delta(ticket_p.escalation_at.to_i, (ticket_p.created_at + 1.hour).to_i, 120)
|
assert_in_delta(ticket_p.escalation_at.to_i, (ticket_p.created_at + 1.hour).to_i, 90)
|
||||||
|
|
||||||
travel 3.hours
|
travel 3.hours
|
||||||
article = nil
|
article = nil
|
||||||
|
@ -217,10 +217,10 @@ Some Text"
|
||||||
end
|
end
|
||||||
|
|
||||||
ticket_p.reload
|
ticket_p.reload
|
||||||
assert_in_delta(ticket_p.first_response_escalation_at.to_i, (ticket_p.created_at + 1.hour).to_i, 120)
|
assert_in_delta(ticket_p.first_response_escalation_at.to_i, (ticket_p.created_at + 1.hour).to_i, 90)
|
||||||
assert_in_delta(ticket_p.update_escalation_at.to_i, (ticket_p.last_contact_agent_at + 3.hours).to_i, 120)
|
assert_in_delta(ticket_p.update_escalation_at.to_i, (ticket_p.last_contact_agent_at + 3.hours).to_i, 90)
|
||||||
assert_in_delta(ticket_p.close_escalation_at.to_i, (ticket_p.created_at + 4.hours).to_i, 120)
|
assert_in_delta(ticket_p.close_escalation_at.to_i, (ticket_p.created_at + 4.hours).to_i, 90)
|
||||||
assert_in_delta(ticket_p.escalation_at.to_i, (ticket_p.created_at + 4.hours).to_i, 120)
|
assert_in_delta(ticket_p.escalation_at.to_i, (ticket_p.created_at + 4.hours).to_i, 90)
|
||||||
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in a new issue