Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Custom buttons for list views #796

Merged
merged 20 commits into from
Apr 13, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
5fd1d21
Add custom button options for list view support.
martinpovolny Mar 13, 2017
807b3b1
Toolbar: submit list of checked items for custom buttons.
martinpovolny Mar 24, 2017
4b5a8d3
Enable custom button toolbar for list views.
martinpovolny Mar 24, 2017
7e24e68
Add list support to custom button toolbar/button creation.
martinpovolny Mar 24, 2017
7328d73
Add list support to custom button click action handling.
martinpovolny Mar 24, 2017
63c28d6
Replace find_by_id with find to make Rubocop happy.
martinpovolny Mar 24, 2017
59abac3
Fix custom_button specs to match added arguments.
martinpovolny Mar 27, 2017
4afe315
Differenciate custom buttons for lists and single item.
martinpovolny Apr 5, 2017
7ad4907
Custom buttons: temporary disable submit_how:all.
martinpovolny Apr 10, 2017
7b571ec
Custom buttons: remove forgotten "pry".
martinpovolny Apr 10, 2017
4d6656c
Custom buttons: handle situation where @view_context is a controller.
martinpovolny Apr 10, 2017
802d3bb
Custom buttons: display custom buttons for lists when navigating a tree.
martinpovolny Apr 10, 2017
29ddceb
Custom buttons: make Rubocop less unhappy.
martinpovolny Apr 10, 2017
6a7e18d
Custom buttons: add missing file.
martinpovolny Apr 10, 2017
0ef724a
Custom buttons: Fix specs.
martinpovolny Apr 10, 2017
6834d56
CustomButtons: rename result class.
martinpovolny Apr 11, 2017
2f087b5
CustomButtons: remove question marks from custom_toolbar*?.
martinpovolny Apr 11, 2017
0a95184
Custom buttons: fixes for zero items and default submit type.
martinpovolny Apr 11, 2017
7816308
CustomToolbar: for plural case the record class needs to be taken fro…
martinpovolny Apr 12, 2017
19f4fe9
CustomButtons: rubocop fixes.
martinpovolny Apr 12, 2017
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions app/assets/javascripts/miq_application.js
Original file line number Diff line number Diff line change
Expand Up @@ -1484,6 +1484,10 @@ function miqToolbarOnClick(_e) {
} else {
params = miqSerializeForm(button.data('url_parms'));
}
} else if (button.data('url_parms').match("id=LIST")) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vecerek You will need to handle this case in #642 :)

(The id=LIST is needed by backend, but doesn't have to imply miq_grid_checks in general, just for the buttons touched here..)

// 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];
}
Expand Down
57 changes: 47 additions & 10 deletions app/controllers/application_controller/buttons.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand All @@ -260,28 +263,58 @@ 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])
cls = applies_to_class_model(button.applies_to_class)

@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)
Expand Down Expand Up @@ -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 = []
Expand Down Expand Up @@ -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])
Expand Down
27 changes: 15 additions & 12 deletions app/controllers/mixins/custom_buttons.rb
Original file line number Diff line number Diff line change
@@ -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
Expand Down
13 changes: 13 additions & 0 deletions app/controllers/mixins/custom_buttons/result.rb
Original file line number Diff line number Diff line change
@@ -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
3 changes: 2 additions & 1 deletion app/controllers/service_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
8 changes: 7 additions & 1 deletion app/controllers/vm_common.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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])
Expand Down
6 changes: 1 addition & 5 deletions app/helpers/application_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
75 changes: 49 additions & 26 deletions app/helpers/application_helper/toolbar_builder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand All @@ -239,62 +248,73 @@ 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}"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vecerek .. Aand this will need send_checked (#642)

}
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,
:description => cb.description,
: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]

{:name => "custom_buttons_#{group[:text]}", :items => [props]}
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)
Expand All @@ -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,
Expand All @@ -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)
Expand Down
Loading