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

Fixes #16043 - add 'select all hosts' option into hosts page #4338

Merged
merged 1 commit into from Apr 3, 2017

Conversation

amirfefer
Copy link
Member

Select all items checkbox in hosts page selects only the hosts within the page. when the user wish to select all hosts for a bulk action, he has to select all hosts page after page.

multiple_selection1

multiple_selection

ezgif com-resize

@mention-bot
Copy link

@amirfefer, thanks for your PR! By analyzing the history of the files in this pull request, we identified @shlomizadok, @tbrisker and @ohadlevy to be potential reviewers.

@@ -1,4 +1,6 @@
<div class="row">
<%= alert(:class => 'alert-info hide', :close => true, :header => '',
Copy link
Member

Choose a reason for hiding this comment

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

maybe this should be warning? @Rohoover ?

Choose a reason for hiding this comment

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

@ohadlevy For which part? The selection of all items or that all items are changing owners?

Copy link
Member

Choose a reason for hiding this comment

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

I'm looking for a way to say that you are seeing only the first 20 but you actually selected all of them, so if you take an action against them its the entire list not just he one you see (same workflow if you select all of your emails on gmail).

Choose a reason for hiding this comment

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

@ohadlevy

I actually like the way it's address in the gif. I don't think it should be a warning as nothing "wrong" happened.

Copy link
Member

@tbrisker tbrisker left a comment

Choose a reason for hiding this comment

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

Added a few comments inline. Seeing the amount of changes to host_checkbox.js.erb, I wonder if it makes sense to just rewrite it in ES6? afaict the only reason for it to be an .erb is for the path to the spinner, which I think can be done with just a class anyways.

@@ -81,7 +81,7 @@ def page_entries_info(collection, options = {})
end

def will_paginate_with_info(collection = nil, options = {})
content_tag(:div, :id => "pagination", :class => "row") do
content_tag(:div, :id => "pagination", :class => "row", 'data-meta-count' => collection.count) do
Copy link
Member

Choose a reason for hiding this comment

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

Use collection.total_entries which caches to total count

@@ -750,8 +750,9 @@ def load_vars_for_ajax

def find_multiple
# Lets search by name or id and make sure one of them exists first
if params[:host_names].present? || params[:host_ids].present?
@hosts = resource_base.where("hosts.id IN (?) or hosts.name IN (?)", params[:host_ids], params[:host_names])
if params[:host_names].present? || params[:host_ids].present? || params.key?(:search)
Copy link
Member

Choose a reason for hiding this comment

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

mind turning all of these to .key??

$("#confirmation-modal form").submit();
$('#confirmation-modal').modal('hide');
}

