From dce3f34019cb02352d46152b66db4fa324ccdea3 Mon Sep 17 00:00:00 2001 From: Ian Ballou Date: Mon, 23 Dec 2024 15:46:50 +0000 Subject: [PATCH] Refs #38072 - add sorting support to bootc_image api --- .../api/v2/host_bootc_images_controller.rb | 33 +++++++++++++------ config/routes/api/v2.rb | 2 +- lib/katello/permissions/host_permissions.rb | 2 ++ .../v2/host_bootc_images_controller_test.rb | 14 ++++---- 4 files changed, 33 insertions(+), 18 deletions(-) diff --git a/app/controllers/katello/api/v2/host_bootc_images_controller.rb b/app/controllers/katello/api/v2/host_bootc_images_controller.rb index a271fe90dd7..3aaab688841 100644 --- a/app/controllers/katello/api/v2/host_bootc_images_controller.rb +++ b/app/controllers/katello/api/v2/host_bootc_images_controller.rb @@ -8,20 +8,34 @@ class Api::V2::HostBootcImagesController < Api::V2::ApiController end api :GET, "/hosts/bootc_images", N_("List booted bootc container images for hosts") - param :page, :number, :desc => N_("Page number, starting at 1") - param :per_page, :number, :desc => N_("Number of results per page to return") + param_group :search, Api::V2::ApiController def bootc_images - bootc_image_map = bootc_host_image_map(params[:search]) - - page = params[:page].present? ? params[:page].to_i : 1 + params[:sort_by] ||= 'bootc_booted_image' + params[:sort_order] ||= 'asc' + if params[:order] + params[:order] = "#{params[:order].split(' ')[0]} #{sanitize_sort_order(params[:order].split(' ')[1])}" + else + params[:order] = "#{params[:sort_by]} #{sanitize_sort_order(params[:sort_order])}" + end per_page = params[:per_page].present? ? params[:per_page].to_i : Setting[:entries_per_page] + page = params[:page].present? ? params[:page].to_i : 1 + + bootc_image_map = bootc_host_image_map paged_images = bootc_image_map.to_a.paginate(page: page, per_page: per_page) - results = paged_images.collect { |image| { image_name: image[0], digests: image[1] } } + results = paged_images.collect { |image| { bootc_booted_image: image[0], digests: image[1] } } render json: { total: bootc_image_map.size, page: page, per_page: per_page, subtotal: bootc_image_map.size, results: results} end private + def sanitize_sort_order(sort_order) + if sort_order.present? && ['asc', 'desc'].include?(sort_order.downcase) + sort_order.downcase + else + 'asc' + end + end + def index_relation query = resource_class.authorized(:view_hosts).distinct query.joins(:content_facet).where.not(bootc_booted_image: nil, bootc_booted_digest: nil) @@ -32,12 +46,11 @@ def resource_class ::Host::Managed end - def bootc_host_image_map(host_search) - # TODO: can this be optimized such that it doesn't require two queries? - content_facets = ::Katello::Host::ContentFacet.where(host_id: ::Host::Managed.joins(:content_facet).search_for(host_search).pluck(:id)) + def bootc_host_image_map + content_facets = ::Katello::Host::ContentFacet.where(host_id: ::Host::Managed.joins(:content_facet).search_for(params[:search]).pluck(:id)) aggregate_bootc_data = content_facets.where.not(bootc_booted_image: nil, bootc_booted_digest: nil). select(:bootc_booted_image, :bootc_booted_digest, 'COUNT(hosts.id) as host_count'). - joins(:host).group(:bootc_booted_image, :bootc_booted_digest).order(:bootc_booted_image) + joins(:host).group(:bootc_booted_image, :bootc_booted_digest).order(params[:order]) bootc_image_map = Hash.new { |h, k| h[k] = [] } aggregate_bootc_data.each do |host_image| bootc_image_map[host_image.bootc_booted_image] << { bootc_booted_digest: host_image.bootc_booted_digest, host_count: host_image.host_count.to_i } diff --git a/config/routes/api/v2.rb b/config/routes/api/v2.rb index e945334f019..d3dd1fa9272 100644 --- a/config/routes/api/v2.rb +++ b/config/routes/api/v2.rb @@ -30,7 +30,7 @@ class ActionDispatch::Routing::Mapper end end - api_resources :host_bootc_images, :only => [:bootc_images] do + api_resources :host_bootc_images, :only => [:auto_complete_search] do get :auto_complete_search, :on => :collection end diff --git a/lib/katello/permissions/host_permissions.rb b/lib/katello/permissions/host_permissions.rb index 4677cdf23f9..f573eee7db2 100644 --- a/lib/katello/permissions/host_permissions.rb +++ b/lib/katello/permissions/host_permissions.rb @@ -43,6 +43,8 @@ Foreman::AccessControl.permission(:view_hosts).actions.concat [ 'hosts/content_hosts', + 'katello/api/v2/host_bootc_images/bootc_images', + 'katello/api/v2/host_bootc_images/auto_complete_search', 'katello/api/v2/host_autocomplete/auto_complete_search', 'katello/api/v2/host_errata/index', 'katello/api/v2/host_errata/show', diff --git a/test/controllers/api/v2/host_bootc_images_controller_test.rb b/test/controllers/api/v2/host_bootc_images_controller_test.rb index ab7b6a4d833..32fe82b7a9f 100644 --- a/test/controllers/api/v2/host_bootc_images_controller_test.rb +++ b/test/controllers/api/v2/host_bootc_images_controller_test.rb @@ -26,9 +26,9 @@ def setup def test_bootc_images_counts_properly_no_paging get :bootc_images assert_response :success - results = JSON.parse(@response.body)['bootc_images'] - assert_includes results, ["image1", [{"bootc_booted_digest" => "sha256:dcfb2965cda67bd3731408ace23dd07ff3116168c2b832e16bba8234525724a3", "host_count" => 2}]] - assert_includes results, ["image2", [{"bootc_booted_digest" => "sha256:dcfb2965cda67bc3731408aae23dd07ff3116168c2b832e16bba8234525724a5", "host_count" => 1}]] + results = JSON.parse(@response.body)['results'] + assert_includes results, {"bootc_booted_image" => "image1", "digests" => [{"bootc_booted_digest" => "sha256:dcfb2965cda67bd3731408ace23dd07ff3116168c2b832e16bba8234525724a3", "host_count" => 2}]} + assert_includes results, {"bootc_booted_image" => "image2", "digests" => [{"bootc_booted_digest" => "sha256:dcfb2965cda67bc3731408aae23dd07ff3116168c2b832e16bba8234525724a5", "host_count" => 1}]} end def test_bootc_images_pages @@ -43,10 +43,10 @@ def test_bootc_images_pages get :bootc_images, params: { page: 4, per_page: 1 } page4 = @response.body - assert_equal [["image1", [{"bootc_booted_digest" => "sha256:dcfb2965cda67bd3731408ace23dd07ff3116168c2b832e16bba8234525724a3", "host_count" => 1}]]], JSON.parse(page1)['bootc_images'] - assert_equal [["image2", [{"bootc_booted_digest" => "sha256:dcfb2965cda67bc3731408aae23dd07ff3116168c2b832e16bba8234525724a5", "host_count" => 1}]]], JSON.parse(page2)['bootc_images'] - assert_equal [["image3", [{"bootc_booted_digest" => "sha256:dcfb2965cda67bd3731408ace93dd07ff3116168c2b832e16bba8234525724c9", "host_count" => 1}]]], JSON.parse(page3)['bootc_images'] - assert_empty JSON.parse(page4)['bootc_images'] + assert_equal [{"bootc_booted_image" => "image1", "digests" => [{"bootc_booted_digest" => "sha256:dcfb2965cda67bd3731408ace23dd07ff3116168c2b832e16bba8234525724a3", "host_count" => 1}]}], JSON.parse(page1)['results'] + assert_equal [{"bootc_booted_image" => "image2", "digests" => [{"bootc_booted_digest" => "sha256:dcfb2965cda67bc3731408aae23dd07ff3116168c2b832e16bba8234525724a5", "host_count" => 1}]}], JSON.parse(page2)['results'] + assert_equal [{"bootc_booted_image" => "image3", "digests" => [{"bootc_booted_digest" => "sha256:dcfb2965cda67bd3731408ace93dd07ff3116168c2b832e16bba8234525724c9", "host_count" => 1}]}], JSON.parse(page3)['results'] + assert_empty JSON.parse(page4)['results'] end end end