diff --git a/app/assets/javascripts/miq_application.js b/app/assets/javascripts/miq_application.js index 4f4c8fd6a16..12b406dcc93 100644 --- a/app/assets/javascripts/miq_application.js +++ b/app/assets/javascripts/miq_application.js @@ -1484,6 +1484,10 @@ function miqToolbarOnClick(_e) { } else { params = miqSerializeForm(button.data('url_parms')); } + } else if (button.data('url_parms').match("id=LIST")) { + // this is used by custom buttons in lists + params = button.data('url_parms').split("?")[1] + + "&miq_grid_checks=" + ManageIQ.gridChecks.join(','); } else { params = button.data('url_parms').split("?")[1]; } diff --git a/app/controllers/application_controller/buttons.rb b/app/controllers/application_controller/buttons.rb index 8d0a3517d79..2687dcb7844 100644 --- a/app/controllers/application_controller/buttons.rb +++ b/app/controllers/application_controller/buttons.rb @@ -106,11 +106,14 @@ def automate_button_field_changed @edit[:new][:name] = params[:name] if params[:name] @edit[:new][:display] = params[:display] == "1" if params[:display] @edit[:new][:open_url] = params[:open_url] == "1" if params[:open_url] + @edit[:new][:display_for] = params[:display_for] if params[:display_for] + @edit[:new][:submit_how] = params[:submit_how] if params[:submit_how] @edit[:new][:description] = params[:description] if params[:description] @edit[:new][:button_image] = params[:button_image].to_i if params[:button_image] @edit[:new][:dialog_id] = params[:dialog_id] if params[:dialog_id] visibility_box_edit end + render :update do |page| page << javascript_prologue if params.key?(:instance_name) || params.key?(:other_name) || params.key?(:target_class) @@ -237,7 +240,7 @@ def ab_group_delete end end - private ########### + private BASE_MODEL_EXPLORER_CLASSES = [Vm, MiqTemplate, Service].freeze APPLIES_TO_CLASS_BASE_MODELS = %w(CloudTenant EmsCluster ExtManagementSystem Host MiqTemplate Service ServiceTemplate Storage Vm).freeze @@ -260,7 +263,17 @@ def custom_button_done render_flash(_('No url was returned from automate.'), :error) end end - private :custom_button_done + + def custom_buttons_invoke(button, objs) + if objs.length > 1 && + (button.options && button.options.key?(:submit_how) && button.options[:submit_how].to_s == 'all') + # FIXME: wee need something like this from the core/automate: + # button.invoke(:object_id => objs.map(&:id), :object_type => objs[0].class.base_class.name) + raise "Not implemented." + else + objs.each { |obj| button.invoke(obj) } + end + end def custom_buttons button = CustomButton.find_by_id(params[:button_id]) @@ -268,20 +281,40 @@ def custom_buttons @explorer = true if BASE_MODEL_EXPLORER_CLASSES.include?(cls) - obj = cls.find_by_id(params[:id].to_i) + if params[:id].to_s == 'LIST' + objs = Rbac.filtered(cls.where(:id => find_checked_items)) + obj = objs.first + else + obj = Rbac.filtered_object(cls.find(params[:id].to_i)) + objs = [obj] + end + + if objs.empty? + render_flash(_("Error executing custom button: No item was selected."), :error) + return + end + @right_cell_text = _("%{record} - \"%{button_text}\"") % {:record => obj.name, :button_text => button.name} + if button.resource_action.dialog_id - options = {} - options[:header] = @right_cell_text - options[:target_id] = obj.id - options[:target_kls] = obj.class.name + options = { + :header => @right_cell_text, + :target_id => obj.id, + :target_ids => objs.collect(&:id), + :target_kls => obj.class.name, + } + dialog_initialize(button.resource_action, options) + elsif button.options && button.options.key?(:open_url) && button.options[:open_url] + # not supported for objs: cannot do wait for task for multiple tasks task_id = button.invoke_async(obj) initiate_wait_for_task(:task_id => task_id, :action => :custom_button_done) + else begin - button.invoke(obj) + custom_buttons_invoke(button, objs) + rescue StandardError => bang add_flash(_("Error executing: \"%{task_description}\" %{error_message}") % {:task_description => params[:desc], :error_message => bang.message}, :error) @@ -757,8 +790,10 @@ def button_set_record_vars(button) button[:options][:button_image] ||= {} button[:options][:button_image] = @edit[:new][:button_image] end - button[:options][:display] = @edit[:new][:display] - button[:options][:open_url] = @edit[:new][:open_url] + + %i(display open_url display_for submit_how).each do |key| + button[:options][key] = @edit[:new][key] + end button.visibility ||= {} if @edit[:new][:visibility_typ] == "role" roles = [] @@ -846,6 +881,8 @@ def button_set_form_vars :button_image => @custom_button.options.try(:[], :button_image).to_s, :display => @custom_button.options.try(:[], :display).nil? ? true : @custom_button.options[:display], :open_url => @custom_button.options.try(:[], :open_url) ? @custom_button.options[:open_url] : false, + :display_for => @custom_button.options.try(:[], :display_for) ? @custom_button.options[:display_for] : 'single', + :submit_how => @custom_button.options.try(:[], :submit_how) ? @custom_button.options[:submit_how] : 'one', :object_message => @custom_button.uri_message || "create", ) @edit[:current] = copy_hash(@edit[:new]) diff --git a/app/controllers/mixins/custom_buttons.rb b/app/controllers/mixins/custom_buttons.rb index 14f56816beb..52317eeba88 100644 --- a/app/controllers/mixins/custom_buttons.rb +++ b/app/controllers/mixins/custom_buttons.rb @@ -1,27 +1,30 @@ module Mixins::CustomButtons extend ActiveSupport::Concern - def custom_toolbar? + def custom_toolbar return nil unless self.class.instance_eval { @custom_buttons } - @explorer ? custom_toolbar_explorer? : custom_toolbar_simple? + @explorer ? custom_toolbar_explorer : custom_toolbar_simple end - def custom_toolbar_explorer? - if x_tree # Make sure we have the trees defined - if x_node == "root" || # If on a root, create placeholder toolbar - !@record # or no record showing - :blank - elsif @display == "main" - true + def custom_toolbar_explorer + if x_tree + if @display == "main" && @record + Mixins::CustomButtons::Result.new(:single) + elsif @lastaction == "show_list" + Mixins::CustomButtons::Result.new(:list) else - :blank + 'blank_view_tb' end end end - def custom_toolbar_simple? - @record && @lastaction == "show" && @display == "main" + def custom_toolbar_simple + if @record && @lastaction == "show" && @display == "main" + Mixins::CustomButtons::Result.new(:single) + elsif @lastaction == "show_list" + Mixins::CustomButtons::Result.new(:list) + end end class_methods do diff --git a/app/controllers/mixins/custom_buttons/result.rb b/app/controllers/mixins/custom_buttons/result.rb new file mode 100644 index 00000000000..4501c1ef39c --- /dev/null +++ b/app/controllers/mixins/custom_buttons/result.rb @@ -0,0 +1,13 @@ +module Mixins::CustomButtons + Result = Struct.new(:plural_form) do + def plural_form_matches(button) + if plural_form == :list + %w(list both).include?(button.options.try(:[], :display_for)) + + else # :single + [nil, 'single', 'both'].include?(button.options.try(:[], :display_for)) + + end + end + end +end diff --git a/app/controllers/service_controller.rb b/app/controllers/service_controller.rb index 50f59e09717..b262864cdaa 100644 --- a/app/controllers/service_controller.rb +++ b/app/controllers/service_controller.rb @@ -285,8 +285,9 @@ def replace_right_cell(options = {}) record_showing = type && ["Service"].include?(TreeBuilder.get_model_for_prefix(type)) if x_active_tree == :svcs_tree && !@in_a_form && !@sb[:action] if record_showing && @sb[:action].nil? - cb_tb = build_toolbar("custom_buttons_tb") + cb_tb = build_toolbar(Mixins::CustomButtons::Result.new(:single)) else + cb_tb = build_toolbar(Mixins::CustomButtons::Result.new(:list)) v_tb = build_toolbar("x_gtl_view_tb") end c_tb = build_toolbar(center_toolbar_filename) diff --git a/app/controllers/vm_common.rb b/app/controllers/vm_common.rb index 26d3ff45905..33839e21d25 100644 --- a/app/controllers/vm_common.rb +++ b/app/controllers/vm_common.rb @@ -1157,6 +1157,11 @@ def get_node_info(treenodeid) end # Add adv search filter to header @right_cell_text += @edit[:adv_search_applied][:text] if @edit && @edit[:adv_search_applied] + + # save model being displayed for custom buttons + @tree_selected_model = if model.present? + model.constantize + end end if @edit && @edit.fetch_path(:adv_search_applied, :qs_exp) # If qs is active, save it in history @@ -1197,9 +1202,10 @@ def replace_right_cell(options = {}) record_showing = type && ["Vm", "MiqTemplate"].include?(TreeBuilder.get_model_for_prefix(type)) c_tb = build_toolbar(center_toolbar_filename) # Use vm or template tb if record_showing - cb_tb = build_toolbar("custom_buttons_tb") + cb_tb = build_toolbar(Mixins::CustomButtons::Result.new(:single)) v_tb = build_toolbar("x_summary_view_tb") else + cb_tb = build_toolbar(Mixins::CustomButtons::Result.new(:list)) v_tb = build_toolbar("x_gtl_view_tb") end elsif ["compare", "drift"].include?(@sb[:action]) diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index ad8a30476af..8e4add5a1d7 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -718,11 +718,7 @@ def calculate_toolbars end toolbars['center_tb'] = center_toolbar_filename - - custom_toolbar = controller.custom_toolbar? - if custom_toolbar - toolbars['custom_tb'] = custom_toolbar == :blank ? 'blank_view_tb' : 'custom_buttons_tb' - end + toolbars['custom_tb'] = controller.custom_toolbar toolbars['view_tb'] = inner_layout_present? ? x_view_toolbar_filename : view_toolbar_filename toolbars diff --git a/app/helpers/application_helper/toolbar_builder.rb b/app/helpers/application_helper/toolbar_builder.rb index 288724fdb4b..854170cb6da 100644 --- a/app/helpers/application_helper/toolbar_builder.rb +++ b/app/helpers/application_helper/toolbar_builder.rb @@ -54,11 +54,19 @@ def predefined_toolbar_class(tb_name) class_name.constantize end + def controller + @view_context.respond_to?(:controller) ? @view_context.controller : @view_context + end + + def model_for_custom_toolbar + controller.instance_eval { @tree_selected_model } || controller.class.model + end + # According to toolbar name in parameter `toolbar_name` either returns class # for generic toolbar, or starts building custom toolbar def toolbar_class(toolbar_name) - if toolbar_name == "custom_buttons_tb" - custom_toolbar_class(@record) + if Mixins::CustomButtons::Result === toolbar_name + custom_toolbar_class(toolbar_name) else predefined_toolbar_class(toolbar_name) end @@ -227,10 +235,11 @@ def group_skipped?(name) x_edit_view_tb history_main ems_container_dashboard ems_infra_dashboard).include?(name) end - def create_custom_button_hash(input, record, options = {}) + def create_custom_button(input, model, record, options = {}) options[:enabled] = true unless options.key?(:enabled) button_id = input[:id] button_name = input[:name].to_s + record_id = record.present? ? record.id : 'LIST' button = { :id => "custom__custom_#{button_id}", :type => :button, @@ -239,13 +248,14 @@ def create_custom_button_hash(input, record, options = {}) :enabled => options[:enabled], :klass => ApplicationHelper::Button::ButtonWithoutRbacCheck, :url => "button", - :url_parms => "?id=#{record.id}&button_id=#{button_id}&cls=#{record.class}&pressed=custom_button&desc=#{button_name}" + :url_parms => "?id=#{record_id}&button_id=#{button_id}&cls=#{model}&pressed=custom_button&desc=#{button_name}" } button[:text] = button_name if input[:text_display] button end def create_raw_custom_button_hash(cb, record) + record_id = record.present? ? record.id : 'LIST' { :id => cb.id, :class => cb.applies_to_class, @@ -253,19 +263,19 @@ def create_raw_custom_button_hash(cb, record) :name => cb.name, :image => cb.options[:button_image], :text_display => cb.options.key?(:display) ? cb.options[:display] : true, - :target_object => record.id.to_i + :target_object => record_id } end - def custom_buttons_hash(record) - get_custom_buttons(record).collect do |group| + def custom_button_selects(model, record, toolbar_result) + get_custom_buttons(model, record, toolbar_result).collect do |group| props = { :id => "custom_#{group[:id]}", :type => :buttonSelect, :icon => "product product-custom-#{group[:image]} fa-lg", :title => group[:description], :enabled => true, - :items => group[:buttons].collect { |b| create_custom_button_hash(b, record) } + :items => group[:buttons].collect { |b| create_custom_button(b, model, record) } } props[:text] = group[:text] if group[:text_display] @@ -273,28 +283,38 @@ def custom_buttons_hash(record) end end - def custom_toolbar_class(record) + def custom_toolbar_class(toolbar_result) + model = @record ? @record.class : model_for_custom_toolbar + build_custom_toolbar_class(model, @record, toolbar_result) + end + + def build_custom_toolbar_class(model, record, toolbar_result) # each custom toolbar is an anonymous subclass of this class + toolbar = Class.new(ApplicationHelper::Toolbar::Basic) - custom_buttons_hash(record).each do |button_group| + + # This creates several drop-down (select) with custom buttons. + # Each select is placed into a separate group. + custom_button_selects(model, record, toolbar_result).each do |button_group| toolbar.button_group(button_group[:name], button_group[:items]) end - service_buttons = record_to_service_buttons(record) - unless service_buttons.empty? - buttons = service_buttons.collect { |b| create_custom_button_hash(b, record, :enabled => nil) } - toolbar.button_group("custom_buttons_", buttons) + # For Service, we include buttons for ServiceTemplate. + # These buttons are added as a single group with multiple buttons + if record.present? + service_buttons = record_to_service_buttons(record) + unless service_buttons.empty? + buttons = service_buttons.collect { |b| create_custom_button(b, model, record, :enabled => nil) } + toolbar.button_group("custom_buttons_", buttons) + end end toolbar end - def button_class_name(record) - case record - when Service then "ServiceTemplate" # Service Buttons are defined in the ServiceTemplate class - when VmOrTemplate then record.class.base_model.name - else record.class.base_class.name - end + def button_class_name(model) + # Service Buttons are defined in the ServiceTemplate class + model >= Service ? "ServiceTemplate" : model.base_model.name end def service_template_id(record) @@ -310,9 +330,9 @@ def record_to_service_buttons(record) record.service_template.custom_buttons.collect { |cb| create_raw_custom_button_hash(cb, record) } end - def get_custom_buttons(record) - cbses = CustomButtonSet.find_all_by_class_name(button_class_name(record), service_template_id(record)) - cbses.sort_by { |cbs| cbs[:set_data][:group_index] }.collect do |cbs| + def get_custom_buttons(model, record, toolbar_result) + cbses = CustomButtonSet.find_all_by_class_name(button_class_name(model), service_template_id(record)) + cbses.sort_by { |cbs| cbs.set_data[:group_index] }.collect do |cbs| group = { :id => cbs.id, :text => cbs.name.split("|").first, @@ -322,11 +342,14 @@ def get_custom_buttons(record) } available = CustomButton.available_for_user(current_user, cbs.name) # get all uri records for this user for specified uri set - available = available.select { |b| cbs.members.include?(b) } # making sure available_for_user uri is one of the members + available = available.select do |b| + cbs.members.include?(b) && toolbar_result.plural_form_matches(b) + end + group[:buttons] = available.collect { |cb| create_raw_custom_button_hash(cb, record) }.uniq - if cbs[:set_data][:button_order] # Show custom buttons in the order they were saved + if cbs.set_data[:button_order] # Show custom buttons in the order they were saved ordered_buttons = [] - cbs[:set_data][:button_order].each do |bidx| + cbs.set_data[:button_order].each do |bidx| group[:buttons].each do |b| if bidx == b[:id] && !ordered_buttons.include?(b) ordered_buttons.push(b) diff --git a/app/views/shared/buttons/_ab_form.html.haml b/app/views/shared/buttons/_ab_form.html.haml index 72f6358fbc0..4838fd867e9 100644 --- a/app/views/shared/buttons/_ab_form.html.haml +++ b/app/views/shared/buttons/_ab_form.html.haml @@ -56,15 +56,31 @@ = _('Dialog') .col-md-8 = select_tag('dialog_id', - options_for_select(([[_(""), nil]]) + Array(@edit[:new][:available_dialogs].invert).sort_by { |a| a.first.downcase }, @edit[:new][:dialog_id]), + options_for_select([[_(""), nil]] + Array(@edit[:new][:available_dialogs].invert).sort_by { |a| a.first.downcase }, @edit[:new][:dialog_id]), "data-miq_sparkle_on" => true, :class => "selectpicker") .form-group %label.control-label.col-md-2 = _('Open URL') .col-md-8 - = check_box_tag("open_url", "1", @edit[:new][:open_url], - "data-miq_observe_checkbox" => {:interval => '.5', :url => url}.to_json) + = check_box_tag("open_url", "1", @edit[:new][:open_url], "data-miq_observe_checkbox" => {:interval => '.5', :url => url}.to_json) + + .form-group + %label.control-label.col-md-2 + = _('Display for') + .col-md-8 + = select_tag("display_for", + options_for_select([[_('Single entity'), 'single'], [_('List'), 'list'], [_('Single and list'), 'both']], @edit[:new][:display_for]), + "data-miq_sparkle_on" => true) + -# TODO: temporary disabled until the backend supports "submit all" + -# .form-group + -# %label.control-label.col-md-2 + -# = _('Submit') + -# .col-md-8 + -# = select_tag("submit_how", + -# options_for_select([[_('Submit all'), 'all'], [_('One by one'), 'one']], @edit[:new][:submit_how]), + -# "data-miq_sparkle_on" => true, + -# ) = render(:partial => "layouts/ae_resolve_options", :locals => {:resolve => @edit, @@ -75,5 +91,7 @@ :locals => {:rec_id => @custom_button ? @custom_button.id : 'new', :action => "automate_button_field_changed"}) :javascript miqInitSelectPicker(); - miqSelectPickerEvent("button_image", "#{url}") - miqSelectPickerEvent('dialog_id', '#{url}') + miqSelectPickerEvent('button_image', '#{url}'); + miqSelectPickerEvent('dialog_id', '#{url}'); + miqSelectPickerEvent('display_for', '#{url}'); + miqSelectPickerEvent('submit_how', '#{url}'); diff --git a/app/views/shared/buttons/_ab_show.html.haml b/app/views/shared/buttons/_ab_show.html.haml index 0fb7fa653e2..c6baee6d9b8 100644 --- a/app/views/shared/buttons/_ab_show.html.haml +++ b/app/views/shared/buttons/_ab_show.html.haml @@ -32,6 +32,17 @@ = _('Open URL') .col-md-8 = h(@custom_button.options.key?(:open_url) ? @custom_button.options[:open_url] : false) + .form-group + %label.control-label.col-md-2 + = _('Display for') + .col-md-8 + = @custom_button.options[:display_for] + -# TODO: temporary disabled until the backend supports "submit all" + -# .form-group + -# %label.control-label.col-md-2 + -# = _('Submit') + -# .col-md-8 + -# = @custom_button.options[:submit_how] %hr %h3 diff --git a/spec/helpers/application_helper/toolbar_builder_spec.rb b/spec/helpers/application_helper/toolbar_builder_spec.rb index 0ba318f6c77..f6f93c4340e 100644 --- a/spec/helpers/application_helper/toolbar_builder_spec.rb +++ b/spec/helpers/application_helper/toolbar_builder_spec.rb @@ -15,9 +15,18 @@ let(:user) { FactoryGirl.create(:user, :role => "super_administrator") } shared_examples "no custom buttons" do - it("#get_custom_buttons") { expect(toolbar_builder.get_custom_buttons(subject)).to be_blank } - it("#custom_buttons_hash") { expect(toolbar_builder.custom_buttons_hash(subject)).to be_blank } - it("#custom_toolbar_class") { expect(toolbar_builder.custom_toolbar_class(subject).definition).to be_blank } + it "#get_custom_buttons" do + expect(toolbar_builder.get_custom_buttons(subject.class, subject, Mixins::CustomButtons::Result.new(:single))).to be_blank + end + + it "#custom_button_selects" do + expect(toolbar_builder.custom_button_selects(subject.class, subject, Mixins::CustomButtons::Result.new(:single))).to be_blank + end + + it "#build_custom_toolbar_class" do + expect(toolbar_builder.build_custom_toolbar_class(subject.class, subject, Mixins::CustomButtons::Result.new(:single)).definition).to be_blank + end + it("#record_to_service_buttons") { expect(toolbar_builder.record_to_service_buttons(subject)).to be_blank } end @@ -48,14 +57,14 @@ :buttons => [expected_button1] } - expect(toolbar_builder.get_custom_buttons(subject)).to eq([expected_button_set]) + expect(toolbar_builder.get_custom_buttons(subject.class, subject, Mixins::CustomButtons::Result.new(:single))).to eq([expected_button_set]) end it "#record_to_service_buttons" do expect(toolbar_builder.record_to_service_buttons(subject)).to be_blank end - it "#custom_buttons_hash" do + it "#custom_button_selects" do escaped_button1_text = CGI.escapeHTML(@button1.name.to_s) button1 = { :id => "custom__custom_#{@button1.id}", @@ -80,10 +89,11 @@ } items = [button_set_item1] name = "custom_buttons_#{@button_set.name}" - expect(toolbar_builder.custom_buttons_hash(subject)).to eq([:name => name, :items => items]) + result = toolbar_builder.custom_button_selects(subject.class, subject, Mixins::CustomButtons::Result.new(:single)) + expect(result).to eq([:name => name, :items => items]) end - it "#custom_toolbar_class" do + it "#build_custom_toolbar_class" do escaped_button1_text = CGI.escapeHTML(@button1.name.to_s) button1 = { :id => "custom__custom_#{@button1.id}", @@ -107,7 +117,7 @@ :items => button_set_item1_items } group_name = "custom_buttons_#{@button_set.name}" - expect(toolbar_builder.custom_toolbar_class(subject).definition[group_name].buttons).to eq([button_set_item1]) + expect(toolbar_builder.build_custom_toolbar_class(subject.class, subject, Mixins::CustomButtons::Result.new(:single)).definition[group_name].buttons).to eq([button_set_item1]) end end diff --git a/spec/shared/controllers/shared_example_for_custom_buttons_spec.rb b/spec/shared/controllers/shared_example_for_custom_buttons_spec.rb index e07dedb3903..cf55ede94a0 100644 --- a/spec/shared/controllers/shared_example_for_custom_buttons_spec.rb +++ b/spec/shared/controllers/shared_example_for_custom_buttons_spec.rb @@ -4,7 +4,7 @@ controller.instance_variable_set(:@lastaction, 'show') controller.instance_variable_set(:@record, true) - expect(controller.custom_toolbar?).to be true + expect(controller.custom_toolbar).to be_a_kind_of Mixins::CustomButtons::Result end it "has no custom toolbar when showing main view w/o @record" do @@ -12,7 +12,7 @@ controller.instance_variable_set(:@lastaction, 'show') controller.instance_variable_set(:@record, nil) - expect(controller.custom_toolbar?).to be_falsey + expect(controller.custom_toolbar).to be_falsey end it "has no custom toolbar when not showing main view w/ @record" do @@ -20,7 +20,7 @@ controller.instance_variable_set(:@lastaction, 'show') controller.instance_variable_set(:@record, true) - expect(controller.custom_toolbar?).to be_falsey + expect(controller.custom_toolbar).to be_falsey end end @@ -34,7 +34,7 @@ :trees => {"my_tree" => {:active_node => 'root'}} ) - expect(controller.custom_toolbar?).to eq(:blank) + expect(controller.custom_toolbar).to eq('blank_view_tb') end it "has no custom toolbar when not showing main view w/ @record" do @@ -47,7 +47,7 @@ :trees => {"my_tree" => {:active_node => 'v-1r35'}} ) - expect(controller.custom_toolbar?).to eq(:blank) + expect(controller.custom_toolbar).to eq('blank_view_tb') end it "has custom toolbar when showing main view w/ @record" do @@ -60,6 +60,6 @@ :trees => {"my_tree" => {:active_node => 'v-1r35'}} ) - expect(controller.custom_toolbar?).to be(true) + expect(controller.custom_toolbar).to be_a_kind_of Mixins::CustomButtons::Result end end