function build_modal(element, url) {
var url = url + "?" + $.param({host_ids: $.foremanSelectedHosts});
if ($("#multiple-chosen").data('multiple')) {
var query = $("<input>")
Copy link
Member

Choose a reason for hiding this comment

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

why do you need to create an input here if the value is encoded directly to the query?

var query = $("<input>")
.attr("type", "hidden")
.attr("name", "search").val($("#search").val());
$("#confirmation-modal form").append($(query));
Copy link
Member

Choose a reason for hiding this comment

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

no need to wrap query here, it is already a jquery object

rmHostId(cid);
if ($(".alert").length){
Copy link
Member

Choose a reason for hiding this comment

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

might this be a different alert?

@amirfefer
Copy link
Member Author

thanks @tbrisker for your review, I've followed your comments.

@ohadlevy
Copy link
Member

ohadlevy commented Mar 2, 2017

@tbrisker I would favor to move this to ES6 in a separate commit, as that most likely will invlove the entire file vs just this particular feature?

also the js.erb file has been fixed in #4349

@tbrisker
Copy link
Member

tbrisker commented Mar 8, 2017

In general this works well, however there is an issue when selecting a very large amount of hosts - loading the modal with the host list can take several minutes as it isn't paginated.
The table in the modal should be paginated or only display the matching search when "select all" is chosen. Also it seems to join tables that aren't needed for the fields it displays and can be removed to speed it up.

@amirfefer
Copy link
Member Author

@Rohoover: What do you think is more suitable for the modal dialog alert?

Info alert:
info_alert
Warning alert:
warning_alert

@tbrisker: Thanks, I think it better to display just the alert with hosts quantity and filter query, as you can see at the screenshots above. I've fixed the join issue.

@Rohoover
Copy link

Rohoover commented Mar 9, 2017

Use the grey box if the action isn't particular serious.

If it has strong consequences use the warning (orange) box.

Remove the chevrons in both. It would be nice to have (but not required) if the messaging would read" Reminder: All 10 hosts are selected for query filter architecture=..." because the important part is not what query they searched but that they have them all selected.

@amirfefer
Copy link
Member Author

amirfefer commented Mar 9, 2017

@Rohoover Thanks 👍 . I've kept the grey box, and changed the bold string. otherwise the user would think he made something wrong. The chevron mark notes that the string is translatable.

Copy link
Member

@tbrisker tbrisker left a comment

Choose a reason for hiding this comment

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

Getting there, a few mostly minor inline comments.

<table class="<%= table_css_classes %>">
<thead>
<%= alert(:class => 'alert-info hide', :id => 'multiple-modal-alert', :close => true, :header => '',
:text => _("Reminder: <strong> All #{hosts.count.to_s} hosts are selected </strong> for query filter #{multiple_filter}").html_safe) %>
Copy link
Member

Choose a reason for hiding this comment

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

I think it makes sense to only show the "for query filter..." part if there is a filter, otherwise it looks odd:
image

Copy link
Member

Choose a reason for hiding this comment

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

The filter should also be escaped to prevent XSS :
image

@@ -81,7 +81,7 @@ def page_entries_info(collection, options = {})
end

def will_paginate_with_info(collection = nil, options = {})
content_tag(:div, :id => "pagination", :class => "row") do
content_tag(:div, :id => "pagination", :class => "row", 'data-meta-count' => collection.total_entries) do
Copy link
Member

Choose a reason for hiding this comment

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

We also have a meta tag on the host list for number per page, it makes sense to me to use the same here as well using a <meta name="totalResults" content="#{collection.total_entries}"/> tag.

Copy link
Member Author

Choose a reason for hiding this comment

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

I've added this particularly to pagination div, so the total collection size would be collectable from any pagination page.

As you can see at the following screenshot, the total size is visable though hardly collectable.
total
The meta name per page exists only in hosts page.

Copy link
Member

Choose a reason for hiding this comment

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

Copy link
Member Author

@amirfefer amirfefer Mar 13, 2017

Choose a reason for hiding this comment

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

I've removed the use of meta attribute, instead I've expanded will_paginate_with_info with include_data_tag flag.

@@ -1,2 +1,4 @@
<% title_actions multiple_actions_select, button_group(new_link(_("Create Host"))) %>
<%= alert(:class => 'alert-info hide', :id => 'multiple-alert', :close => false, :header => '',
:text => _("All #{Setting[:entries_per_page]} hosts on this page are selected. ").html_safe + link_to_function((_("Select all <b> #{@hosts.count.to_s} </b> hosts")).html_safe, "multiple_selection()")) %>
Copy link
Member

Choose a reason for hiding this comment

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

This should also use the cached @hosts.total_entries instead of @hosts.count.

</tbody>
</table>
<%= check_box_tag "keep_selected", "", false, :title => _("Keep selected hosts for a future action") %>
<%= _('Keep selected hosts for a future action') %><br/><br/>
Copy link
Member

Choose a reason for hiding this comment

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

shouldn't this be possible also when selecting all? (i.e. outside the if block)

Copy link
Member Author

Choose a reason for hiding this comment

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

I've thought at the beginning that it useless due to cookie implementation , but I've managed not to expand this cookie 😄

@@ -750,8 +761,9 @@ def load_vars_for_ajax

def find_multiple
# Lets search by name or id and make sure one of them exists first
if params[:host_names].present? || params[:host_ids].present?
@hosts = resource_base.where("hosts.id IN (?) or hosts.name IN (?)", params[:host_ids], params[:host_names])
if params.key?(:host_names) || params.key?(:host_ids) || multiple_with_filter?
Copy link
Member

Choose a reason for hiding this comment

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

I wonder - do we actually ever call this method with a host_names parameter?

Copy link
Member Author

Choose a reason for hiding this comment

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

I haven't found anything using host names in params...

if multiple_with_filter?
params[:search].blank? ? _("Empty") : params[:search]
end
end
Copy link
Member

Choose a reason for hiding this comment

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

I think this should go in the helper instead of the controller.

Copy link
Member Author

Choose a reason for hiding this comment

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

host_helper is too large, hence I've extract this logic into hosts_and_hostgroup_helper

assert_equal users(:one).id_and_type, @host1.reload.is_owned_by
assert_equal users(:one).id_and_type, @host2.reload.is_owned_by
end
end
Copy link
Member

Choose a reason for hiding this comment

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

Please add a test that has an actual search query and make sure only the matching hosts are selected.

Copy link
Member Author

Choose a reason for hiding this comment

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

I've added two additional tests

var count = $("#pagination").data("meta-count");
var per_page = $("meta[name='itemsPerPage']").attr("content");
var alert_text = Jed.sprintf(__("All %s hosts on this page are selected. "), per_page);
var select_text = Jed.sprintf(__("Select all") + "<b> %s </b>" + __("hosts"), count);
Copy link
Member

Choose a reason for hiding this comment

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

This won't translate correctly in all languages as the strings Select all and hosts will be translated separately and not as one sentence. Also above on line 96.

@amirfefer
Copy link
Member Author

@tbrisker I've fixed most of your inline comments, I've left some inline comments.

@amirfefer
Copy link
Member Author

amirfefer commented Mar 23, 2017

@tbrisker IMO the cookie is the problem here.
I think it better to keep the cookie behavior as it was, which means only for save specific hosts selection.
I don't see any benefit for saving the user's choice in a cookie when he select a whole collection by a search filter, (because it simple and quick to re-use a whole collection selection - just 2 clicks -> all_hosts checkbox >> select all x hosts).
Therefore I've removed the keep hosts selected checkbox when a user select all hosts by query filter.

Copy link
Member

@tbrisker tbrisker left a comment

Choose a reason for hiding this comment

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

Here is a screenshot explaining the problem with not clearing the cookie on a new search:
screenshot from 2017-03-27 11-45-07
Note that the top shown "20 selected", but the bottom says "40 selected" which is the correct number. (to get to this situation, search for something, mark some hosts, search a different search and select all)

end

def per_page(collection)
collection.total_entries > Setting[:entries_per_page] ? Setting[:entries_per_page] : collection.total_entries
Copy link
Member

Choose a reason for hiding this comment

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

Shouldn't this be in layout helper?
also, how about [Setting[:entries_per_page], collection.total_entries].min?


function undo_multiple_selection(){
var count = $("#pagination").data("count");
var per_page = $("#pagination").data("per-page");
Copy link
Member

Choose a reason for hiding this comment

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

Please cache $("#pagination") to save dom lookups

var per_page = $("#pagination").data("per-page");
var alert_text = Jed.sprintf(__("All %s hosts on this page are selected. "), per_page);
var select_text = Jed.sprintf(__("Select all<b> %s </b> hosts"), count);
$(".text").html('<span id="multiple-chosen">' + alert_text + '<a href="#" onclick="multiple_selection();">' + select_text + '</a></span>');
Copy link
Member

Choose a reason for hiding this comment

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

Isn't .text a bit of a generic selector?

function toggleCheck() {
var checked = $("#check_all").is(':checked');
$('.host_select_boxes').each(function(index, box) {
box.checked = checked;
hostChecked(box);
});
if(!checked)
var per_page = $("#pagination").data("per-page");
var total_host = $("#pagination").data("count");
Copy link
Member

Choose a reason for hiding this comment

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

ditto

@amirfefer
Copy link
Member Author

@tbrisker : From your screenshot It looks like the top number is presented correctly while the bottom does not.
This issue occurs due to a counter bug which exists in develop, as you can see in the following screenshot.
selected_counter

I've fixed the rest.

Copy link
Member

@tbrisker tbrisker left a comment

Choose a reason for hiding this comment

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

a few more comments, please also note related tests seem to be failing intermittently.

@@ -56,6 +61,8 @@ function toggle_actions() {

// setups checkbox values upon document load
$(function() {
$.count = $("#pagination").data("count");
$.per_page = $("#pagination").data("per-page");
Copy link
Member

Choose a reason for hiding this comment

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

Why are you adding these to the jQuery object?

Copy link
Member

Choose a reason for hiding this comment

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

also please cache $("#pagination")

rmHostId(cid);
if ($("#multiple-alert").length){
$("#multiple-alert").hide('slow');
$("#multiple-alert").data('multiple', false)
Copy link
Member

Choose a reason for hiding this comment

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

$("#multiple-alert") should be cached to reduce lookups

}
else if (!checked) {
$("#multiple-alert").hide('slow');
$("#multiple-alert").data('multiple', false);
Copy link
Member

Choose a reason for hiding this comment

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

please cache these too

@amirfefer
Copy link
Member Author

@tbrisker , thanks, I've added caching to reduce lookups.

@tbrisker
Copy link
Member

tbrisker commented Apr 2, 2017

@amirfefer Following our talk, looks like you didn't push an updated version with correct caching for jquery objects?

@amirfefer
Copy link
Member Author

@tbrisker , You're right, now you can see caching for jquery objects

@tbrisker tbrisker merged commit 13a7bf0 into theforeman:develop Apr 3, 2017
@tbrisker
Copy link
Member

tbrisker commented Apr 3, 2017

Thanks @amirfefer ! I'd be happy to see all of this code rewritten in es6 or react, could you please open a refactor issue for that?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
6 participants