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 #19954 - Add CSV export on content host index #6814

Merged
merged 1 commit into from
Jul 3, 2017

Conversation

tbrisker
Copy link
Member

@tbrisker tbrisker commented Jun 7, 2017

TODO:

  • Tests
  • Make sure all needed tables are preloaded

@mention-bot
Copy link

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

'applicable_errata.enhancement.count', 'applicable_rpms.count',
:operatingsystem, :lifecycle_environment, :content_view,
'subscription_facet.registered_at', 'subscription_facet.last_checkin'],
['Name', 'Subscription Status', 'Installable Updates - Security', 'Installable Updates - Bug Fixes', 'Installable Updates - Enhancements', 'Installable Updates - Package Count', 'OS', 'Environment', 'Content View', 'Registered', 'Last Checkin'])

Choose a reason for hiding this comment

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

Line is too long. [261/200]

'applicable_errata.security.count', 'applicable_errata.bugfix.count',
'applicable_errata.enhancement.count', 'applicable_rpms.count',
:operatingsystem, :lifecycle_environment, :content_view,
'subscription_facet.registered_at', 'subscription_facet.last_checkin'],

Choose a reason for hiding this comment

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

Align the elements of an array literal if they span more than one line.

csv_response(@hosts, [:name, :subscription_status_label,
'applicable_errata.security.count', 'applicable_errata.bugfix.count',
'applicable_errata.enhancement.count', 'applicable_rpms.count',
:operatingsystem, :lifecycle_environment, :content_view,

Choose a reason for hiding this comment

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

Align the elements of an array literal if they span more than one line.

.preload(:subscription_status_object, :content_facet, :subscription_facet)
csv_response(@hosts, [:name, :subscription_status_label,
'applicable_errata.security.count', 'applicable_errata.bugfix.count',
'applicable_errata.enhancement.count', 'applicable_rpms.count',

Choose a reason for hiding this comment

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

Align the elements of an array literal if they span more than one line.

@hosts = resource_base_with_search.where(organization_id: params[:organization_id])
.preload(:subscription_status_object, :content_facet, :subscription_facet)
csv_response(@hosts, [:name, :subscription_status_label,
'applicable_errata.security.count', 'applicable_errata.bugfix.count',

Choose a reason for hiding this comment

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

Align the elements of an array literal if they span more than one line.

format.csv do
@hosts = resource_base_with_search.where(organization_id: params[:organization_id])
.preload(:subscription_status_object, :content_facet, :subscription_facet)
csv_response(@hosts,
Copy link
Member

Choose a reason for hiding this comment

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

Why not use a view layer (i.e. hosts.csv.erb) ? More complex model-formatting logic could go in a decorator or presenter. It seems like bad practice to bloat the controller for csv functionality.

Copy link
Member Author

Choose a reason for hiding this comment

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

There is no view, the data is streamed directly one row at a time by the CSV exporter to the client. Since reports can sometimes be very large, we don't want to block the download until they are generated completely.
This is only complex because of the amount of columns and relations this table has, other tables are much simpler (you can see some examples in foreman core).

@sjha4
Copy link
Member

sjha4 commented Jun 8, 2017

This works after updating the foreman controller/csv_responder.rb file following similar changes in the app/services/csv_exporter.rb to accept the third parameter. Could you verify this?

@@ -23,6 +26,35 @@ def puppet_environment_for_content_view
cvpe = Katello::ContentViewPuppetEnvironment.where(:environment_id => environment, :content_view_version_id => version).first
render :json => cvpe.nil? ? nil : {:name => cvpe.puppet_environment.name, :id => cvpe.puppet_environment.id}
end

def content_hosts
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 having a separate endpoint for 'content host' information is not ideal. We've worked on unifying these concepts at the api and model layer and I think we should continue work towards that. This would mean that the list of attributes for csv would need to be pluggable, but i think thats a better route and could be used for our extensions of hostgroup as well.

@tbrisker
Copy link
Member Author

@sjha4 Thanks for catching that, I've opened theforeman/foreman#4578 to fix the issue.
@jlsherrill I'm not sure how else we can approach this - since the host controller already has a csv response used for the export button on the host index. I'd be happy for any suggestions.

@tbrisker
Copy link
Member Author

Pushed with a bit of improvement to the preloaded tables; still having an issue of having to count applicable errata by type per host, not sure if there is anything that can be done about that though.

@jlsherrill
Copy link
Member

@jlsherrill I'm not sure how else we can approach this - since the host controller already has a csv response used for the export button on the host index. I'd be happy for any suggestions.

I'm suggesting that the same endpoint be used and return all the same information no matter which page they are on. When katello is installed, more information is returned on both pages. This could involve:

a) using the 'rails' way of alias method chaining the 'csv_columns' method to add things to the normal host controller array.
b) creating some sort of plugin endpoint to 'extend' the csv columns for a particular object

a) is quite trivial so i'm fine with that in the shorter term, but i do think its a better solution than creating two different host csv lists. The UI being split into two (Hosts and content hosts) is a bug not a feature, so I'd like to limit it as much as possible

@tbrisker
Copy link
Member Author

@jlsherrill There are two problems with your suggested approach:

  1. That would cause both the host and content host export button to return the same data, both of which won't match the table displayed in the UI.
  2. Due to the amount of related tables, exporting any large amount of hosts would lead to a very slow response. We already have this issue with the errata types, since there is no direct relation atm as I mentioned before.

@jlsherrill
Copy link
Member

I see, so the intent is to just let the user download the information in the table rather than download information about 'hosts'. If thats the case I'm okay with it!

@jlsherrill
Copy link
Member

still having an issue of having to count applicable errata by type per host, not sure if there is anything that can be done about that though.

Yes, currently for the hosts api we're still having to do an N+1 query. I don't know of a way in rails to do this as there is no attribute on the model that can represent the various counts. The only solution I know of to eliminate them completely would be to cache the values on the objects themselves which i think is becoming more and more of a necessity.

@jlsherrill
Copy link
Member

Current test failure is just due to permissions, your new route needs to be added here: https://github.com/Katello/katello/blob/master/lib/katello/permissions/host_permissions.rb#L36

I opened http://projects.theforeman.org/issues/20019 to pull the errata counts into the various objects as a cache.

@tbrisker
Copy link
Member Author

@jlsherrill thanks, added the missing permission

assert_equal "attachment; filename=\"hosts-#{Date.today}.csv\"", response.headers["Content-Disposition"]
buf = response.stream.instance_variable_get(:@buf)
assert buf.is_a? Enumerator
assert_equal Katello::Concerns::HostsControllerExtensions::CSV_HEADER.join(',')+"\n", buf.next

Choose a reason for hiding this comment

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

Surrounding space missing for operator +.

'applicable_errata.bugfix.size', 'applicable_errata.enhancement.size',
'applicable_rpms.size', :operatingsystem, :lifecycle_environment, :content_view,
'subscription_facet.registered_at', 'subscription_facet.last_checkin']
CSV_HEADER= ['Name', 'Subscription Status', 'Installable Updates - Security',

Choose a reason for hiding this comment

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

Surrounding space missing for operator =.
Freeze mutable objects assigned to constants.

@@ -3,7 +3,17 @@ module Concerns
module HostsControllerExtensions
extend ActiveSupport::Concern
include ForemanTasks::Triggers
CSV_FIELDS = [:name, :subscription_status_label, 'applicable_errata.security.size',

Choose a reason for hiding this comment

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

Freeze mutable objects assigned to constants.

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 did, bad dog!

@tbrisker
Copy link
Member Author

Added tests, optimized table loading as best as possible atm

assert_equal "attachment; filename=\"hosts-#{Date.today}.csv\"", response.headers["Content-Disposition"]
buf = response.stream.instance_variable_get(:@buf)
assert buf.is_a? Enumerator
assert_equal "Name,Subscription Status,Installable Updates - Security,Installable Updates - Bug Fixes,Installable Updates - Enhancements,Installable Updates - Package Count,OS,Environment,Content View,Registered,Last Checkin\n", buf.next

Choose a reason for hiding this comment

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

Line is too long. [243/200]

assert_equal "attachment; filename=\"hosts-#{Date.today}.csv\"", response.headers["Content-Disposition"]
buf = response.stream.instance_variable_get(:@buf)
assert buf.is_a? Enumerator
assert_equal "Name,Subscription Status,Installable Updates - Security,Installable Updates - Bug Fixes,Installable Updates - Enhancements,Installable Updates - Package Count,OS,Environment,Content View,Registered,Last Checkin\n", buf.next

Choose a reason for hiding this comment

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

Line is too long. [243/200]

assert_equal "attachment; filename=\"hosts-#{Date.today}.csv\"", response.headers["Content-Disposition"]
buf = response.stream.instance_variable_get(:@buf)
assert buf.is_a? Enumerator
assert_equal "Name,Subscription Status,Installable Updates - Security,Installable Updates - Bug Fixes,Installable Updates - Enhancements,Installable Updates - Package Count,OS,Environment,Content View,Registered,Last Checkin\n", buf.next

Choose a reason for hiding this comment

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

Line is too long. [243/200]

buf = response.stream.instance_variable_get(:@buf)
assert buf.is_a? Enumerator
assert_equal "Name,Subscription Status,Installable Updates - Security,Installable Updates - Bug"+
" Fixes,Installable Updates - Enhancements,Installable Updates - Package Count,OS,Environment,"+

Choose a reason for hiding this comment

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

Surrounding space missing for operator +.
Use \ instead of + or << to concatenate those strings.

assert_equal "attachment; filename=\"hosts-#{Date.today}.csv\"", response.headers["Content-Disposition"]
buf = response.stream.instance_variable_get(:@buf)
assert buf.is_a? Enumerator
assert_equal "Name,Subscription Status,Installable Updates - Security,Installable Updates - Bug"+

Choose a reason for hiding this comment

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

Surrounding space missing for operator +.
Use \ instead of + or << to concatenate those strings.

@@ -5,6 +5,9 @@ <h2 translate>Content Hosts</h2>
</div>

<div data-block="list-actions">
<a href="/hosts/content_hosts.csv?{{csvQuery}}" class="btn btn-default">
<span translate>Export</span>
</a>
Copy link
Member

Choose a reason for hiding this comment

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

seeing a small issue with using this. It causes the URL to change which messes up things such as:
a) clicking export a 2nd time doesn't work
b) clicking 'search' does cause a 2nd download

adding target="_self" seems to fix it. @waldenraines is that appropriate here?

Copy link
Contributor

Choose a reason for hiding this comment

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

Try ng-href instead of href. If that doesn't work I'm fine with this.

Copy link
Member Author

Choose a reason for hiding this comment

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

ng-href didn't fix it, added target.

Copy link
Member

@jlsherrill jlsherrill left a comment

Choose a reason for hiding this comment

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

ACK thanks @tbrisker

@jlsherrill jlsherrill merged commit 6362c73 into Katello:master Jul 3, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
8 participants