Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
27 changes: 23 additions & 4 deletions app/controllers/hosts_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ def index(title = nil)
end
format.csv do
@hosts = search.preload(included_associations - [:host_statuses, :token])
csv_response(@hosts)
csv_response(@hosts, csv_columns, csv_headers)
end
end
end
Expand Down Expand Up @@ -892,9 +892,28 @@ def host_attributes_for_templates(host)
end

def csv_columns
Pagelets::Manager.pagelets_at("hosts/_list", 'hosts_table_column_header', filter: { selected: @selected_columns })
.map { |pagelet| pagelet.opts[:export_data] || pagelet.opts[:export_key] || pagelet.opts[:key] }
.flatten
csv_pagelets.map { |pagelet| pagelet.opts[:export_data] || pagelet.opts[:export_key] || pagelet.opts[:key] }.flatten
end

def csv_headers
csv_pagelets.map do |pagelet|
export_data = pagelet.opts[:export_data]

# Handle array of ExportDefinitions (like installable_updates)
if export_data.is_a?(Array)
export_data.map(&:label)
elsif export_data.is_a?(CsvExporter::ExportDefinition)
export_data.label
else
# Check for explicit export_label first, then pagelet label, then derive from export_key/key
# Use the same logic as CsvExporter::ExportDefinition.derive_label
pagelet.opts[:export_label] || pagelet.opts[:label] || (pagelet.opts[:export_key] || pagelet.opts[:key]).to_s.titleize.gsub('.', ' - ')
end
end.flatten
end

def csv_pagelets
@csv_pagelets ||= Pagelets::Manager.pagelets_at("hosts/_list", 'hosts_table_column_header', filter: { selected: @selected_columns })
end

def origin_intervals_query(compare_with)
Expand Down
4 changes: 4 additions & 0 deletions config/initializers/foreman_register.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@
add_pagelet :hosts_table_column_content, key: :owner, callback: ->(host) { host_owner_column(host) }, class: common_class
add_pagelet :hosts_table_column_header, key: :hostgroup, label: N_('Host group'), sortable: true, width: '15%', class: common_class
add_pagelet :hosts_table_column_content, key: :hostgroup, callback: ->(host) { label_with_link host.hostgroup, 23, @hostgroup_authorizer }, class: common_class
add_pagelet :hosts_table_column_header, key: :organization, label: N_('Organization'), sortable: true, width: '12%', export_key: 'organization.name', class: common_class
add_pagelet :hosts_table_column_content, key: :organization, callback: ->(host) { host.organization&.name }, class: common_class
add_pagelet :hosts_table_column_header, key: :location, label: N_('Location'), sortable: true, width: '12%', export_key: 'location.name', class: common_class
add_pagelet :hosts_table_column_content, key: :location, callback: ->(host) { host.location&.name }, class: common_class
add_pagelet :hosts_table_column_header, key: :boot_time, label: N_('Boot time'), sortable: true, width: '10%', export_key: 'reported_data.boot_time', class: common_class
add_pagelet :hosts_table_column_content, key: :boot_time, callback: ->(host) { date_time_unless_empty(host.reported_data&.boot_time) }, class: common_class
add_pagelet :hosts_table_column_header, key: :last_report, label: N_('Last report'), sortable: true, default_sort: 'DESC', width: '10%', class: common_class
Expand Down
1 change: 1 addition & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -552,6 +552,7 @@

match 'host_statuses' => 'react#index', :via => :get
match 'new/hosts/auto_complete_search', :via => :get, :to => 'hosts#auto_complete_search', :as => "auto_complete_search_hosts_new"
match 'new/hosts.csv', :via => :get, :to => 'hosts#index', :defaults => { :format => 'csv' }, :as => :new_hosts_csv
constraints(id: /[^\/]+/) do
match 'new/hosts/:id' => 'react#index', :via => :get, :as => :host_details_page
end
Expand Down
37 changes: 37 additions & 0 deletions test/controllers/hosts_controller_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,43 @@ def host_attributes(host)
end
end

test "csv_headers should use ExportDefinition labels for power_status" do
User.current.table_preferences.create(name: 'hosts', columns: ['power_status'])
get :index, params: { :format => 'csv' }, session: set_session_user
assert_response :success
buf = response.stream.instance_variable_get(:@buf)
header_line = buf.next
assert_includes header_line, "Power Status", "Header should use ExportDefinition label 'Power Status' not 'Power'"
end

test "csv_headers should derive headers from export_key when available" do
User.current.table_preferences.create(name: 'hosts', columns: ['os_title', 'boot_time'])
get :index, params: { :format => 'csv' }, session: set_session_user
assert_response :success
buf = response.stream.instance_variable_get(:@buf)
header_line = buf.next
assert_includes header_line, "Operatingsystem", "Header should be derived from export_key 'operatingsystem'"
assert_includes header_line, "Reported Data - Boot Time", "Header should format dotted keys with ' - '"
end

test "csv_columns should return columns from pagelets" do
User.current.table_preferences.create(name: 'hosts', columns: ['name', 'power_status'])
get :index, params: { :format => 'csv' }, session: set_session_user
assert_response :success
# Verify columns are properly extracted from pagelets
assert @controller.send(:csv_columns).any? { |col| col.is_a?(String) || col.is_a?(CsvExporter::ExportDefinition) }
end

test "csv_pagelets should be memoized" do
User.current.table_preferences.create(name: 'hosts', columns: ['name'])
get :index, params: { :format => 'csv' }, session: set_session_user

# Call csv_pagelets twice and verify it returns the same object (memoized)
pagelets1 = @controller.send(:csv_pagelets)
pagelets2 = @controller.send(:csv_pagelets)
assert_same pagelets1, pagelets2, "csv_pagelets should be memoized"
end

test "should include registered scope on index" do
# remember the previous state
old_scopes = HostsController.scopes_for(:index).dup
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -465,6 +465,23 @@ const HostsIndex = () => {
selectedCount={selectedCount}
/>
</SplitItem>
<SplitItem>
<Button
component="a"
ouiaId="export-hosts-button"
href={foremanUrl(
`${hostsIndexUrl}.csv${
searchQuery
? `?search=${encodeURIComponent(searchQuery)}`
: ''
}`
)}
variant="secondary"
isDisabled={false}
>
{__('Export')}
</Button>
</SplitItem>
<SplitItem>
<Button
component="a"
Expand Down
Loading