diff --git a/app/presenters/menu/item.rb b/app/presenters/menu/item.rb index 3cf98f1a11d..bbe9dedb49f 100644 --- a/app/presenters/menu/item.rb +++ b/app/presenters/menu/item.rb @@ -1,5 +1,5 @@ module Menu - Item = Struct.new(:id, :name, :feature, :rbac_feature, :href, :type, :defaults) do + Item = Struct.new(:id, :name, :feature, :rbac_feature, :href, :type, :parent_id) do extend ActiveModel::Naming def self.base_class @@ -10,7 +10,7 @@ def self.base_model model_name end - def initialize(an_id, a_name, features, rbac_feature, href, type = :default, defaults = nil) + def initialize(an_id, a_name, features, rbac_feature, href, type = :default, parent_id = nil) super @parent = nil @name = a_name.kind_of?(Proc) ? a_name : -> { a_name } diff --git a/app/presenters/menu/manager.rb b/app/presenters/menu/manager.rb index ebb9ea8a991..32e7d56ddbf 100644 --- a/app/presenters/menu/manager.rb +++ b/app/presenters/menu/manager.rb @@ -74,23 +74,32 @@ def initialize def merge_sections(sections) sections.each do |section| position = nil + + parent = if section.parent_id && @id_to_section.key?(section.parent_id) + @id_to_section[section.parent_id].items + else + @menu + end + if section.before - position = @menu.index { |existing_section| existing_section.id == section.before } + position = parent.index { |existing_section| existing_section.id == section.before } end if position - @menu.insert(position, section) + parent.insert(position, section) else - @menu << section + parent << section end end end def merge_items(items) items.each do |item| - raise InvalidMenuDefinition, 'Invalid parent' unless @id_to_section.key?(item.parent) - @id_to_section[item.parent].items << item - item.parent = @id_to_section[item.parent] + parent = @id_to_section[item.parent_id] + raise InvalidMenuDefinition, 'Invalid parent' if parent.nil? + + parent.items << item + item.parent = parent end end diff --git a/app/presenters/menu/section.rb b/app/presenters/menu/section.rb index 20fcfeaf603..7a9ca97b8b6 100644 --- a/app/presenters/menu/section.rb +++ b/app/presenters/menu/section.rb @@ -1,5 +1,5 @@ module Menu - Section = Struct.new(:id, :name, :icon, :items, :placement, :before, :type, :href) do + Section = Struct.new(:id, :name, :icon, :items, :placement, :before, :type, :href, :parent_id) do extend ActiveModel::Naming def self.base_class diff --git a/app/presenters/menu/yaml_loader.rb b/app/presenters/menu/yaml_loader.rb index 92be5817435..e954d1bb4b8 100644 --- a/app/presenters/menu/yaml_loader.rb +++ b/app/presenters/menu/yaml_loader.rb @@ -33,13 +33,15 @@ def create_custom_menu_item(properties) raise Menu::Manager::InvalidMenuDefinition, "incomplete definition -- missing #{property}" if properties[property].blank? end - item = Item.new(properties['id'], - properties['name'], - properties['feature'], - rbac, - properties['href'], - item_type) - item.parent = properties['parent'].to_sym + item = Item.new( + properties['id'], + properties['name'], + properties['feature'], + rbac, + properties['href'], + item_type, + properties['parent'].to_sym + ) item end diff --git a/spec/presenters/menu/custom_loader_spec.rb b/spec/presenters/menu/custom_loader_spec.rb new file mode 100644 index 00000000000..86c3a0c6af3 --- /dev/null +++ b/spec/presenters/menu/custom_loader_spec.rb @@ -0,0 +1,66 @@ +describe Menu::CustomLoader do + before :each do + Singleton.__init__(Menu::Manager) + Singleton.__init__(Menu::CustomLoader) + end + + context '.register' do + it 'loads custom menu items' do + # create custom section with 2 custom items + described_class.register( + Menu::Section.new(:spike, N_('Plugin'), 'fa fa-map-pin', [ + Menu::Item.new('plug1', N_('Test'), 'miq_report', {:feature => 'miq_report', :any => true}, '/plug'), + Menu::Item.new('plug2', N_('Demo'), 'miq_report', {:feature => 'miq_report', :any => true}, '/demo') + ]) + ) + + expect(Menu::Manager.item('plug1')).to be + expect(Menu::Manager.item('plug2')).to be + end + + it 'loads a custom menu item under an existing section' do + # create custom item placed in an existing section 'vi' (Cloud Intel) + described_class.register( + Menu::Item.new('plug3', N_('Plug Item'), 'miq_report', {:feature => 'miq_report', :any => true}, '/demo', :default, :vi) + ) + + item = Menu::Manager.item('plug3') + expect(item).to be + expect(item.parent.id).to eq(:vi) + end + + it 'loads a custom menu section and places it before an existing section' do + # create custom section and place it before existing section 'compute' (Compute) + described_class.register( + Menu::Section.new(:spike3, N_('Plugin 2'), 'fa fa-map-pin', [ + Menu::Item.new('plug4', N_('Demo'), 'miq_report', {:feature => 'miq_report', :any => true}, '/demo') + ], :default, :compute) + ) + + item = Menu::Manager.item('plug4') + expect(item).to be + + menu = Menu::Manager.instance.instance_eval { @menu } + compute_index = menu.find_index { |s| s.id == :compute } + + expect(menu[compute_index - 1].id).to eq(:spike3) + end + + it 'loads a custom menu section and places it at a given position in inside an existing section' do + # create custom section and place it inside an existing section 'compute' (Compute), before existing subsection 'clo' (Cloud) + described_class.register( + Menu::Section.new(:spike3, N_('Nested section after'), 'fa fa-map-pin', [ + Menu::Item.new('plug5', N_('Test item'), 'miq_report', {:feature => 'miq_report', :any => true}, '/demo') + ], :default, :clo, :default, nil, :compute) + ) + + item = Menu::Manager.item('plug5') + expect(item).to be + + compute_section = Menu::Manager.section(:compute) + spike_index = compute_section.items.find_index { |i| i.id == :spike3 } + clo_index = compute_section.items.find_index { |i| i.id == :clo } + expect(spike_index).to eq(clo_index - 1) + end + end +end