Fixed issue #1660 - ObjectManager Attribute of type Tree-Select mixes up children.
This commit is contained in:
parent
1e04b35d5a
commit
7680c51624
6 changed files with 574 additions and 13 deletions
|
@ -1,9 +1,8 @@
|
|||
# coffeelint: disable=duplicate_key
|
||||
treeParams = (e, params) ->
|
||||
tree = []
|
||||
lastLevel = 0
|
||||
lastLevels = []
|
||||
valueLevels = []
|
||||
previousValueLevels = []
|
||||
|
||||
$(e.target).closest('.modal').find('.js-treeTable .js-key').each( ->
|
||||
$element = $(@)
|
||||
|
@ -19,20 +18,19 @@ treeParams = (e, params) ->
|
|||
lastLevels[level-1].children.push item
|
||||
else
|
||||
console.log('ERROR', item)
|
||||
if level is 0
|
||||
|
||||
valueLevels = []
|
||||
else if lastLevel is level
|
||||
valueLevels.pop()
|
||||
else if lastLevel > level
|
||||
down = lastLevel - level
|
||||
for count in [1..down]
|
||||
valueLevels.pop()
|
||||
if lastLevel <= level
|
||||
valueLevels.push name
|
||||
# add previous level values
|
||||
if level > 0
|
||||
for previousLevel in [0..level - 1]
|
||||
valueLevels.push(previousValueLevels[previousLevel])
|
||||
|
||||
# add current level value
|
||||
valueLevels.push(name)
|
||||
|
||||
item.value = valueLevels.join('::')
|
||||
lastLevels[level] = item
|
||||
lastLevel = level
|
||||
previousValueLevels = valueLevels
|
||||
|
||||
)
|
||||
if tree[0]
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
class Issue1660FixTreeSelectConfigurations < ActiveRecord::Migration[5.1]
|
||||
def change
|
||||
|
||||
# return if it's a new setup
|
||||
return if !Setting.find_by(name: 'system_init_done')
|
||||
|
||||
return if attributes.blank?
|
||||
attributes.each do |attribute|
|
||||
|
||||
next if attribute.data_option.blank?
|
||||
next if attribute.data_option[:options].blank?
|
||||
|
||||
fixed_options = fix(attribute.data_option[:options])
|
||||
|
||||
attribute.data_option[:options] = fixed_options
|
||||
|
||||
attribute.save!
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def attributes
|
||||
@attributes ||= ObjectManager::Attribute.where(data_type: 'tree_select')
|
||||
end
|
||||
|
||||
def fix(options, namespace = nil)
|
||||
|
||||
options.tap do |ref|
|
||||
|
||||
ref.each do |option|
|
||||
|
||||
option_namespace = Array(namespace.dup)
|
||||
option_namespace.push(option['name'])
|
||||
|
||||
option['value'] = option_namespace.join('::')
|
||||
|
||||
next if option['children'].blank?
|
||||
|
||||
option['children'] = fix(option['children'], option_namespace)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -16,6 +16,7 @@ if [ "$LEVEL" == '1' ]; then
|
|||
rm test/browser/abb_one_group_test.rb
|
||||
rm test/browser/admin_channel_email_test.rb
|
||||
rm test/browser/admin_object_manager_test.rb
|
||||
rm test/browser/admin_object_manager_tree_select_test.rb
|
||||
rm test/browser/admin_overview_test.rb
|
||||
rm test/browser/admin_role_test.rb
|
||||
# test/browser/agent_navigation_and_title_test.rb
|
||||
|
@ -79,6 +80,7 @@ elif [ "$LEVEL" == '2' ]; then
|
|||
# test/browser/abb_one_group_test.rb
|
||||
rm test/browser/admin_channel_email_test.rb
|
||||
rm test/browser/admin_object_manager_test.rb
|
||||
rm test/browser/admin_object_manager_tree_select_test.rb
|
||||
rm test/browser/admin_overview_test.rb
|
||||
#rm test/browser/admin_role_test.rb
|
||||
rm test/browser/agent_navigation_and_title_test.rb
|
||||
|
@ -142,6 +144,7 @@ elif [ "$LEVEL" == '3' ]; then
|
|||
# test/browser/abb_one_group_test.rb
|
||||
rm test/browser/admin_channel_email_test.rb
|
||||
rm test/browser/admin_object_manager_test.rb
|
||||
rm test/browser/admin_object_manager_tree_select_test.rb
|
||||
rm test/browser/admin_overview_test.rb
|
||||
rm test/browser/admin_role_test.rb
|
||||
rm test/browser/agent_navigation_and_title_test.rb
|
||||
|
@ -205,6 +208,7 @@ elif [ "$LEVEL" == '4' ]; then
|
|||
# test/browser/abb_one_group_test.rb
|
||||
rm test/browser/admin_channel_email_test.rb
|
||||
rm test/browser/admin_object_manager_test.rb
|
||||
rm test/browser/admin_object_manager_tree_select_test.rb
|
||||
rm test/browser/admin_overview_test.rb
|
||||
rm test/browser/admin_role_test.rb
|
||||
rm test/browser/agent_navigation_and_title_test.rb
|
||||
|
@ -267,6 +271,7 @@ elif [ "$LEVEL" == '5' ]; then
|
|||
# test/browser/abb_one_group_test.rb
|
||||
# test/browser/admin_channel_email_test.rb
|
||||
# test/browser/admin_object_manager_test.rb
|
||||
# test/browser/admin_object_manager_tree_select_test.rb
|
||||
# test/browser/admin_overview_test.rb
|
||||
rm test/browser/admin_role_test.rb
|
||||
rm test/browser/agent_navigation_and_title_test.rb
|
||||
|
@ -332,6 +337,7 @@ elif [ "$LEVEL" == '6' ]; then
|
|||
rm test/browser/abb_one_group_test.rb
|
||||
rm test/browser/admin_channel_email_test.rb
|
||||
rm test/browser/admin_object_manager_test.rb
|
||||
rm test/browser/admin_object_manager_tree_select_test.rb
|
||||
rm test/browser/admin_overview_test.rb
|
||||
rm test/browser/admin_role_test.rb
|
||||
rm test/browser/agent_navigation_and_title_test.rb
|
||||
|
|
|
@ -0,0 +1,224 @@
|
|||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Issue1660FixTreeSelectConfigurations, type: :db_migration do
|
||||
|
||||
it 'corrects broken data_option options' do
|
||||
|
||||
# as provided in issue #1775
|
||||
expected = [
|
||||
{
|
||||
'name' => 'Blaak',
|
||||
'value' => 'Blaak',
|
||||
'children' => [
|
||||
{
|
||||
'name' => 'BL.-1',
|
||||
'value' => 'Blaak::BL.-1',
|
||||
'children' => [
|
||||
{
|
||||
'name' => 'BL.-1.3',
|
||||
'value' => 'Blaak::BL.-1::BL.-1.3',
|
||||
},
|
||||
{
|
||||
'name' => 'BL.-1.19',
|
||||
'value' => 'Blaak::BL.-1::BL.-1.19',
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
'name' => 'BL.0',
|
||||
'value' => 'Blaak::BL.0',
|
||||
'children' => [
|
||||
{
|
||||
'name' => 'BL.00.10a',
|
||||
'value' => 'Blaak::BL.0::BL.00.10a',
|
||||
},
|
||||
{
|
||||
'name' => 'BL.00.7',
|
||||
'value' => 'Blaak::BL.0::BL.00.7',
|
||||
},
|
||||
{
|
||||
'name' => 'BL.01.18',
|
||||
'value' => 'Blaak::BL.0::BL.01.18',
|
||||
},
|
||||
{
|
||||
'name' => 'BL.00.12',
|
||||
'value' => 'Blaak::BL.0::BL.00.12',
|
||||
},
|
||||
{
|
||||
'name' => 'BL.e0.3',
|
||||
'value' => 'Blaak::BL.0::BL.e0.3',
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
'name' => 'BL.1',
|
||||
'value' => 'Blaak::BL.1',
|
||||
'children' => [
|
||||
{
|
||||
'name' => 'BL.01.i',
|
||||
'value' => 'Blaak::BL.1::BL.01.i',
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
'name' => 'BL.2',
|
||||
'value' => 'Blaak::BL.2',
|
||||
'children' => [
|
||||
{
|
||||
'name' => 'BL.02.2',
|
||||
'value' => 'Blaak::BL.2::BL.02.2',
|
||||
},
|
||||
{
|
||||
'name' => 'BL.02.4',
|
||||
'value' => 'Blaak::BL.2::BL.02.4',
|
||||
},
|
||||
{
|
||||
'name' => 'BL.02.5',
|
||||
'value' => 'Blaak::BL.2::BL.02.5',
|
||||
},
|
||||
{
|
||||
'name' => 'BL.02.6',
|
||||
'value' => 'Blaak::BL.2::BL.02.6',
|
||||
},
|
||||
{
|
||||
'name' => 'BL.02.7',
|
||||
'value' => 'Blaak::BL.2::BL.02.7',
|
||||
},
|
||||
]
|
||||
},
|
||||
],
|
||||
}
|
||||
]
|
||||
|
||||
broken = [
|
||||
{
|
||||
'name' => 'Blaak',
|
||||
'value' => 'Blaak',
|
||||
'children' => [
|
||||
{
|
||||
'name' => 'BL.-1',
|
||||
'value' => 'Blaak::BL.-1',
|
||||
'children' => [
|
||||
{
|
||||
'name' => 'BL.-1.3',
|
||||
'value' => 'Blaak::BL.2::BL.-1.3',
|
||||
},
|
||||
{
|
||||
'name' => 'BL.-1.19',
|
||||
'value' => 'Blaak::BL.2::BL.-1.19',
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
'name' => 'BL.0',
|
||||
'value' => 'Blaak::BL.0',
|
||||
'children' => [
|
||||
{
|
||||
'name' => 'BL.00.10a',
|
||||
'value' => 'Blaak::BL.2::BL.00.10a',
|
||||
},
|
||||
{
|
||||
'name' => 'BL.00.7',
|
||||
'value' => 'Blaak::BL.2::BL.00.7',
|
||||
},
|
||||
{
|
||||
'name' => 'BL.01.18',
|
||||
'value' => 'Blaak::BL.2::BL.01.18',
|
||||
},
|
||||
{
|
||||
'name' => 'BL.00.12',
|
||||
'value' => 'Blaak::BL.2::BL.00.12',
|
||||
},
|
||||
{
|
||||
'name' => 'BL.e0.3',
|
||||
'value' => 'Blaak::BL.2::BL.e0.3',
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
'name' => 'BL.1',
|
||||
'value' => 'Blaak::BL.1',
|
||||
'children' => [
|
||||
{
|
||||
'name' => 'BL.01.i',
|
||||
'value' => 'Blaak::BL.2::BL.01.i',
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
'name' => 'BL.2',
|
||||
'value' => 'Blaak::BL.2',
|
||||
'children' => [
|
||||
{
|
||||
'name' => 'BL.02.2',
|
||||
'value' => 'Blaak::BL.2::BL.02.2',
|
||||
},
|
||||
{
|
||||
'name' => 'BL.02.4',
|
||||
'value' => 'Blaak::BL.2::BL.02.4',
|
||||
},
|
||||
{
|
||||
'name' => 'BL.02.5',
|
||||
'value' => 'Blaak::BL.2::BL.02.5',
|
||||
},
|
||||
{
|
||||
'name' => 'BL.02.6',
|
||||
'value' => 'Blaak::BL.2::BL.02.6',
|
||||
},
|
||||
{
|
||||
'name' => 'BL.02.7',
|
||||
'value' => 'Blaak::BL.2::BL.02.7',
|
||||
},
|
||||
]
|
||||
},
|
||||
],
|
||||
}
|
||||
]
|
||||
|
||||
attribute = create(:object_manager_attribute_tree_select, data_option: { options: broken })
|
||||
|
||||
expect do
|
||||
migrate
|
||||
end.to change {
|
||||
attribute.reload.data_option[:options]
|
||||
}
|
||||
|
||||
expect(attribute.data_option[:options]).to eq(expected)
|
||||
end
|
||||
|
||||
it 'performs no action for new systems', system_init_done: false do
|
||||
migrate do |instance|
|
||||
expect(instance).not_to receive(:attributes)
|
||||
end
|
||||
end
|
||||
|
||||
it 'skips blank data_option' do
|
||||
attribute = create(:object_manager_attribute_tree_select, data_option: {})
|
||||
|
||||
expect do
|
||||
migrate
|
||||
end.not_to change {
|
||||
attribute.reload.data_option
|
||||
}
|
||||
end
|
||||
|
||||
it 'skips blank data_option options' do
|
||||
attribute = create(:object_manager_attribute_tree_select, data_option: { options: [] })
|
||||
|
||||
expect do
|
||||
migrate
|
||||
end.not_to change {
|
||||
attribute.reload.data_option[:options]
|
||||
}
|
||||
end
|
||||
|
||||
it 'does not change correct data_option options' do
|
||||
attribute = create(:object_manager_attribute_tree_select)
|
||||
|
||||
expect do
|
||||
migrate
|
||||
end.not_to change {
|
||||
attribute.reload.data_option[:options]
|
||||
}
|
||||
end
|
||||
end
|
221
test/browser/admin_object_manager_tree_select_test.rb
Normal file
221
test/browser/admin_object_manager_tree_select_test.rb
Normal file
|
@ -0,0 +1,221 @@
|
|||
require 'browser_test_helper'
|
||||
|
||||
class AdminObjectManagerTreeSelectTest < TestCase
|
||||
|
||||
def test_basic_a
|
||||
|
||||
@browser = browser_instance
|
||||
login(
|
||||
username: 'master@example.com',
|
||||
password: 'test',
|
||||
url: browser_url,
|
||||
)
|
||||
tasks_close_all()
|
||||
|
||||
object_manager_attribute_create(
|
||||
data: {
|
||||
name: 'browser_test_tree_select1',
|
||||
display: 'Browser Test TreeSelect1',
|
||||
data_type: 'Tree Select',
|
||||
data_option: {
|
||||
options: {
|
||||
'Incident' => {
|
||||
'Hardware' => {
|
||||
'Monitor' => {},
|
||||
'Mouse' => {},
|
||||
'Keyboard' => {},
|
||||
},
|
||||
'Softwareproblem' => {
|
||||
'CRM' => {},
|
||||
'EDI' => {},
|
||||
'SAP' => {
|
||||
'Authentication' => {},
|
||||
'Not reachable' => {},
|
||||
},
|
||||
'MS Office' => {
|
||||
'Excel' => {},
|
||||
'PowerPoint' => {},
|
||||
'Word' => {},
|
||||
'Outlook' => {},
|
||||
},
|
||||
},
|
||||
},
|
||||
'Service request' => {
|
||||
'New software requirement' => {},
|
||||
'New hardware' => {},
|
||||
'Consulting' => {},
|
||||
},
|
||||
'Change request' => {},
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
correct_options = [
|
||||
{
|
||||
'name' => 'Incident',
|
||||
'value' => 'Incident',
|
||||
'children' => [
|
||||
{
|
||||
'name' => 'Hardware',
|
||||
'value' => 'Incident::Hardware',
|
||||
'children' => [
|
||||
{
|
||||
'name' => 'Monitor',
|
||||
'value' => 'Incident::Hardware::Monitor'
|
||||
},
|
||||
{
|
||||
'name' => 'Mouse',
|
||||
'value' => 'Incident::Hardware::Mouse'
|
||||
},
|
||||
{
|
||||
'name' => 'Keyboard',
|
||||
'value' => 'Incident::Hardware::Keyboard'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
'name' => 'Softwareproblem',
|
||||
'value' => 'Incident::Softwareproblem',
|
||||
'children' => [
|
||||
{
|
||||
'name' => 'CRM',
|
||||
'value' => 'Incident::Softwareproblem::CRM'
|
||||
},
|
||||
{
|
||||
'name' => 'EDI',
|
||||
'value' => 'Incident::Softwareproblem::EDI'
|
||||
},
|
||||
{
|
||||
'name' => 'SAP',
|
||||
'value' => 'Incident::Softwareproblem::SAP',
|
||||
'children' => [
|
||||
{
|
||||
'name' => 'Authentication',
|
||||
'value' => 'Incident::Softwareproblem::SAP::Authentication'
|
||||
},
|
||||
{
|
||||
'name' => 'Not reachable',
|
||||
'value' => 'Incident::Softwareproblem::SAP::Not reachable'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
'name' => 'MS Office',
|
||||
'value' => 'Incident::Softwareproblem::MS Office',
|
||||
'children' => [
|
||||
{
|
||||
'name' => 'Excel',
|
||||
'value' => 'Incident::Softwareproblem::MS Office::Excel'
|
||||
},
|
||||
{
|
||||
'name' => 'PowerPoint',
|
||||
'value' => 'Incident::Softwareproblem::MS Office::PowerPoint'
|
||||
},
|
||||
{
|
||||
'name' => 'Word',
|
||||
'value' => 'Incident::Softwareproblem::MS Office::Word'
|
||||
},
|
||||
{
|
||||
'name' => 'Outlook',
|
||||
'value' => 'Incident::Softwareproblem::MS Office::Outlook'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
'name' => 'Service request',
|
||||
'value' => 'Service request',
|
||||
'children' => [
|
||||
{
|
||||
'name' => 'New software requirement',
|
||||
'value' => 'Service request::New software requirement'
|
||||
},
|
||||
{
|
||||
'name' => 'New hardware',
|
||||
'value' => 'Service request::New hardware'
|
||||
},
|
||||
{
|
||||
'name' => 'Consulting',
|
||||
'value' => 'Service request::Consulting'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
'name' => 'Change request',
|
||||
'value' => 'Change request'
|
||||
}
|
||||
]
|
||||
|
||||
created_attribute = ObjectManager::Attribute.last
|
||||
assert_equal(correct_options, created_attribute.data_option[:options])
|
||||
|
||||
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',
|
||||
)
|
||||
|
||||
# discard new attribute
|
||||
click(css: 'a[href="#manage"]')
|
||||
click(css: 'a[href="#system/object_manager"]')
|
||||
watch_for(
|
||||
css: '.content.active table',
|
||||
value: 'browser_test_tree_select1',
|
||||
)
|
||||
match_not(
|
||||
css: '.content.active',
|
||||
value: 'Database Update required',
|
||||
)
|
||||
object_manager_attribute_delete(
|
||||
data: {
|
||||
name: 'browser_test_tree_select1',
|
||||
},
|
||||
)
|
||||
watch_for(
|
||||
css: '.content.active',
|
||||
value: 'Database Update required',
|
||||
)
|
||||
watch_for(
|
||||
css: '.content.active table',
|
||||
value: 'browser_test_tree_select1',
|
||||
)
|
||||
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',
|
||||
)
|
||||
match_not(
|
||||
css: '.content.active table',
|
||||
value: 'browser_test_tree_select1',
|
||||
)
|
||||
end
|
||||
|
||||
end
|
|
@ -3362,6 +3362,11 @@ wait untill text in selector disabppears
|
|||
element.clear
|
||||
element.send_keys(data[:data_option][:options][:false])
|
||||
# rubocop:enable Lint/BooleanSymbol
|
||||
elsif data[:data_type] == 'Tree Select'
|
||||
add_tree_options(
|
||||
instance: instance,
|
||||
options: data[:data_option][:options],
|
||||
)
|
||||
else
|
||||
data[:data_option][:options].each do |key, value|
|
||||
element = instance.find_elements(css: '.modal .js-Table .js-key').last
|
||||
|
@ -3695,4 +3700,67 @@ wait untill text in selector disabppears
|
|||
return if params[:mute_log]
|
||||
puts "#{Time.zone.now}/#{method}: #{params.inspect}"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def add_tree_options(instance:, options:)
|
||||
|
||||
# first level entries have to get added in regular order
|
||||
options.each_key.with_index do |option, index|
|
||||
|
||||
if index != 0
|
||||
element = instance.find_elements(css: '.modal .js-treeTable .js-addRow')[index - 1]
|
||||
element.click
|
||||
end
|
||||
|
||||
element = instance.find_elements(css: '.modal .js-treeTable .js-key')[index]
|
||||
element.clear
|
||||
element.send_keys(option)
|
||||
end
|
||||
|
||||
add_sub_tree_recursion(
|
||||
instance: instance,
|
||||
options: options,
|
||||
)
|
||||
end
|
||||
|
||||
def add_sub_tree_recursion(instance:, options:, offset: 0)
|
||||
options.each_value.inject(offset) do |child_offset, children|
|
||||
|
||||
child_offset += 1
|
||||
|
||||
# put your recursion glasses on 8-)
|
||||
add_sub_tree_options(
|
||||
instance: instance,
|
||||
options: children,
|
||||
offset: child_offset,
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def add_sub_tree_options(instance:, options:, offset:)
|
||||
|
||||
# sub level entries have to get added in reversed order
|
||||
level_options = options.to_a.reverse.to_h.keys
|
||||
|
||||
level_options.each do |option|
|
||||
|
||||
# sub level entries have to get added via 'add child row' link
|
||||
click_index = offset - 1
|
||||
|
||||
element = instance.find_elements(css: '.modal .js-treeTable .js-addChild')[click_index]
|
||||
element.click
|
||||
|
||||
element = instance.find_elements(css: '.modal .js-treeTable .js-key')[offset]
|
||||
element.clear
|
||||
element.send_keys(option)
|
||||
sleep 0.25
|
||||
end
|
||||
|
||||
add_sub_tree_recursion(
|
||||
instance: instance,
|
||||
options: options,
|
||||
offset: offset,
|
||||
)
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue