diff --git a/app/controllers/katello/concerns/hosts_controller_extensions.rb b/app/controllers/katello/concerns/hosts_controller_extensions.rb index bc17b34ae47..147423a24c6 100644 --- a/app/controllers/katello/concerns/hosts_controller_extensions.rb +++ b/app/controllers/katello/concerns/hosts_controller_extensions.rb @@ -15,13 +15,42 @@ def action_permission super end end + + def csv_pagelets + base_pagelets = super + # Only append Katello pagelets for /new/hosts.csv + return base_pagelets unless request.path.start_with?('/new/hosts') + + # Get Katello pagelets from the :content profile + if @selected_columns + # User has customized columns - use their selection + katello_pagelets = Pagelets::Manager.pagelets_at('hosts/_list', 'hosts_table_column_header', profile: :content, filter: { selected: @selected_columns }) + else + # No customization - use default Katello columns matching content_hosts method + all_katello_pagelets = Pagelets::Manager.pagelets_at('hosts/_list', 'hosts_table_column_header', profile: :content) + default_katello_columns = [:installable_updates, :content_view_environments, :registered_at, :last_checkin] + katello_pagelets = all_katello_pagelets.select { |p| default_katello_columns.include?(p.opts[:key]) } + end + + # Exclude pagelets that are already in base (like :name which uses use_pagelet) + existing_keys = base_pagelets.map { |p| p.opts[:key] } + katello_pagelets = katello_pagelets.reject { |p| existing_keys.include?(p.opts[:key]) } + + base_pagelets + katello_pagelets + end end included do prepend Overrides def included_associations(include = []) - [:host_traces] + super + base_associations = super + # Only add Katello associations for /new/hosts.csv + return base_associations unless request.path.start_with?('/new/hosts') + + katello_associations = [:host_traces, :subscription_facet, :content_facet, + :applicable_rpms, :content_view_environments] + katello_associations + base_associations end def update_multiple_taxonomies(type) diff --git a/lib/katello/plugin.rb b/lib/katello/plugin.rb index b9401fb8a0f..ab558f29efa 100644 --- a/lib/katello/plugin.rb +++ b/lib/katello/plugin.rb @@ -293,6 +293,10 @@ common_class = 'hidden-tablet hidden-xs ellipsis' use_pagelet :hosts_table_column_header, :name use_pagelet :hosts_table_column_content, :name + add_pagelet :hosts_table_column_header, key: :bootc_booted_image, label: _('Type'), sortable: true, class: common_class, width: '10%', + export_data: CsvExporter::ExportDefinition.new('content_facet_attributes.bootc_booted_image', label: 'Image Type') + add_pagelet :hosts_table_column_content, key: :bootc_booted_image, class: common_class, callback: ->(host) { host.content_facet&.bootc_booted_image } + add_pagelet :hosts_table_column_header, key: :rhel_lifecycle_status, label: _('RHEL Lifecycle status'), sortable: true, class: common_class, width: '10%', export_key: 'rhel_lifecycle_status' add_pagelet :hosts_table_column_content, key: :rhel_lifecycle_status, class: common_class, callback: ->(host) { host_status_icon(host.rhel_lifecycle_global_status) } @@ -300,17 +304,25 @@ export_data: [:security, :bugfix, :enhancement].map { |kind| CsvExporter::ExportDefinition.new("installable_updates.#{kind}", callback: ->(host) { (host.content_facet_attributes&.errata_counts || {})[kind] }) } + [:rpm, :deb].map { |kind| CsvExporter::ExportDefinition.new("installable_packages.#{kind}", callback: ->(host) { host&.content_facet_attributes&.public_send("upgradable_#{kind}_count".to_sym) || 0 }) } add_pagelet :hosts_table_column_content, key: :installable_updates, class: common_class, callback: ->(host) { errata_counts(host) } - use_pagelet :hosts_table_column_header, :os_title - use_pagelet :hosts_table_column_content, :os_title + add_pagelet :hosts_table_column_header, key: :last_checkin, label: _('Last checkin'), sortable: true, class: common_class, width: '10%', export_data: CsvExporter::ExportDefinition.new('subscription_facet_attributes.last_checkin', label: 'Last Checkin') + add_pagelet :hosts_table_column_content, key: :last_checkin, class: common_class, callback: ->(host) { host_checkin_time(host) } + add_pagelet :hosts_table_column_header, key: :content_view_environments, label: _('Content View Environments'), class: common_class, width: '15%', + export_data: CsvExporter::ExportDefinition.new('content_view_environment_labels', label: 'Content View Environments', callback: ->(host) { host.content_view_environment_labels }) + add_pagelet :hosts_table_column_content, key: :content_view_environments, class: common_class, callback: ->(host) { host.content_view_environment_labels } add_pagelet :hosts_table_column_header, key: :lifecycle_environment, label: _('Lifecycle environment'), sortable: true, class: common_class, width: '10%', - export_data: CsvExporter::ExportDefinition.new('single_lifecycle_environment', label: 'Lifecycle Environment') + export_data: CsvExporter::ExportDefinition.new('single_lifecycle_environment', label: 'Lifecycle Environment', callback: ->(host) { host.content_facet&.single_lifecycle_environment&.name }) add_pagelet :hosts_table_column_content, key: :lifecycle_environment, class: common_class, callback: ->(host) { host.content_facet&.single_lifecycle_environment&.name } - add_pagelet :hosts_table_column_header, key: :content_view, label: _('Content view'), sortable: true, class: common_class, width: '10%', export_data: CsvExporter::ExportDefinition.new('single_content_view', label: 'Content View') + add_pagelet :hosts_table_column_header, key: :content_view, label: _('Content view'), sortable: true, class: common_class, width: '10%', + export_data: CsvExporter::ExportDefinition.new('single_content_view', label: 'Content View', callback: ->(host) { host.content_facet&.single_content_view&.name }) add_pagelet :hosts_table_column_content, key: :content_view, class: common_class, callback: ->(host) { host.content_facet&.single_content_view&.name } + add_pagelet :hosts_table_column_header, key: :content_source, label: _('Content Source'), sortable: true, class: common_class, width: '12%', + export_data: CsvExporter::ExportDefinition.new('content_facet_attributes.content_source_name', label: 'Content Source') + add_pagelet :hosts_table_column_content, key: :content_source, class: common_class, callback: ->(host) { host.content_facet&.content_source&.name } add_pagelet :hosts_table_column_header, key: :registered_at, label: _('Registered'), sortable: true, class: common_class, width: '10%', export_data: CsvExporter::ExportDefinition.new('subscription_facet_attributes.registered_at', label: 'Registered') add_pagelet :hosts_table_column_content, key: :registered_at, class: common_class, callback: ->(host) { host_registered_time(host) } - add_pagelet :hosts_table_column_header, key: :last_checkin, label: _('Last checkin'), sortable: true, class: common_class, width: '10%', export_data: CsvExporter::ExportDefinition.new('subscription_facet_attributes.last_checkin', label: 'Last Checkin') - add_pagelet :hosts_table_column_content, key: :last_checkin, class: common_class, callback: ->(host) { host_checkin_time(host) } + add_pagelet :hosts_table_column_header, key: :host_collections, label: _('Host Collections'), class: common_class, width: '15%', + export_data: CsvExporter::ExportDefinition.new('host_collections', label: 'Host Collections', callback: ->(host) { host.host_collections.map(&:name).join(', ') }) + add_pagelet :hosts_table_column_content, key: :host_collections, class: common_class, callback: ->(host) { host.host_collections.map(&:name).join(', ') } end end diff --git a/test/controllers/foreman/hosts_controller_test.rb b/test/controllers/foreman/hosts_controller_test.rb index 7427583339a..bf1dbab05ee 100644 --- a/test/controllers/foreman/hosts_controller_test.rb +++ b/test/controllers/foreman/hosts_controller_test.rb @@ -102,6 +102,99 @@ def test_csv_export_search buf = response.stream.instance_variable_get(:@buf) assert_equal 2, buf.count end + + def test_index_csv_includes_katello_columns_for_new_hosts_path + @request.path = '/new/hosts.csv' + # Select Katello content columns that user wants to see (in UI weight order) + User.current.table_preferences.create(name: 'hosts', columns: ['name', 'bootc_booted_image', 'rhel_lifecycle_status', 'installable_updates', 'last_checkin', 'content_view_environments', 'lifecycle_environment', 'content_view', 'content_source', 'registered_at', 'host_collections']) + + get :index, params: { :format => 'csv' } + assert_response :success + + buf = response.stream.instance_variable_get(:@buf) + header_line = buf.next + + # Verify Katello columns are included (in UI weight order) + assert_includes header_line, "Image Type" + assert_includes header_line, "Installable Updates - Security" + assert_includes header_line, "Installable Updates - Bugfix" + assert_includes header_line, "Installable Updates - Enhancement" + assert_includes header_line, "Installable Packages - Rpm" + assert_includes header_line, "Last Checkin" + assert_includes header_line, "Content View Environments" + assert_includes header_line, "Lifecycle Environment" + assert_includes header_line, "Content View" + assert_includes header_line, "Content Source" + assert_includes header_line, "Registered" + assert_includes header_line, "Host Collections" + end + + def test_index_csv_excludes_katello_columns_for_legacy_hosts_path + @request.path = '/hosts.csv' + User.current.table_preferences.create(name: 'hosts', columns: ['name']) + + get :index, params: { :format => 'csv' } + assert_response :success + + buf = response.stream.instance_variable_get(:@buf) + header_line = buf.next + + # Verify Katello columns are NOT included + refute_includes header_line, "Installable Updates - Security" + refute_includes header_line, "Installable Updates - Bug Fixes" + refute_includes header_line, "Content View Environments" + end + + def test_csv_pagelets_conditional_on_request_path + @request.path = '/new/hosts.csv' + # Select Katello content columns + User.current.table_preferences.create(name: 'hosts', columns: ['name', 'installable_updates', 'registered_at']) + + get :index, params: { :format => 'csv' } + pagelets = @controller.send(:csv_pagelets) + + # Verify Katello pagelets are appended + katello_keys = pagelets.map { |p| p.opts[:key] } + assert_includes(katello_keys, :installable_updates) + assert_includes(katello_keys, :registered_at) + end + + def test_csv_pagelets_not_appended_for_legacy_path + @request.path = '/hosts.csv' + User.current.table_preferences.create(name: 'hosts', columns: ['name']) + + get :index, params: { :format => 'csv' } + pagelets = @controller.send(:csv_pagelets) + + # Verify Katello pagelets are NOT appended + katello_keys = pagelets.map { |p| p.opts[:key] } + refute_includes(katello_keys, :installable_updates) + refute_includes(katello_keys, :registered_at) + end + + def test_csv_exports_default_katello_columns_when_no_preferences + @request.path = '/new/hosts.csv' + # Don't create any table preferences - user hasn't customized columns + + get :index, params: { :format => 'csv' } + assert_response :success + + buf = response.stream.instance_variable_get(:@buf) + header_line = buf.next + + # Verify default Katello columns are included (matching content_hosts method) + assert_includes header_line, "Installable Updates - Security" + assert_includes header_line, "Installable Updates - Bugfix" + assert_includes header_line, "Installable Updates - Enhancement" + assert_includes header_line, "Installable Packages - Rpm" + assert_includes header_line, "Content View Environments" + assert_includes header_line, "Registered" + assert_includes header_line, "Last Checkin" + + # Verify non-default Katello columns are NOT included + refute_includes(header_line, "Content Source") + refute_includes(header_line, "Host Collections") + end end context 'destroy with katello overrides' do