diff --git a/app/controllers/katello/api/v2/subscriptions_controller.rb b/app/controllers/katello/api/v2/subscriptions_controller.rb index 4622a8be888..961a854d268 100644 --- a/app/controllers/katello/api/v2/subscriptions_controller.rb +++ b/app/controllers/katello/api/v2/subscriptions_controller.rb @@ -41,16 +41,17 @@ def index format.csv do options[:csv] = true collection = scoped_search(*base_args, options) - csv_response(collection, - [:id, :subscription_id, :name, :cp_id, :organization_id, :sockets, :cores, - :start_date, :end_date, :consumed, :quantity, :account_number, :contract_number, - :support_level, :ram, :stacking_id, :multi_entitlement, :type, :product_id, - :unmapped_guest, :virt_only, :virt_who, :upstream?], - ['Pool Id Number', 'Subscription Id', 'Name', 'Pool Id', 'Organization Id', - 'Sockets', 'Cores', 'Start Date', 'End Date', 'Consumed', 'Quantity', 'Account Number', - 'Contract Number', 'Support Level', 'RAM', 'Stacking Id', 'Multi Entitlement', 'Type', - 'Product Id', 'Unmapped Guest', 'Virt Only', 'Requires Virt Who', 'Upstream']) + fields = [:id, :subscription_id, :name, :cp_id, :organization_id, :sockets, :cores, + :start_date, :end_date, :consumed, :quantity, :account_number, :contract_number, + :support_level, :ram, :stacking_id, :multi_entitlement, :type, :product_id, + :unmapped_guest, :virt_only, :virt_who, :upstream?, :product_host_count] + headers = ['Pool Id Number', 'Subscription Id', 'Name', 'Pool Id', 'Organization Id', + 'Sockets', 'Cores', 'Start Date', 'End Date', 'Consumed', 'Quantity', 'Account Number', + 'Contract Number', 'Support Level', 'RAM', 'Stacking Id', 'Multi Entitlement', 'Type', + 'Product Id', 'Unmapped Guest', 'Virt Only', 'Requires Virt Who', 'Upstream', 'Product Host Count'] + csv_response(collection, fields, headers) end + format.any do collection = scoped_search(*base_args, options) if params[:activation_key_id] @@ -59,6 +60,9 @@ def index ActivationKeySubscriptionsPresenter.new(pool, key_pools) end end + collection[:results] = collection[:results].map do |pool| + ProductHostCountPresenter.new(pool) + end respond(:collection => collection) end end @@ -88,6 +92,8 @@ def show fail HttpErrors::BadRequest, N_('This subscription is not relevant to the current organization.') end + @resource = ProductHostCountPresenter.new(@resource) + respond(:resource => @resource) end diff --git a/app/models/katello/pool.rb b/app/models/katello/pool.rb index 3ddab289272..6ae4c4b1f18 100644 --- a/app/models/katello/pool.rb +++ b/app/models/katello/pool.rb @@ -55,6 +55,10 @@ class Pool < Katello::Model validate :subscription_matches_organization + def product_host_count + Katello::Host::ContentFacet.joins(bound_repositories: { product: :pools }).where(pools: { id: id }).distinct.count + end + def subscription_matches_organization return if errors[:subscription].any? || errors[:organization].any? # let other validations catch this unless subscription&.organization_id == self.organization_id @@ -147,7 +151,7 @@ def default_sort property :days_until_expiration, Integer, desc: 'Returns number of days until expiration' end class Jail < ::Safemode::Jail - allow :id, :name, :available, :quantity, :product_id, :contract_number, :type, :account_number, :start_date, :end_date, :organization, :consumed, :days_until_expiration + allow :id, :name, :available, :quantity, :product_id, :contract_number, :type, :account_number, :start_date, :end_date, :organization, :consumed, :days_until_expiration, :product_host_count end end end diff --git a/app/presenters/katello/product_host_count_presenter.rb b/app/presenters/katello/product_host_count_presenter.rb new file mode 100644 index 00000000000..69ecad931d2 --- /dev/null +++ b/app/presenters/katello/product_host_count_presenter.rb @@ -0,0 +1,10 @@ +module Katello + class ProductHostCountPresenter < SimpleDelegator + attr_reader :product_host_count + + def initialize(pool) + @product_host_count = pool.product_host_count + super(pool) + end + end +end diff --git a/app/views/katello/api/v2/subscriptions/index.json.rabl b/app/views/katello/api/v2/subscriptions/index.json.rabl index 27c5b8d899c..b86079a4fb8 100644 --- a/app/views/katello/api/v2/subscriptions/index.json.rabl +++ b/app/views/katello/api/v2/subscriptions/index.json.rabl @@ -6,5 +6,6 @@ extends 'katello/api/v2/subscriptions/permissions' child @collection[:results] => :results do attributes :quantity_attached + attributes :product_host_count extends "katello/api/v2/subscriptions/base" end diff --git a/app/views/katello/api/v2/subscriptions/show.json.rabl b/app/views/katello/api/v2/subscriptions/show.json.rabl index 5ef9e8749b1..18bd06688f5 100644 --- a/app/views/katello/api/v2/subscriptions/show.json.rabl +++ b/app/views/katello/api/v2/subscriptions/show.json.rabl @@ -6,6 +6,7 @@ attributes :arch attributes :description attributes :support_type attributes :roles, :usage, :addons +attributes :product_host_count node(:host_count) do |subscription| subscription.hosts.count diff --git a/test/controllers/api/v2/products_controller_test.rb b/test/controllers/api/v2/products_controller_test.rb index b12b5a01735..5eb8cb939f1 100644 --- a/test/controllers/api/v2/products_controller_test.rb +++ b/test/controllers/api/v2/products_controller_test.rb @@ -70,7 +70,7 @@ def test_index_custom_products_only get :index, params: { organization_id: @organization.id, custom: true } body = JSON.parse(response.body) - assert_equal 7, body['total'] + assert_equal Katello::Product.custom.count, body['total'] end def test_index_no_custom_products diff --git a/test/fixtures/models/katello_products.yml b/test/fixtures/models/katello_products.yml index d8059f4bf0c..f6fd54f8239 100644 --- a/test/fixtures/models/katello_products.yml +++ b/test/fixtures/models/katello_products.yml @@ -67,3 +67,10 @@ oracle: cp_id: 123123123006 provider_id: <%= ActiveRecord::FixtureSet.identify(:anonymous) %> organization_id: <%= ActiveRecord::FixtureSet.identify(:empty_organization) %> + +product_host_count: + name: product_host_count + description: A dummy product for pool tests + label: product_host_count_label + provider_id: <%= ActiveRecord::FixtureSet.identify(:anonymous) %> + organization_id: <%= ActiveRecord::FixtureSet.identify(:empty_organization) %> diff --git a/test/fixtures/models/katello_repositories.yml b/test/fixtures/models/katello_repositories.yml index 4d94a677a54..a706767cfc1 100644 --- a/test/fixtures/models/katello_repositories.yml +++ b/test/fixtures/models/katello_repositories.yml @@ -395,4 +395,18 @@ pulp3_ostree_1: pulp_id: "Default_Organization-Cabinet-pulp3_OSTree_1" relative_path: 'Default_Organization/library/pulp3_OSTree_1' environment_id: <%= ActiveRecord::FixtureSet.identify(:library) %> - content_view_version_id: <%= ActiveRecord::FixtureSet.identify(:library_default_version) %> \ No newline at end of file + content_view_version_id: <%= ActiveRecord::FixtureSet.identify(:library_default_version) %> + +product_host_count_repo1: + root_id: <%= ActiveRecord::FixtureSet.identify(:product_host_count_repo1_root) %> + pulp_id: "default_organization-my-product-host-count-repo1" + relative_path: 'default_organization/library/my-product-host-count-repo1' + environment_id: <%= ActiveRecord::FixtureSet.identify(:library) %> + content_view_version_id: <%= ActiveRecord::FixtureSet.identify(:library_default_version) %> + +product_host_count_repo2: + root_id: <%= ActiveRecord::FixtureSet.identify(:product_host_count_repo2_root) %> + pulp_id: "default_organization-my-product-host-count-repo2" + relative_path: 'default_organization/library/my-product-host-count-repo2' + environment_id: <%= ActiveRecord::FixtureSet.identify(:library) %> + content_view_version_id: <%= ActiveRecord::FixtureSet.identify(:library_default_version) %> diff --git a/test/fixtures/models/katello_root_repositories.yml b/test/fixtures/models/katello_root_repositories.yml index 51378b48083..203fde16bac 100644 --- a/test/fixtures/models/katello_root_repositories.yml +++ b/test/fixtures/models/katello_root_repositories.yml @@ -305,3 +305,24 @@ pulp3_ostree_root_1: unprotected: <%= true %> url: "" mirroring_policy: "mirror_content_only" + +product_host_count_repo1_root: + name: product host count repo 1 + content_id: 34 + content_type: yum + label: product_host_count_repo_1__label + product_id: <%= ActiveRecord::FixtureSet.identify(:product_host_count) %> + url: "https://product-host-count-repo1.com" + download_policy: on_demand + mirroring_policy: "mirror_content_only" + +product_host_count_repo2_root: + name: product host count repo 2 + content_id: 35 + content_type: yum + label: product_host_count_repo_2__label + product_id: <%= ActiveRecord::FixtureSet.identify(:product_host_count) %> + url: "https://product-host-count-repo2.com" + download_policy: on_demand + mirroring_policy: "mirror_content_only" + diff --git a/test/lib/tasks/check_candlepin_content_test.rb b/test/lib/tasks/check_candlepin_content_test.rb index 598b01f041d..97100e659d3 100644 --- a/test/lib/tasks/check_candlepin_content_test.rb +++ b/test/lib/tasks/check_candlepin_content_test.rb @@ -27,7 +27,7 @@ def test_candlepin_check_with_bad_ping def test_candlepin_content_check_with_missing_repos Katello::Ping.expects(:ping).returns(:status => 'ok') - Katello::Util::CandlepinRepositoryChecker.expects(:repository_exist_in_backend?).at_most(12) + Katello::Util::CandlepinRepositoryChecker.expects(:repository_exist_in_backend?).at_most(14) Rake.application.invoke_task('katello:check_candlepin_content') end end diff --git a/test/models/pool_test.rb b/test/models/pool_test.rb index 232ff5ce9cc..d6f53c8d26e 100644 --- a/test/models/pool_test.rb +++ b/test/models/pool_test.rb @@ -344,5 +344,26 @@ def test_audit_hook_to_find_records_should_return_hosts assert_equal pool_host_ids.length, hosts_list.keys.length end + + def test_product_host_count + product1 = katello_products(:product_host_count) + product1.pools << @pool_one + product1.pools << @pool_two + repo1 = katello_repositories(:product_host_count_repo1) + repo2 = katello_repositories(:product_host_count_repo2) + repo1.product = product1 + repo2.product = product1 + content_facet1 = katello_content_facets(:content_facet_one) + content_facet2 = katello_content_facets(:content_facet_two) + + # test if both hosts are counted correctly + content_facet1.bound_repositories = [repo1] + content_facet2.bound_repositories = [repo1] + assert_equal 2, @pool_one.product_host_count + + # test if multiple repositories in one product are counted only as one + content_facet2.bound_repositories = [repo1, repo2] + assert_equal 2, @pool_one.product_host_count + end end end diff --git a/webpack/scenes/Subscriptions/Details/SubscriptionAttributes.js b/webpack/scenes/Subscriptions/Details/SubscriptionAttributes.js index a39bd6715f5..28b5a17c436 100644 --- a/webpack/scenes/Subscriptions/Details/SubscriptionAttributes.js +++ b/webpack/scenes/Subscriptions/Details/SubscriptionAttributes.js @@ -15,4 +15,5 @@ export default { type: __('Type'), multi_entitlement: __('Multi-entitlement'), stacking_id: __('Stacking ID'), + product_host_count: __('Product Host Count'), }; diff --git a/webpack/scenes/Subscriptions/Details/__tests__/__snapshots__/SubscriptionDetailInfo.test.js.snap b/webpack/scenes/Subscriptions/Details/__tests__/__snapshots__/SubscriptionDetailInfo.test.js.snap index 2a00d6aea71..a7f59439d6d 100644 --- a/webpack/scenes/Subscriptions/Details/__tests__/__snapshots__/SubscriptionDetailInfo.test.js.snap +++ b/webpack/scenes/Subscriptions/Details/__tests__/__snapshots__/SubscriptionDetailInfo.test.js.snap @@ -150,6 +150,16 @@ exports[`subscriptions detail associations page renders correctly 1`] = ` SER0421 + + + + Product Host Count + + + + + + diff --git a/webpack/scenes/Subscriptions/SubscriptionConstants.js b/webpack/scenes/Subscriptions/SubscriptionConstants.js index eabb96113e5..41af13f9fea 100644 --- a/webpack/scenes/Subscriptions/SubscriptionConstants.js +++ b/webpack/scenes/Subscriptions/SubscriptionConstants.js @@ -102,6 +102,11 @@ export const SUBSCRIPTION_TABLE_COLUMNS = [ label: __('Entitlements'), value: false, }, + { + key: 'product_host_count', + label: __('Product Host Count'), + value: false, + }, ]; export const SUBSCRIPTION_TABLE_DEFAULT_COLUMNS = [ @@ -114,4 +119,5 @@ export const SUBSCRIPTION_TABLE_DEFAULT_COLUMNS = [ 'consumed', 'quantity', 'type', + 'product_host_count', ]; diff --git a/webpack/scenes/Subscriptions/__tests__/__snapshots__/SubscriptionsPage.test.js.snap b/webpack/scenes/Subscriptions/__tests__/__snapshots__/SubscriptionsPage.test.js.snap index cc1876b773c..9c2d76e50d7 100644 --- a/webpack/scenes/Subscriptions/__tests__/__snapshots__/SubscriptionsPage.test.js.snap +++ b/webpack/scenes/Subscriptions/__tests__/__snapshots__/SubscriptionsPage.test.js.snap @@ -117,6 +117,7 @@ exports[`subscriptions page should render 1`] = ` "instance_multiplier": 1, "multi_entitlement": null, "name": "zoo", + "product_host_count": 1, "product_id": "853987721546", "product_name": "zoo", "quantity": -1, @@ -143,6 +144,7 @@ exports[`subscriptions page should render 1`] = ` "instance_multiplier": 1, "multi_entitlement": null, "name": "hsdfhsdh", + "product_host_count": 0, "product_id": "947637693017", "product_name": "hsdfhsdh", "quantity": -1, diff --git a/webpack/scenes/Subscriptions/__tests__/subscriptions.fixtures.js b/webpack/scenes/Subscriptions/__tests__/subscriptions.fixtures.js index 1cbae3f2872..8d522612aa2 100644 --- a/webpack/scenes/Subscriptions/__tests__/subscriptions.fixtures.js +++ b/webpack/scenes/Subscriptions/__tests__/subscriptions.fixtures.js @@ -63,6 +63,7 @@ export const requestSuccessResponse = Immutable({ unmapped_guest: false, virt_only: false, virt_who: false, + product_host_count: 1, }, { id: 4, @@ -89,6 +90,7 @@ export const requestSuccessResponse = Immutable({ unmapped_guest: false, virt_only: false, virt_who: false, + product_host_count: 0, }, ], }); @@ -110,6 +112,7 @@ export const quantitiesRequestSuccessResponse = Immutable({ 4, 5, ], + product_host_count: 9469, }, { id: '6b123381519abf020151ab082c5e4678', @@ -125,6 +128,7 @@ export const quantitiesRequestSuccessResponse = Immutable({ local_pool_ids: [ 6, ], + product_host_count: 9469, }, ], page: 1, @@ -167,6 +171,7 @@ export const groupedSubscriptions = Immutable({ unmapped_guest: false, virt_only: false, virt_who: false, + product_host_count: 1, }, { id: 4, @@ -193,6 +198,7 @@ export const groupedSubscriptions = Immutable({ unmapped_guest: false, virt_only: false, virt_who: false, + product_host_count: 0, }, ], searchIsActive: false, @@ -236,6 +242,7 @@ export const successState = Immutable({ unmapped_guest: false, virt_only: false, virt_who: false, + product_host_count: 1, }, { id: 4, @@ -262,6 +269,7 @@ export const successState = Immutable({ unmapped_guest: false, virt_only: false, virt_who: false, + product_host_count: 0, }, ], searchIsActive: false, @@ -454,6 +462,11 @@ export const tableColumns = [ label: 'Entitlements', value: true, }, + { + key: 'product_host_count', + label: 'Product Host Count', + value: true, + }, ]; export const loadTableColumnsSuccessAction = [ @@ -470,6 +483,7 @@ export const loadTableColumnsSuccessAction = [ 'consumed', 'quantity', 'type', + 'product_host_count', ], }, }, diff --git a/webpack/scenes/Subscriptions/components/SubscriptionsTable/SubscriptionsTableHelpers.js b/webpack/scenes/Subscriptions/components/SubscriptionsTable/SubscriptionsTableHelpers.js index b0b9fa3d85c..fa5c417a76a 100644 --- a/webpack/scenes/Subscriptions/components/SubscriptionsTable/SubscriptionsTableHelpers.js +++ b/webpack/scenes/Subscriptions/components/SubscriptionsTable/SubscriptionsTableHelpers.js @@ -50,6 +50,7 @@ const buildTableCollapseRow = (subscriptionGroup) => { name: first.name, virt_only: first.virt_only, hypervisor: first.hypervisor, + product_host_count: 'NA', }; return heading; }; diff --git a/webpack/scenes/Subscriptions/components/SubscriptionsTable/SubscriptionsTableSchema.js b/webpack/scenes/Subscriptions/components/SubscriptionsTable/SubscriptionsTableSchema.js index 82315129f2c..5757d9e57a5 100644 --- a/webpack/scenes/Subscriptions/components/SubscriptionsTable/SubscriptionsTableSchema.js +++ b/webpack/scenes/Subscriptions/components/SubscriptionsTable/SubscriptionsTableSchema.js @@ -146,4 +146,14 @@ export const createSubscriptionsTableSchema = ( formatters: [getEntitlementsFormatter(inlineEditController, hasPermission)], }, }, + { + property: 'product_host_count', + header: { + label: __('Product Host Count'), + formatters: [headerFormatter], + }, + cell: { + formatters: [cellFormatter], + }, + }, ]; diff --git a/webpack/scenes/Subscriptions/components/SubscriptionsTable/__tests__/SubscriptionsTable.fixtures.js b/webpack/scenes/Subscriptions/components/SubscriptionsTable/__tests__/SubscriptionsTable.fixtures.js index a98a29b5861..70ce68162af 100644 --- a/webpack/scenes/Subscriptions/components/SubscriptionsTable/__tests__/SubscriptionsTable.fixtures.js +++ b/webpack/scenes/Subscriptions/components/SubscriptionsTable/__tests__/SubscriptionsTable.fixtures.js @@ -69,6 +69,7 @@ export const genericRow = Immutable({ product_id: 'RH00001', start_date: 'NA', virt_only: undefined, + product_host_count: 'NA', }); export const subOneRowOne = Immutable({