From 1d61d10c3af36fba66681ff4ec235bcb3b063d33 Mon Sep 17 00:00:00 2001 From: Jeremy Lenz Date: Wed, 18 Dec 2024 14:20:34 -0500 Subject: [PATCH 1/4] Add scoped search and rabl nodes for IoP --- app/models/concerns/rh_cloud_host.rb | 5 +++++ app/views/api/v2/hosts/insights/base.rabl | 5 +++++ app/views/api/v2/hosts/insights/insights.rabl | 3 +++ lib/foreman_rh_cloud/engine.rb | 7 +++++++ 4 files changed, 20 insertions(+) create mode 100644 app/views/api/v2/hosts/insights/base.rabl create mode 100644 app/views/api/v2/hosts/insights/insights.rabl diff --git a/app/models/concerns/rh_cloud_host.rb b/app/models/concerns/rh_cloud_host.rb index e776ae84..4436f59f 100644 --- a/app/models/concerns/rh_cloud_host.rb +++ b/app/models/concerns/rh_cloud_host.rb @@ -21,5 +21,10 @@ module RhCloudHost scoped_search :relation => :inventory_sync_status_object, :on => :status, :rename => :insights_inventory_sync_status, :complete_value => { :disconnect => ::InventorySync::InventoryStatus::DISCONNECT, :sync => ::InventorySync::InventoryStatus::SYNC } + scoped_search :relation => :insights, :on => :uuid, :only_explicit => true, :rename => :insights_uuid + + def insights_facet + insights + end end end diff --git a/app/views/api/v2/hosts/insights/base.rabl b/app/views/api/v2/hosts/insights/base.rabl new file mode 100644 index 00000000..2afff2b0 --- /dev/null +++ b/app/views/api/v2/hosts/insights/base.rabl @@ -0,0 +1,5 @@ +attributes :uuid + +node :insights_hit_details do |facet| + facet&.host&.facts('insights::hit_details')&.values&.first +end diff --git a/app/views/api/v2/hosts/insights/insights.rabl b/app/views/api/v2/hosts/insights/insights.rabl new file mode 100644 index 00000000..645bb595 --- /dev/null +++ b/app/views/api/v2/hosts/insights/insights.rabl @@ -0,0 +1,3 @@ +node :insights_attributes do + partial 'api/v2/hosts/insights/base', object: @object&.insights_facet +end diff --git a/lib/foreman_rh_cloud/engine.rb b/lib/foreman_rh_cloud/engine.rb index 69d3a57a..51f6fd99 100644 --- a/lib/foreman_rh_cloud/engine.rb +++ b/lib/foreman_rh_cloud/engine.rb @@ -112,6 +112,7 @@ def self.register_scheduled_task(task_class, cronline) register_facet InsightsFacet, :insights do configure_host do + api_view :list => 'api/v2/hosts/insights/insights' set_dependent_action :destroy end end @@ -151,6 +152,12 @@ def self.register_scheduled_task(task_class, cronline) end end + initializer "foreman_rh_cloud.add_rabl_view_path" do + Rabl.configure do |config| + config.view_paths << ForemanRhCloud::Engine.root.join('app', 'views') + end + end + initializer 'foreman_rh_cloud.register_scheduled_tasks', :before => :finisher_hook do |_app| # skip database manipulations while tables do not exist, like in migrations # skip object creation when admin user is not present, for example in test DB From e4a1d20b3b238d46058c367e75b0737bfc83f96c Mon Sep 17 00:00:00 2001 From: Jeremy Lenz Date: Thu, 19 Dec 2024 17:17:35 -0500 Subject: [PATCH 2/4] Add new endpoint for host insights details --- .../insights_advisor_controller.rb | 27 +++++++++++++++++++ .../insights_advisor/host_details.json.rabl | 9 +++++++ config/routes.rb | 5 ++++ 3 files changed, 41 insertions(+) create mode 100644 app/controllers/api/v2/insights_advisor/insights_advisor_controller.rb create mode 100644 app/views/api/v2/insights_advisor/host_details.json.rabl diff --git a/app/controllers/api/v2/insights_advisor/insights_advisor_controller.rb b/app/controllers/api/v2/insights_advisor/insights_advisor_controller.rb new file mode 100644 index 00000000..de1441c8 --- /dev/null +++ b/app/controllers/api/v2/insights_advisor/insights_advisor_controller.rb @@ -0,0 +1,27 @@ +module Api + module V2 + module InsightsAdvisor + class InsightsAdvisorController < ::Api::V2::BaseController + include ::Api::Version2 + + before_action :find_organization + + def host_details + @hosts = ::Host::Managed.search_for(params[:search] || "", :order => params[:order]).where(:organization_id => @organization.id).includes(:insights) + respond_to do |format| + format.json { render 'api/v2/insights_advisor/host_details' } + end + end + + private + + def find_organization + @organization ||= Organization.find_by(label: params[:organization_label]) if params[:organization_label] + @organization ||= Organization.find_by(label: params[:organization]) if params[:organization] + @organization ||= Organization.find(params[:organization_id]) if params[:organization_id] + raise ::Foreman::Exception.new(N_("Organization not found")) unless @organization + end + end + end + end +end diff --git a/app/views/api/v2/insights_advisor/host_details.json.rabl b/app/views/api/v2/insights_advisor/host_details.json.rabl new file mode 100644 index 00000000..24a94809 --- /dev/null +++ b/app/views/api/v2/insights_advisor/host_details.json.rabl @@ -0,0 +1,9 @@ +collection @hosts + +attributes :name +node :insights_uuid do |host| + host.insights_facet&.uuid +end +node :insights_hit_details do |host| + host&.facts('insights::hit_details')&.values&.first +end diff --git a/config/routes.rb b/config/routes.rb index 1c7aaa77..97dfe12a 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -65,6 +65,11 @@ post 'cloud_request', to: 'cloud_request#update' end + + namespace 'insights_advisor' do + get 'host_details', to: 'insights_advisor#host_details' + # post 'upload_hits', to: 'insights_advisor#upload_hits' + end end end end From 3b30531cb6b25368347295dcaa4d0b29642fda5b Mon Sep 17 00:00:00 2001 From: Jeremy Lenz Date: Fri, 20 Dec 2024 09:34:24 -0500 Subject: [PATCH 3/4] add tests --- .../api/insights_advisor_controller_test.rb | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 test/controllers/insights_cloud/api/insights_advisor_controller_test.rb diff --git a/test/controllers/insights_cloud/api/insights_advisor_controller_test.rb b/test/controllers/insights_cloud/api/insights_advisor_controller_test.rb new file mode 100644 index 00000000..31b117f5 --- /dev/null +++ b/test/controllers/insights_cloud/api/insights_advisor_controller_test.rb @@ -0,0 +1,39 @@ +require 'test_plugin_helper' + +module InsightsCloud + module Api + class InsightsAdvisorControllerTest < ActionController::TestCase + tests ::Api::V2::InsightsAdvisor::InsightsAdvisorController + + setup do + @test_org = FactoryBot.create(:organization) + @host1 = FactoryBot.create(:host, :with_insights_hits, organization: @test_org, hostname: 'insightshost1') + @host2 = FactoryBot.create(:host, :with_insights_hits, organization: @test_org, hostname: 'insightshost2') + @host3 = FactoryBot.create(:host, organization: @test_org) + end + + test 'shows all hosts with no search param' do + get :host_details, params: { organization_id: @test_org.id } + + assert_response :success + assert_template 'api/v2/insights_advisor/host_details' + assert_equal @test_org.hosts.count, assigns(:hosts).count + end + + test 'shows hosts with search param' do + search = @host1.name[0..4] + get :host_details, params: { organization_id: @test_org.id, search: search } + assert_response :success + assert_template 'api/v2/insights_advisor/host_details' + assert_equal @test_org.hosts.where('name LIKE ?', "%#{search}%").count, assigns(:hosts).count + refute_equal @test_org.hosts.count, assigns(:hosts).count + end + + test 'fails without org id' do + response = get :host_details + + assert_includes response.body, 'Organization not found' + end + end + end +end From 783ba1ec702b10fa87367cd649b18e5c6eb408d3 Mon Sep 17 00:00:00 2001 From: Jeremy Lenz Date: Mon, 23 Dec 2024 10:20:59 -0500 Subject: [PATCH 4/4] address comments --- .../insights_advisor_controller.rb | 24 ++++++++----------- .../api/insights_advisor_controller_test.rb | 24 +++++++------------ test/factories/insights_factories.rb | 2 +- 3 files changed, 19 insertions(+), 31 deletions(-) diff --git a/app/controllers/api/v2/insights_advisor/insights_advisor_controller.rb b/app/controllers/api/v2/insights_advisor/insights_advisor_controller.rb index de1441c8..b3c80374 100644 --- a/app/controllers/api/v2/insights_advisor/insights_advisor_controller.rb +++ b/app/controllers/api/v2/insights_advisor/insights_advisor_controller.rb @@ -4,23 +4,19 @@ module InsightsAdvisor class InsightsAdvisorController < ::Api::V2::BaseController include ::Api::Version2 - before_action :find_organization - + api :GET, "insights_advisor/host_details", N_('Fetch Insights-related host details') + param :host_uuids, Array, required: true, desc: N_('List of host UUIDs') def host_details - @hosts = ::Host::Managed.search_for(params[:search] || "", :order => params[:order]).where(:organization_id => @organization.id).includes(:insights) - respond_to do |format| - format.json { render 'api/v2/insights_advisor/host_details' } + uuids = params.require(:host_uuids) + @hosts = ::Host.joins(:insights).where(:insights => { :uuid => uuids }) + if @hosts.empty? + render json: { error: 'No hosts found for the given UUIDs' }, status: :not_found + else + respond_to do |format| + format.json { render 'api/v2/insights_advisor/host_details' } + end end end - - private - - def find_organization - @organization ||= Organization.find_by(label: params[:organization_label]) if params[:organization_label] - @organization ||= Organization.find_by(label: params[:organization]) if params[:organization] - @organization ||= Organization.find(params[:organization_id]) if params[:organization_id] - raise ::Foreman::Exception.new(N_("Organization not found")) unless @organization - end end end end diff --git a/test/controllers/insights_cloud/api/insights_advisor_controller_test.rb b/test/controllers/insights_cloud/api/insights_advisor_controller_test.rb index 31b117f5..fa6b4ed6 100644 --- a/test/controllers/insights_cloud/api/insights_advisor_controller_test.rb +++ b/test/controllers/insights_cloud/api/insights_advisor_controller_test.rb @@ -12,27 +12,19 @@ class InsightsAdvisorControllerTest < ActionController::TestCase @host3 = FactoryBot.create(:host, organization: @test_org) end - test 'shows all hosts with no search param' do - get :host_details, params: { organization_id: @test_org.id } - - assert_response :success - assert_template 'api/v2/insights_advisor/host_details' - assert_equal @test_org.hosts.count, assigns(:hosts).count - end - - test 'shows hosts with search param' do - search = @host1.name[0..4] - get :host_details, params: { organization_id: @test_org.id, search: search } + test 'shows hosts with uuids' do + uuids = [@host1.insights.uuid, @host2.insights.uuid] + get :host_details, params: { organization_id: @test_org.id, host_uuids: uuids } assert_response :success assert_template 'api/v2/insights_advisor/host_details' - assert_equal @test_org.hosts.where('name LIKE ?', "%#{search}%").count, assigns(:hosts).count + assert_equal @test_org.hosts.joins(:insights).where(:insights => { :uuid => uuids }).count, assigns(:hosts).count refute_equal @test_org.hosts.count, assigns(:hosts).count end - test 'fails without org id' do - response = get :host_details - - assert_includes response.body, 'Organization not found' + test 'shows error when no hosts found' do + get :host_details, params: { organization_id: @test_org.id, host_uuids: ['nonexistentuuid'] } + assert_response :not_found + assert_equal 'No hosts found for the given UUIDs', JSON.parse(response.body)['error'] end end end diff --git a/test/factories/insights_factories.rb b/test/factories/insights_factories.rb index e9bf730c..725aa25d 100644 --- a/test/factories/insights_factories.rb +++ b/test/factories/insights_factories.rb @@ -1,6 +1,6 @@ FactoryBot.define do factory :insights_facet do - # sequence(:uuid) { |n| "uuid-#{n}" } + sequence(:uuid) { |n| "uuid-#{n}" } trait :with_hits do hits do