diff --git a/app/lib/actions/katello/capsule_content/update_content_counts.rb b/app/lib/actions/katello/capsule_content/update_content_counts.rb index 7833208479e..4bf846bac3f 100644 --- a/app/lib/actions/katello/capsule_content/update_content_counts.rb +++ b/app/lib/actions/katello/capsule_content/update_content_counts.rb @@ -14,6 +14,10 @@ def run smart_proxy = ::SmartProxy.unscoped.find(input[:smart_proxy_id]) smart_proxy.update_content_counts! end + + def rescue_strategy + Dynflow::Action::Rescue::Skip + end end end end diff --git a/app/models/katello/concerns/smart_proxy_extensions.rb b/app/models/katello/concerns/smart_proxy_extensions.rb index 8ba281c460e..c6c2e218f8e 100644 --- a/app/models/katello/concerns/smart_proxy_extensions.rb +++ b/app/models/katello/concerns/smart_proxy_extensions.rb @@ -122,7 +122,7 @@ def alternate_content_sources end def update_content_counts! - # {:content_view_versions=>{87=>{:repositories=>{1=>{:rpms=>98, :module_streams=>9898}}}}} + # {:content_view_versions=>{87=>{:repositories=>{1=>{:metadata=>{},:counts=>{:rpms=>98, :module_streams=>9898}}}}} new_content_counts = { content_view_versions: {} } smart_proxy_helper = ::Katello::SmartProxyHelper.new(self) repos = smart_proxy_helper.repositories_available_to_capsule @@ -131,24 +131,30 @@ def update_content_counts! repos.each do |repo| repo_mirror_service = repo.backend_service(self).with_mirror_adapter repo_content_counts = repo_mirror_service.latest_content_counts - translated_counts = {} - repo_content_counts.each do |name, count| + translated_counts = {metadata: {}, counts: {}} + translated_counts[:metadata] = { + env_id: repo.environment_id, + library_instance_id: repo.library_instance_or_self.id, + product_id: repo.product_id, + content_type: repo.content_type + } + repo_content_counts&.each do |name, count| count = count[:count] # Some content units in Pulp have the same model if name == 'rpm.package' && repo.content_counts['srpm'] > 0 - translated_counts['srpm'] = repo_mirror_service.count_by_pulpcore_type(::Katello::Pulp3::Srpm) - translated_counts['rpm'] = count - translated_counts['srpm'] + translated_counts[:counts]['srpm'] = repo_mirror_service.count_by_pulpcore_type(::Katello::Pulp3::Srpm) + translated_counts[:counts]['rpm'] = count - translated_counts[:counts]['srpm'] elsif name == 'container.manifest' && repo.content_counts['docker_manifest_list'] > 0 - translated_counts['docker_manifest_list'] = repo_mirror_service.count_by_pulpcore_type(::Katello::Pulp3::DockerManifestList) - translated_counts['docker_manifest'] = count - translated_counts['docker_manifest_list'] + translated_counts[:counts]['docker_manifest_list'] = repo_mirror_service.count_by_pulpcore_type(::Katello::Pulp3::DockerManifestList) + translated_counts[:counts]['docker_manifest'] = count - translated_counts[:counts]['docker_manifest_list'] else - translated_counts[::Katello::Pulp3::PulpContentUnit.katello_name_from_pulpcore_name(name, repo)] = count + translated_counts[:counts][::Katello::Pulp3::PulpContentUnit.katello_name_from_pulpcore_name(name, repo)] = count end end new_content_counts[:content_view_versions][repo.content_view_version_id] ||= { repositories: {}} # Store counts on capsule of archived repos which are reused across environment copies # of the archived repo corresponding to each environment CV version is promoted to. - new_content_counts[:content_view_versions][repo.content_view_version_id][:repositories][repo.content_view_version.archived_repos.find_by(library_instance_id: repo.library_instance_id)&.id] = translated_counts + new_content_counts[:content_view_versions][repo.content_view_version_id][:repositories][repo.id] = translated_counts end update(content_counts: new_content_counts) end @@ -424,10 +430,12 @@ def last_sync_audit Audited::Audit.where(:auditable_id => self, :auditable_type => SmartProxy.name, action: "sync capsule").order(:created_at).last end - def last_sync_time - task = sync_tasks.where.not(:ended_at => nil).where(:result => 'success').order(:ended_at).last + def last_sync_task + sync_tasks.where.not(:ended_at => nil).where(:result => 'success').order(:ended_at).last + end - task&.ended_at || last_sync_audit&.created_at&.to_s + def last_sync_time + last_sync_task&.ended_at || last_sync_audit&.created_at&.to_s end def environment_syncable?(env) diff --git a/app/services/katello/pulp3/repository_mirror.rb b/app/services/katello/pulp3/repository_mirror.rb index 6164486d89e..5f01be34994 100644 --- a/app/services/katello/pulp3/repository_mirror.rb +++ b/app/services/katello/pulp3/repository_mirror.rb @@ -75,7 +75,7 @@ def fetch_repository end def version_href - fetch_repository.latest_version_href + fetch_repository&.latest_version_href end def publication_href @@ -225,7 +225,9 @@ def count_by_pulpcore_type(service_class) end def latest_content_counts - api.repository_versions_api.read(version_href)&.content_summary&.present + version_pulp_href = version_href + return unless version_pulp_href + api.repository_versions_api.read(version_pulp_href)&.content_summary&.present end def pulp3_enabled_repo_types diff --git a/app/views/foreman/smart_proxies/_content_tab.html.erb b/app/views/foreman/smart_proxies/_content_tab.html.erb index 233c548d18f..c54de330c98 100644 --- a/app/views/foreman/smart_proxies/_content_tab.html.erb +++ b/app/views/foreman/smart_proxies/_content_tab.html.erb @@ -1,3 +1,3 @@ <%= javascript_include_tag *webpack_asset_paths('katello', extension: 'js') %> <% @smartProxyId= @smart_proxy.id %> -<%= react_component('Content', smartProxyId: @smartProxyId,) %> +<%= react_component('Content', smartProxyId: @smartProxyId, organizationId: Organization.current&.id,) %> diff --git a/app/views/katello/api/v2/capsule_content/sync_status.json.rabl b/app/views/katello/api/v2/capsule_content/sync_status.json.rabl index d93d10048b1..d4791c8cd27 100644 --- a/app/views/katello/api/v2/capsule_content/sync_status.json.rabl +++ b/app/views/katello/api/v2/capsule_content/sync_status.json.rabl @@ -1,6 +1,9 @@ object @capsule attribute :last_sync_time +node :last_sync_words do |_object| + @capsule&.last_sync_time ? time_ago_in_words(Time.parse(@capsule&.last_sync_time&.to_s)) : nil +end attribute :download_policy @@ -15,6 +18,14 @@ child :last_failed_sync_tasks => :last_failed_sync_tasks do extends 'foreman_tasks/api/tasks/show' end +child :last_sync_task => :last_sync_task do + extends 'foreman_tasks/api/tasks/show' +end + +node :content_counts do + @capsule.content_counts +end + child @lifecycle_environments => :lifecycle_environments do extends 'katello/api/v2/common/identifier' extends 'katello/api/v2/common/org_reference' @@ -27,16 +38,17 @@ child @lifecycle_environments => :lifecycle_environments do if @capsule.has_feature?(SmartProxy::PULP_NODE_FEATURE) || @capsule.has_feature?(SmartProxy::PULP3_FEATURE) node :counts do |env| { - :content_views => env.content_views.non_default.count, - :content_counts => @capsule.content_counts + :content_views => env.content_views.non_default.count } end node :content_views do |env| env.content_views.ignore_generated.map do |content_view| + cvv = ::Katello::ContentViewVersion.in_environment(env).find_by(:content_view => content_view) attributes = { :id => content_view.id, - :cvv_id => ::Katello::ContentViewVersion.in_environment(env).find_by(:content_view => content_view)&.id, + :cvv_id => cvv&.id, + :cvv_version => cvv&.version, :label => content_view.label, :name => content_view.name, :composite => content_view.composite, @@ -50,7 +62,9 @@ child @lifecycle_environments => :lifecycle_environments do { :id => repo.id, :name => repo.name, - :library_id => repo.library_instance_id + :library_id => repo.library_instance_id, + :product_id => repo.product_id, + :content_type => repo.content_type } end } diff --git a/test/models/concerns/smart_proxy_extensions_test.rb b/test/models/concerns/smart_proxy_extensions_test.rb index b8ff5cb3697..2000f82371f 100644 --- a/test/models/concerns/smart_proxy_extensions_test.rb +++ b/test/models/concerns/smart_proxy_extensions_test.rb @@ -95,21 +95,80 @@ def test_update_content_counts python_service.expects(:latest_content_counts).once.returns(python_counts) repos = [yum_repo, file_repo, ansible_repo, container_repo, ostree_repo, deb_repo, python_repo] - yum_repo.content_view_version.expects(:archived_repos).returns(::Katello::Repository.where(id: [yum_repo, file_repo, ansible_repo, container_repo, - ostree_repo, deb_repo, python_repo])) ::Katello::SmartProxyHelper.any_instance.expects(:repositories_available_to_capsule).once.returns(repos) @proxy.update_content_counts! counts = @proxy.content_counts expected_counts = { "content_view_versions" => { yum_repo.content_view_version.id.to_s => { "repositories" => - { yum_repo.id.to_s => { "erratum" => 4, "srpm" => 1, "rpm" => 31, "module_stream" => 7, "rpm.modulemd_defaults" => 3, "package_group" => 7, "rpm.packagecategory" => 1 }, - file_repo.id.to_s => { "file" => 100 }, - ansible_repo.id.to_s => { "ansible.collection" => 802 }, - container_repo.id.to_s => { "container.blob" => 30, "docker_manifest_list" => 1, "docker_manifest" => 9, "docker_tag" => 5 }, - ostree_repo.id.to_s => {"ostree_ref" => 30 }, - deb_repo.id.to_s => { "deb" => 987 }, - python_repo.id.to_s => { "python_package" => 42 } + { yum_repo.id.to_s => { + "metadata" => { + "env_id" => yum_repo.environment.id, + "library_instance_id" => yum_repo.library_instance_or_self.id, + "product_id" => yum_repo.product_id, + "content_type" => yum_repo.content_type + }, + "counts" => { "erratum" => 4, "srpm" => 1, "rpm" => 31, "module_stream" => 7, "rpm.modulemd_defaults" => 3, "package_group" => 7, "rpm.packagecategory" => 1 } + }, + file_repo.id.to_s => { + "metadata" => { + "env_id" => file_repo.environment.id, + "library_instance_id" => file_repo.library_instance_or_self.id, + "product_id" => file_repo.product_id, + "content_type" => file_repo.content_type + }, + "counts" => + { "file" => 100 } + }, + ansible_repo.id.to_s => { + "metadata" => { + "env_id" => ansible_repo.environment.id, + "library_instance_id" => ansible_repo.library_instance_or_self.id, + "product_id" => ansible_repo.product_id, + "content_type" => ansible_repo.content_type}, + "counts" => + { "ansible.collection" => 802 } + }, + container_repo.id.to_s => { + "metadata" => { + "env_id" => container_repo.environment.id, + "library_instance_id" => container_repo.library_instance_or_self.id, + "product_id" => container_repo.product_id, + "content_type" => container_repo.content_type + }, + "counts" => + { "container.blob" => 30, "docker_manifest_list" => 1, "docker_manifest" => 9, "docker_tag" => 5 } + }, + ostree_repo.id.to_s => { + "metadata" => { + "env_id" => ostree_repo.environment.id, + "library_instance_id" => ostree_repo.library_instance_or_self.id, + "product_id" => ostree_repo.product_id, + "content_type" => ostree_repo.content_type + }, + "counts" => + {"ostree_ref" => 30 } + }, + deb_repo.id.to_s => { + "metadata" => { + "env_id" => deb_repo.environment.id, + "library_instance_id" => deb_repo.library_instance_or_self.id, + "product_id" => deb_repo.product_id, + "content_type" => deb_repo.content_type + }, + "counts" => + { "deb" => 987 } + }, + python_repo.id.to_s => { + "metadata" => { + "env_id" => python_repo.environment.id, + "library_instance_id" => python_repo.library_instance_or_self.id, + "product_id" => python_repo.product_id, + "content_type" => python_repo.content_type + }, + "counts" => + { "python_package" => 42 } + } } } } diff --git a/webpack/scenes/ContentViews/Details/Repositories/RepoIcon.js b/webpack/scenes/ContentViews/Details/Repositories/RepoIcon.js index 8da108f3f6d..30a47cb6692 100644 --- a/webpack/scenes/ContentViews/Details/Repositories/RepoIcon.js +++ b/webpack/scenes/ContentViews/Details/Repositories/RepoIcon.js @@ -1,6 +1,6 @@ import React from 'react'; import { Tooltip } from '@patternfly/react-core'; -import { BundleIcon, MiddlewareIcon, BoxIcon, CodeBranchIcon, FanIcon, TenantIcon } from '@patternfly/react-icons'; +import { BundleIcon, MiddlewareIcon, BoxIcon, CodeBranchIcon, FanIcon, TenantIcon, AnsibleTowerIcon } from '@patternfly/react-icons'; import PropTypes from 'prop-types'; const RepoIcon = ({ type }) => { @@ -10,6 +10,7 @@ const RepoIcon = ({ type }) => { ostree: CodeBranchIcon, file: TenantIcon, deb: FanIcon, + ansible_collection: AnsibleTowerIcon, }; const Icon = iconMap[type] || BoxIcon; diff --git a/webpack/scenes/SmartProxy/Content.js b/webpack/scenes/SmartProxy/Content.js index 57c096d0cd1..0d838c4c0d9 100644 --- a/webpack/scenes/SmartProxy/Content.js +++ b/webpack/scenes/SmartProxy/Content.js @@ -2,16 +2,24 @@ import React from 'react'; import PropTypes from 'prop-types'; import SmartProxyExpandableTable from './SmartProxyExpandableTable'; -const Content = ({ smartProxyId }) => ( - +const Content = ({ smartProxyId, organizationId }) => ( + ); Content.propTypes = { - smartProxyId: PropTypes.number, + smartProxyId: PropTypes.oneOfType([ + PropTypes.number, + PropTypes.string, // The API can sometimes return strings + ]), + organizationId: PropTypes.oneOfType([ + PropTypes.number, + PropTypes.string, // The API can sometimes return strings + ]), }; Content.defaultProps = { smartProxyId: null, + organizationId: null, }; export default Content; diff --git a/webpack/scenes/SmartProxy/ExpandableCvDetails.js b/webpack/scenes/SmartProxy/ExpandableCvDetails.js index 31a63adc64e..fcfe8425310 100644 --- a/webpack/scenes/SmartProxy/ExpandableCvDetails.js +++ b/webpack/scenes/SmartProxy/ExpandableCvDetails.js @@ -9,13 +9,14 @@ import ContentViewIcon from '../ContentViews/components/ContentViewIcon'; import { useSet } from '../../components/Table/TableHooks'; import ExpandedSmartProxyRepositories from './ExpandedSmartProxyRepositories'; -const ExpandableCvDetails = ({ contentViews, counts }) => { +const ExpandableCvDetails = ({ contentViews, contentCounts, envId }) => { const columnHeaders = [ __('Content view'), + __('Version'), __('Last published'), - __('Synced to smart proxy'), + __('Synced'), ]; - const { content_counts: contentCounts } = counts; + // const { content_counts: contentCounts } = counts; const expandedTableRows = useSet([]); const tableRowIsExpanded = id => expandedTableRows.has(id); @@ -38,18 +39,22 @@ const ExpandableCvDetails = ({ contentViews, counts }) => { {contentViews.map((cv, rowIndex) => { const { - id, name: cvName, composite, up_to_date: upToDate, cvv_id: version, repositories, + id, name: cvName, composite, up_to_date: upToDate, + cvv_id: versionId, cvv_version: version, repositories, } = cv; - const upToDateVal = upToDate ? : ; - const isExpanded = tableRowIsExpanded(version); + const upToDateVal = upToDate ? : ; + const isExpanded = tableRowIsExpanded(versionId); return ( - - + + expandedTableRows.onToggle(isOpen, version), + onToggle: (_event, _rInx, isOpen) => + expandedTableRows.onToggle(isOpen, versionId), }} /> @@ -58,14 +63,19 @@ const ExpandableCvDetails = ({ contentViews, counts }) => { description={{cvName}} /> + + {__('Version ')}{version} + {upToDateVal} @@ -80,16 +90,18 @@ const ExpandableCvDetails = ({ contentViews, counts }) => { ExpandableCvDetails.propTypes = { contentViews: PropTypes.arrayOf(PropTypes.shape({})), - counts: PropTypes.shape({ - content_counts: PropTypes.shape({ - content_view_versions: PropTypes.shape({}), - }), + contentCounts: PropTypes.shape({ + content_view_versions: PropTypes.shape({}), }), + envId: PropTypes.oneOfType([ + PropTypes.number, + PropTypes.string, // The API can sometimes return strings + ]).isRequired, }; ExpandableCvDetails.defaultProps = { contentViews: [], - counts: {}, + contentCounts: {}, }; export default ExpandableCvDetails; diff --git a/webpack/scenes/SmartProxy/ExpandedSmartProxyRepositories.js b/webpack/scenes/SmartProxy/ExpandedSmartProxyRepositories.js index c7ef1520f6a..745825c975e 100644 --- a/webpack/scenes/SmartProxy/ExpandedSmartProxyRepositories.js +++ b/webpack/scenes/SmartProxy/ExpandedSmartProxyRepositories.js @@ -4,53 +4,136 @@ import PropTypes from 'prop-types'; import { translate as __ } from 'foremanReact/common/I18n'; import { DataList, DataListItem, DataListItemRow, DataListItemCells, DataListCell } from '@patternfly/react-core'; import AdditionalCapsuleContent from './AdditionalCapsuleContent'; +import InactiveText from '../ContentViews/components/InactiveText'; +import RepoIcon from '../ContentViews/Details/Repositories/RepoIcon'; + +const ExpandedSmartProxyRepositories = ({ + contentCounts, repositories, syncedToCapsule, envId, +}) => { + const filterDataByEnvId = () => { + const filteredData = {}; + + Object.keys(contentCounts).forEach((key) => { + const entry = contentCounts[key]; + if (entry.metadata.env_id === envId) { + filteredData[key] = entry; + } + }); + + return filteredData; + }; + const envContentCounts = filterDataByEnvId(); -const ExpandedSmartProxyRepositories = ({ contentCounts, repositories }) => { const getRepositoryNameById = id => (repositories.find(repo => - Number(repo.id) === Number(id)) || {}).name; + Number(repo.id) === Number(id) || Number(repo.library_id) === Number(id)) || {}).name; + + const dataListCellLists = (repoCounts, repo) => { + const cellList = []; + /* eslint-disable max-len */ + cellList.push({getRepositoryNameById(envContentCounts[repo].metadata.library_instance_id)}); + cellList.push(); + cellList.push({envContentCounts[repo].counts.rpm ? `${envContentCounts[repo].counts.rpm} Packages` : 'N/A'}); + cellList.push(); + /* eslint-enable max-len */ + return cellList; + }; - const dataListCellLists = (repo) => { + const dataListCellListsNotSynced = (repo) => { const cellList = []; /* eslint-disable max-len */ - cellList.push({getRepositoryNameById(repo)}); - cellList.push({contentCounts[repo].rpm ? `${contentCounts[repo].rpm} Packages` : 'N/A'}); - cellList.push(); + cellList.push({repo.name}); + cellList.push(); + cellList.push(); + cellList.push(); /* eslint-enable max-len */ return cellList; }; + if (syncedToCapsule) { + return ( + + + + + {__('Repository')} + , + {__('Type')}, + {__('Packages')}, + {__('Additional content')}, + ]} + /> + + + {Object.keys(envContentCounts).length ? + Object.keys(envContentCounts).map((repo, index) => ( + + + + + + )) : + + + ]} + /> + + + } + + ); + } + return ( - {__('Repositories')} + {__('Repository')} , - {__('Packages')}, - {__('Additional Content')}, + {__('Type')}, + {__('Packages')}, + {__('Additional content')}, ]} /> - {Object.keys(contentCounts).map((repo, index) => ( - + {repositories.length ? + repositories.map((repo, index) => ( + + + + + + )) : + - + ]} + /> - ))} - - ); + } + ); }; ExpandedSmartProxyRepositories.propTypes = { contentCounts: PropTypes.shape({}), repositories: PropTypes.arrayOf(PropTypes.shape({})), + syncedToCapsule: PropTypes.bool, + envId: PropTypes.oneOfType([ + PropTypes.number, + PropTypes.string, // The API can sometimes return strings + ]).isRequired, }; ExpandedSmartProxyRepositories.defaultProps = { contentCounts: {}, repositories: [{}], + syncedToCapsule: false, }; export default ExpandedSmartProxyRepositories; diff --git a/webpack/scenes/SmartProxy/SmartProxyContentActions.js b/webpack/scenes/SmartProxy/SmartProxyContentActions.js index 00e21e3b357..a9a4b103ab9 100644 --- a/webpack/scenes/SmartProxy/SmartProxyContentActions.js +++ b/webpack/scenes/SmartProxy/SmartProxyContentActions.js @@ -1,11 +1,14 @@ -import { API_OPERATIONS, get } from 'foremanReact/redux/API'; +import { API_OPERATIONS, get, post } from 'foremanReact/redux/API'; +import { translate as __ } from 'foremanReact/common/I18n'; import api, { foremanApi, orgId } from '../../services/api'; -import SMART_PROXY_CONTENT_KEY, { SMART_PROXY_KEY } from './SmartProxyContentConstants'; +import SMART_PROXY_CONTENT_KEY, { SMART_PROXY_COUNTS_UPDATE_KEY, SMART_PROXY_KEY } from './SmartProxyContentConstants'; +import { renderTaskStartedToast } from '../Tasks/helpers'; +import { getResponseErrorMsgs } from '../../utils/helpers'; -const getSmartProxyContent = ({ smartProxyId }) => get({ +const getSmartProxyContent = ({ smartProxyId, organizationId }) => get({ type: API_OPERATIONS.GET, key: SMART_PROXY_CONTENT_KEY, - url: api.getApiUrl(`/capsules/${smartProxyId}/content/sync?${orgId()}`), + url: api.getApiUrl(organizationId ? `/capsules/${smartProxyId}/content/sync?organization_id=${organizationId}` : `/capsules/${smartProxyId}/content/sync`), }); export const getSmartProxies = () => get({ @@ -15,4 +18,14 @@ export const getSmartProxies = () => get({ params: { organization_id: orgId(), per_page: 'all' }, }); +export const updateSmartProxyContentCounts = smartProxyId => post({ + type: API_OPERATIONS.POST, + key: SMART_PROXY_COUNTS_UPDATE_KEY, + url: api.getApiUrl(`/capsules/${smartProxyId}/content/update_counts`), + handleSuccess: (response) => { + renderTaskStartedToast(response?.data, __('Smart proxy content count refresh has started in the background')); + }, + errorToast: error => __(`Something went wrong while refreshing content counts: ${getResponseErrorMsgs(error.response)}`), +}); + export default getSmartProxyContent; diff --git a/webpack/scenes/SmartProxy/SmartProxyContentConstants.js b/webpack/scenes/SmartProxy/SmartProxyContentConstants.js index 5db4da1414e..14bc5c92630 100644 --- a/webpack/scenes/SmartProxy/SmartProxyContentConstants.js +++ b/webpack/scenes/SmartProxy/SmartProxyContentConstants.js @@ -1,3 +1,4 @@ const SMART_PROXY_CONTENT_KEY = 'SMART_PROXY_CONTENT'; export const SMART_PROXY_KEY = 'SMART_PROXY'; +export const SMART_PROXY_COUNTS_UPDATE_KEY = 'SMART_PROXY_COUNTS_UPDATE_KEY'; export default SMART_PROXY_CONTENT_KEY; diff --git a/webpack/scenes/SmartProxy/SmartProxyContentTable.js b/webpack/scenes/SmartProxy/SmartProxyContentTable.js deleted file mode 100644 index 0fec8661b63..00000000000 --- a/webpack/scenes/SmartProxy/SmartProxyContentTable.js +++ /dev/null @@ -1,164 +0,0 @@ -import React, { useState, useEffect } from 'react'; -import useDeepCompareEffect from 'use-deep-compare-effect'; -import PropTypes from 'prop-types'; -import { useSelector, useDispatch } from 'react-redux'; -import { wrappable } from '@patternfly/react-table'; -import { CheckCircleIcon, TimesCircleIcon } from '@patternfly/react-icons'; -import { STATUS } from 'foremanReact/constants'; -import { translate as __ } from 'foremanReact/common/I18n'; -import { urlBuilder } from 'foremanReact/common/urlHelpers'; -import LongDateTime from 'foremanReact/components/common/dates/LongDateTime'; -import MainTable from '../../components/Table/MainTable'; -import getSmartProxyContent from './SmartProxyContentActions'; -import ContentViewIcon from '../../scenes/ContentViews/components/ContentViewIcon'; -import { - selectSmartProxyContent, - selectSmartProxyContentStatus, - selectSmartProxyContentError, -} from './SmartProxyContentSelectors'; - -const SmartProxyContentTable = ({ smartProxyId }) => { - const dispatch = useDispatch(); - const [rows, setRows] = useState([]); - const response = useSelector(state => selectSmartProxyContent(state)); - const status = useSelector(state => selectSmartProxyContentStatus(state)); - const error = useSelector(state => selectSmartProxyContentError(state)); - const columnHeaders = [ - { - title: __('Environment'), - transforms: [wrappable], - }, - { - title: __('Content view'), - transforms: [wrappable], - }, - { - title: __('Type'), - transforms: [wrappable], - }, - { - title: __('Last published'), - transforms: [wrappable], - }, - { - title: __('Repositories'), - transforms: [wrappable], - }, - { - title: __('Synced to smart proxy'), - transforms: [wrappable], - }, - ]; - - const buildrows = (results) => { - const newRows = []; - let envCount = 0; - results.forEach((env) => { - const { name, content_views: contentViews } = env; - const cellEnv = { - isOpen: false, - cells: [name, null, null, null, null, null], - }; - newRows.push(cellEnv); - contentViews.forEach((cv) => { - const { - id, name: cvName, composite, last_published: lastPublished, up_to_date: upToDate, counts, - } = cv; - const { repositories } = counts; - const cvType = ; - const upToDateVal = upToDate ? : ; - const cellCv = - { - parent: envCount, - cells: [ - { - title: null, - props: { - colSpan: 1, - }, - }, - { - title: {cvName}, - props: { - colSpan: 1, - }, - }, - { - title: cvType, - props: { - colSpan: 1, - }, - }, - { - title: , - props: { - colSpan: 1, - }, - }, - { - title: repositories, - props: { - colSpan: 1, - }, - }, - { - title: upToDateVal, - props: { - colSpan: 1, - }, - }, - ], - }; - newRows.push(cellCv); - }); - envCount = newRows.length; - }); - return newRows; - }; - - const onCollapse = (row, setRow) => (event, rowKey, isOpen) => { - const newRows = [...row]; - newRows[rowKey].isOpen = isOpen; - setRow(newRows); - }; - - useEffect( - () => { - dispatch(getSmartProxyContent({ smartProxyId })); - } - , [dispatch, smartProxyId], - ); - - useDeepCompareEffect(() => { - if (status !== STATUS.PENDING && response) { - const { lifecycle_environments: env } = response; - setRows(buildrows(env)); - } - }, [response, status, error]); - - - return ( - - ); -}; - -SmartProxyContentTable.propTypes = { - smartProxyId: PropTypes.number, -}; - -SmartProxyContentTable.defaultProps = { - smartProxyId: null, -}; - -export default SmartProxyContentTable; diff --git a/webpack/scenes/SmartProxy/SmartProxyExpandableTable.js b/webpack/scenes/SmartProxy/SmartProxyExpandableTable.js index e90663acf60..6da2412b3a2 100644 --- a/webpack/scenes/SmartProxy/SmartProxyExpandableTable.js +++ b/webpack/scenes/SmartProxy/SmartProxyExpandableTable.js @@ -1,9 +1,9 @@ import React, { useState, useCallback } from 'react'; -import { useSelector } from 'react-redux'; +import { useSelector, useDispatch } from 'react-redux'; import PropTypes from 'prop-types'; import { translate as __ } from 'foremanReact/common/I18n'; import { Thead, Tbody, Th, Tr, Td } from '@patternfly/react-table'; -import getSmartProxyContent from './SmartProxyContentActions'; +import getSmartProxyContent, { updateSmartProxyContentCounts } from './SmartProxyContentActions'; import { selectSmartProxyContent, selectSmartProxyContentStatus, @@ -12,25 +12,40 @@ import { import { useSet } from '../../components/Table/TableHooks'; import TableWrapper from '../../components/Table/TableWrapper'; import ExpandableCvDetails from './ExpandableCvDetails'; +import ComponentEnvironments from '../ContentViews/Details/ComponentContentViews/ComponentEnvironments'; +import LastSync from '../ContentViews/Details/Repositories/LastSync'; -const SmartProxyExpandableTable = ({ smartProxyId }) => { +const SmartProxyExpandableTable = ({ smartProxyId, organizationId }) => { const response = useSelector(selectSmartProxyContent); const status = useSelector(selectSmartProxyContentStatus); const error = useSelector(selectSmartProxyContentError); const [searchQuery, updateSearchQuery] = useState(''); const expandedTableRows = useSet([]); const tableRowIsExpanded = id => expandedTableRows.has(id); + const dispatch = useDispatch(); let metadata = {}; const { - lifecycle_environments: results, + lifecycle_environments: results, last_sync_task: lastTask, last_sync_words: lastSyncWords, + content_counts: contentCounts, } = response; if (results) { metadata = { total: results.length, subtotal: results.length }; } const columnHeaders = [ __('Environment'), + __('Last sync'), ]; - const fetchItems = useCallback(() => getSmartProxyContent({ smartProxyId }), [smartProxyId]); + + const refreshCountAction = { + title: __('Refresh counts'), + onClick: () => { + dispatch(updateSmartProxyContentCounts(smartProxyId)); + }, + }; + const fetchItems = useCallback( + () => getSmartProxyContent({ smartProxyId, organizationId }), + [smartProxyId, organizationId], + ); const emptyContentTitle = __('No content views yet'); const emptyContentBody = __('You currently have no content views to display'); @@ -38,6 +53,7 @@ const SmartProxyExpandableTable = ({ smartProxyId }) => { const emptySearchBody = __('Try changing your search settings.'); const alwaysHideToolbar = true; const hidePagination = true; + return ( { {col} ))} + { - results?.map((env, rowIndex) => { - const { - name, id, content_views: contentViews, counts, - } = env; - const isExpanded = tableRowIsExpanded(id); - return ( - - - - expandedTableRows.onToggle(isOpen, id), - }} - /> - {name} - - - - - - - - ); - }) - } - + results?.map((env, rowIndex) => { + const { + id, content_views: contentViews, + } = env; + const isExpanded = tableRowIsExpanded(id); + return ( + + + + expandedTableRows.onToggle(isOpen, id), + }} + /> + + + + + + + {isExpanded ? + : + <>} + + + + ); + }) + } + ); }; @@ -106,6 +138,14 @@ SmartProxyExpandableTable.propTypes = { PropTypes.number, PropTypes.string, // The API can sometimes return strings ]).isRequired, + organizationId: PropTypes.oneOfType([ + PropTypes.number, + PropTypes.string, // The API can sometimes return strings + ]), +}; + +SmartProxyExpandableTable.defaultProps = { + organizationId: null, }; export default SmartProxyExpandableTable; diff --git a/webpack/scenes/SmartProxy/__tests__/SmartProxyContentResult.fixtures.json b/webpack/scenes/SmartProxy/__tests__/SmartProxyContentResult.fixtures.json deleted file mode 100644 index 2260744198d..00000000000 --- a/webpack/scenes/SmartProxy/__tests__/SmartProxyContentResult.fixtures.json +++ /dev/null @@ -1,140 +0,0 @@ -{ - "last_sync_time": "2021-01-12 14:39:01 -0500", - "active_sync_tasks": [], - "last_failed_sync_tasks": [], - "lifecycle_environments": [ - { - "library": true, - "id": 1, - "name": "Library", - "label": "Library", - "description": null, - "organization_id": 1, - "organization": { - "name": "Default Organization", - "label": "Default_Organization", - "id": 1 - }, - "syncable": false, - "counts": { - "content_hosts": 0, - "content_views": 2, - "products": 1 - }, - "content_views": [ - { - "id": 1, - "label": "Default_Organization_View", - "name": "Default Organization View", - "composite": false, - "last_published": "2021-01-05 09:55:20 -0500", - "default": true, - "up_to_date": true, - "counts": { - "content_hosts": 0, - "products": 1, - "repositories": 2 - } - }, - { - "id": 2, - "label": "cv1", - "name": "cv1", - "composite": false, - "last_published": "2021-01-12 14:36:31 -0500", - "default": false, - "up_to_date": true, - "counts": { - "content_hosts": 0, - "products": 1, - "repositories": 1 - } - }, - { - "id": 3, - "label": "cv2", - "name": "cv2", - "composite": false, - "last_published": "2021-01-12 14:37:05 -0500", - "default": false, - "up_to_date": true, - "counts": { - "content_hosts": 0, - "products": 1, - "repositories": 1 - } - } - ] - }, - { - "library": false, - "id": 2, - "name": "dev", - "label": "dev", - "description": null, - "organization_id": 1, - "organization": { - "name": "Default Organization", - "label": "Default_Organization", - "id": 1 - }, - "syncable": false, - "counts": { - "content_hosts": 0, - "content_views": 1, - "products": 1 - }, - "content_views": [ - { - "id": 2, - "label": "cv1", - "name": "cv1", - "composite": false, - "last_published": "2021-01-12 14:36:31 -0500", - "default": false, - "up_to_date": true, - "counts": { - "content_hosts": 0, - "products": 1, - "repositories": 1 - } - } - ] - }, - { - "library": false, - "id": 3, - "name": "test", - "label": "test", - "description": null, - "organization_id": 1, - "organization": { - "name": "Default Organization", - "label": "Default_Organization", - "id": 1 - }, - "syncable": true, - "counts": { - "content_hosts": 0, - "content_views": 1, - "products": 1 - }, - "content_views": [ - { - "id": 2, - "label": "cv1", - "name": "cv1", - "composite": false, - "last_published": "2021-01-12 14:36:31 -0500", - "default": false, - "up_to_date": false, - "counts": { - "content_hosts": 0, - "products": 1, - "repositories": 0 - } - } - ] - } - ] -} \ No newline at end of file diff --git a/webpack/scenes/SmartProxy/__tests__/SmartProxyContentTest.fixtures.json b/webpack/scenes/SmartProxy/__tests__/SmartProxyContentTest.fixtures.json new file mode 100644 index 00000000000..6d24c7ed0f4 --- /dev/null +++ b/webpack/scenes/SmartProxy/__tests__/SmartProxyContentTest.fixtures.json @@ -0,0 +1,799 @@ +{ + "last_sync_time": "2023-10-23 09:52:53 -0400", + "download_policy": "on_demand", + "active_sync_tasks": [], + "last_failed_sync_tasks": [], + "last_sync_task": { + "id": "d32ad89d-f37b-44d3-9bb0-e64f6758df3e", + "label": "Actions::Katello::CapsuleContent::Sync", + "pending": false, + "action": "Synchronize smart proxy 'centos8-proxy-devel2.sajha.example.com'", + "username": "admin", + "started_at": "2023-10-23 09:52:25 -0400", + "ended_at": "2023-10-23 09:52:53 -0400", + "state": "stopped", + "result": "success", + "progress": 1, + "input": { + "options": { + "content_view_id": 12, + "environment_id": 4 + }, + "smart_proxy": { + "id": 2, + "name": "centos8-proxy-devel2.sajha.example.com" + }, + "services_checked": [ + "pulp3" + ], + "smart_proxy_id": 2, + "locale": "en", + "current_request_id": "a5e3fd5b-daa9-4381-8273-077793a006ec", + "current_timezone": "America/New_York", + "current_organization_id": 1, + "current_location_id": 2, + "current_user_id": 4 + }, + "output": {}, + "humanized": { + "action": "Synchronize smart proxy", + "input": [ + "'centos8-proxy-devel2.sajha.example.com'" + ], + "output": "", + "errors": [] + }, + "cli_example": null, + "start_at": "2023-10-23 09:52:25 -0400", + "available_actions": { + "cancellable": false, + "resumable": false + } + }, + "lifecycle_environments": [ + { + "library": true, + "id": 1, + "name": "Library", + "label": "Library", + "description": null, + "organization_id": 1, + "organization": { + "name": "Default Organization", + "label": "Default_Organization", + "id": 1 + }, + "syncable": false, + "counts": { + "content_views": 3 + }, + "content_views": [ + { + "id": 1, + "cvv_id": 1, + "cvv_version": "1.0", + "label": "Default_Organization_View", + "name": "Default Organization View", + "composite": false, + "last_published": "2023-08-22 14:50:34 -0400", + "default": true, + "up_to_date": true, + "counts": { + "repositories": 7 + }, + "repositories": [ + { + "id": 3, + "name": "test_ansible", + "library_id": null + }, + { + "id": 20, + "name": "Advanced Virtualization CodeReady Builder for RHEL 8 Power little endian RPMs", + "library_id": null + }, + { + "id": 1, + "name": "repo1", + "library_id": null + }, + { + "id": 16, + "name": "file_repo", + "library_id": null + }, + { + "id": 40, + "name": "python-pulp", + "library_id": null + }, + { + "id": 71, + "name": "Advanced Virtualization CodeReady Builder for RHEL 8 IBM z Systems RPMs", + "library_id": null + }, + { + "id": 72, + "name": "deb_test", + "library_id": null + } + ] + }, + { + "id": 12, + "cvv_id": 30, + "cvv_version": "2.0", + "label": "cv1", + "name": "cv1", + "composite": false, + "last_published": "2023-10-23 09:52:08 -0400", + "default": false, + "up_to_date": true, + "counts": { + "repositories": 2 + }, + "repositories": [ + { + "id": 171, + "name": "python-pulp", + "library_id": 40 + }, + { + "id": 170, + "name": "repo1", + "library_id": 1 + } + ] + }, + { + "id": 13, + "cvv_id": 29, + "cvv_version": "2.0", + "label": "cv2", + "name": "cv2", + "composite": false, + "last_published": "2023-10-23 09:48:16 -0400", + "default": false, + "up_to_date": true, + "counts": { + "repositories": 5 + }, + "repositories": [ + { + "id": 163, + "name": "python-pulp", + "library_id": 40 + }, + { + "id": 161, + "name": "repo1", + "library_id": 1 + }, + { + "id": 164, + "name": "deb_test", + "library_id": 72 + }, + { + "id": 160, + "name": "test_ansible", + "library_id": 3 + }, + { + "id": 162, + "name": "file_repo", + "library_id": 16 + } + ] + }, + { + "id": 14, + "cvv_id": 28, + "cvv_version": "1.0", + "label": "ccv1", + "name": "ccv1", + "composite": true, + "last_published": "2023-10-23 09:45:23 -0400", + "default": false, + "up_to_date": true, + "counts": { + "repositories": 5 + }, + "repositories": [ + { + "id": 146, + "name": "repo1", + "library_id": 1 + }, + { + "id": 147, + "name": "test_ansible", + "library_id": 3 + }, + { + "id": 149, + "name": "file_repo", + "library_id": 16 + }, + { + "id": 148, + "name": "deb_test", + "library_id": 72 + }, + { + "id": 145, + "name": "python-pulp", + "library_id": 40 + } + ] + } + ] + }, + { + "library": false, + "id": 2, + "name": "dev", + "label": "dev", + "description": null, + "organization_id": 1, + "organization": { + "name": "Default Organization", + "label": "Default_Organization", + "id": 1 + }, + "syncable": false, + "counts": { + "content_views": 1 + }, + "content_views": [ + { + "id": 12, + "cvv_id": 26, + "cvv_version": "1.0", + "label": "cv1", + "name": "cv1", + "composite": false, + "last_published": "2023-10-20 14:44:49 -0400", + "default": false, + "up_to_date": true, + "counts": { + "repositories": 2 + }, + "repositories": [ + { + "id": 130, + "name": "python-pulp", + "library_id": 40 + }, + { + "id": 129, + "name": "repo1", + "library_id": 1 + } + ] + } + ] + }, + { + "library": false, + "id": 4, + "name": "qa", + "label": "qa", + "description": null, + "organization_id": 1, + "organization": { + "name": "Default Organization", + "label": "Default_Organization", + "id": 1 + }, + "syncable": false, + "counts": { + "content_views": 3 + }, + "content_views": [ + { + "id": 12, + "cvv_id": 30, + "cvv_version": "2.0", + "label": "cv1", + "name": "cv1", + "composite": false, + "last_published": "2023-10-23 09:52:08 -0400", + "default": false, + "up_to_date": true, + "counts": { + "repositories": 2 + }, + "repositories": [ + { + "id": 171, + "name": "python-pulp", + "library_id": 40 + }, + { + "id": 170, + "name": "repo1", + "library_id": 1 + } + ] + }, + { + "id": 13, + "cvv_id": 29, + "cvv_version": "2.0", + "label": "cv2", + "name": "cv2", + "composite": false, + "last_published": "2023-10-23 09:48:16 -0400", + "default": false, + "up_to_date": true, + "counts": { + "repositories": 5 + }, + "repositories": [ + { + "id": 163, + "name": "python-pulp", + "library_id": 40 + }, + { + "id": 161, + "name": "repo1", + "library_id": 1 + }, + { + "id": 164, + "name": "deb_test", + "library_id": 72 + }, + { + "id": 160, + "name": "test_ansible", + "library_id": 3 + }, + { + "id": 162, + "name": "file_repo", + "library_id": 16 + } + ] + }, + { + "id": 14, + "cvv_id": 28, + "cvv_version": "1.0", + "label": "ccv1", + "name": "ccv1", + "composite": true, + "last_published": "2023-10-23 09:45:23 -0400", + "default": false, + "up_to_date": true, + "counts": { + "repositories": 5 + }, + "repositories": [ + { + "id": 146, + "name": "repo1", + "library_id": 1 + }, + { + "id": 147, + "name": "test_ansible", + "library_id": 3 + }, + { + "id": 149, + "name": "file_repo", + "library_id": 16 + }, + { + "id": 148, + "name": "deb_test", + "library_id": 72 + }, + { + "id": 145, + "name": "python-pulp", + "library_id": 40 + } + ] + } + ] + } + ], + "last_sync_words": "3 minutes", + "unsyncable_content_types": [], + "content_counts": { + "content_view_versions": { + "1": { + "repositories": { + "1": { + "counts": { + "rpm": 22, + "erratum": 7, + "rpm.modulemd": 14, + "package_group": 2, + "rpm.packagecategory": 1, + "rpm.distribution_tree": 1, + "rpm.modulemd_defaults": 3 + }, + "metadata": { + "env_id": 1, + "library_instance_id": 1 + } + }, + "3": { + "counts": { + "ansible_collection": 10 + }, + "metadata": { + "env_id": 1, + "library_instance_id": 3 + } + }, + "16": { + "counts": { + "file": 3 + }, + "metadata": { + "env_id": 1, + "library_instance_id": 16 + } + }, + "20": { + "counts": { + "rpm": 71, + "erratum": 31, + "rpm.modulemd": 31, + "rpm.repo_metadata_file": 1 + }, + "metadata": { + "env_id": 1, + "library_instance_id": 20 + } + }, + "40": { + "counts": { + "python_package": 275 + }, + "metadata": { + "env_id": 1, + "library_instance_id": 40 + } + }, + "71": { + "counts": {}, + "metadata": { + "env_id": 1, + "library_instance_id": 71 + } + }, + "72": { + "counts": { + "deb": 6, + "deb.release": 2, + "deb.release_file": 2, + "deb.package_index": 5, + "deb.release_component": 2, + "deb.release_architecture": 5, + "deb.package_release_component": 6 + }, + "metadata": { + "env_id": 1, + "library_instance_id": 72 + } + } + } + }, + "26": { + "repositories": { + "133": { + "counts": { + "rpm": 22, + "erratum": 7, + "rpm.modulemd": 14, + "package_group": 2, + "rpm.packagecategory": 1, + "rpm.distribution_tree": 1, + "rpm.modulemd_defaults": 3 + }, + "metadata": { + "env_id": 2, + "library_instance_id": 1 + } + }, + "134": { + "counts": { + "python_package": 275 + }, + "metadata": { + "env_id": 2, + "library_instance_id": 40 + } + } + } + }, + "28": { + "repositories": { + "150": { + "counts": { + "python_package": 275 + }, + "metadata": { + "env_id": 1, + "library_instance_id": 40 + } + }, + "151": { + "counts": { + "rpm": 22, + "erratum": 7, + "rpm.modulemd": 14, + "package_group": 2, + "rpm.packagecategory": 1, + "rpm.distribution_tree": 1, + "rpm.modulemd_defaults": 3 + }, + "metadata": { + "env_id": 1, + "library_instance_id": 1 + } + }, + "152": { + "counts": { + "ansible_collection": 10 + }, + "metadata": { + "env_id": 1, + "library_instance_id": 3 + } + }, + "153": { + "counts": { + "deb": 6, + "deb.release": 2, + "deb.release_file": 2, + "deb.package_index": 5, + "deb.release_component": 2, + "deb.release_architecture": 5, + "deb.package_release_component": 6 + }, + "metadata": { + "env_id": 1, + "library_instance_id": 72 + } + }, + "154": { + "counts": { + "file": 3 + }, + "metadata": { + "env_id": 1, + "library_instance_id": 16 + } + }, + "155": { + "counts": { + "python_package": 275 + }, + "metadata": { + "env_id": 4, + "library_instance_id": 40 + } + }, + "156": { + "counts": { + "rpm": 22, + "erratum": 7, + "rpm.modulemd": 14, + "package_group": 2, + "rpm.packagecategory": 1, + "rpm.distribution_tree": 1, + "rpm.modulemd_defaults": 3 + }, + "metadata": { + "env_id": 4, + "library_instance_id": 1 + } + }, + "157": { + "counts": { + "ansible_collection": 10 + }, + "metadata": { + "env_id": 4, + "library_instance_id": 3 + } + }, + "158": { + "counts": { + "deb": 6, + "deb.release": 2, + "deb.release_file": 2, + "deb.package_index": 5, + "deb.release_component": 2, + "deb.release_architecture": 5, + "deb.package_release_component": 6 + }, + "metadata": { + "env_id": 4, + "library_instance_id": 72 + } + }, + "159": { + "counts": { + "file": 3 + }, + "metadata": { + "env_id": 4, + "library_instance_id": 16 + } + } + } + }, + "29": { + "repositories": { + "140": { + "counts": { + "rpm": 22, + "erratum": 7, + "rpm.modulemd": 14, + "package_group": 2, + "rpm.packagecategory": 1, + "rpm.distribution_tree": 1, + "rpm.modulemd_defaults": 3 + }, + "metadata": { + "env_id": 1, + "library_instance_id": 1 + } + }, + "141": { + "counts": { + "ansible_collection": 10 + }, + "metadata": { + "env_id": 1, + "library_instance_id": 3 + } + }, + "142": { + "counts": { + "file": 3 + }, + "metadata": { + "env_id": 1, + "library_instance_id": 16 + } + }, + "143": { + "counts": { + "python_package": 275 + }, + "metadata": { + "env_id": 1, + "library_instance_id": 40 + } + }, + "144": { + "counts": { + "deb": 6, + "deb.release": 2, + "deb.release_file": 2, + "deb.package_index": 5, + "deb.release_component": 2, + "deb.release_architecture": 5, + "deb.package_release_component": 6 + }, + "metadata": { + "env_id": 1, + "library_instance_id": 72 + } + }, + "165": { + "counts": { + "ansible_collection": 10 + }, + "metadata": { + "env_id": 4, + "library_instance_id": 3 + } + }, + "166": { + "counts": { + "rpm": 22, + "erratum": 7, + "rpm.modulemd": 14, + "package_group": 2, + "rpm.packagecategory": 1, + "rpm.distribution_tree": 1, + "rpm.modulemd_defaults": 3 + }, + "metadata": { + "env_id": 4, + "library_instance_id": 1 + } + }, + "167": { + "counts": { + "file": 3 + }, + "metadata": { + "env_id": 4, + "library_instance_id": 16 + } + }, + "168": { + "counts": { + "python_package": 275 + }, + "metadata": { + "env_id": 4, + "library_instance_id": 40 + } + }, + "169": { + "counts": { + "deb": 6, + "deb.release": 2, + "deb.release_file": 2, + "deb.package_index": 5, + "deb.release_component": 2, + "deb.release_architecture": 5, + "deb.package_release_component": 6 + }, + "metadata": { + "env_id": 4, + "library_instance_id": 72 + } + } + } + }, + "30": { + "repositories": { + "131": { + "counts": { + "rpm": 14, + "erratum": 6, + "rpm.modulemd": 14, + "rpm.distribution_tree": 1, + "rpm.modulemd_defaults": 3 + }, + "metadata": { + "env_id": 1, + "library_instance_id": 1 + } + }, + "132": { + "counts": {}, + "metadata": { + "env_id": 1, + "library_instance_id": 40 + } + }, + "172": { + "counts": { + "rpm": 14, + "erratum": 6, + "rpm.modulemd": 14, + "rpm.distribution_tree": 1, + "rpm.modulemd_defaults": 3 + }, + "metadata": { + "env_id": 4, + "library_instance_id": 1 + } + }, + "173": { + "counts": {}, + "metadata": { + "env_id": 4, + "library_instance_id": 40 + } + } + } + } + } + } +} \ No newline at end of file diff --git a/webpack/scenes/SmartProxy/__tests__/SmartProxyContentTest.js b/webpack/scenes/SmartProxy/__tests__/SmartProxyContentTest.js index 58a7c4e97bb..61b3d9ed82c 100644 --- a/webpack/scenes/SmartProxy/__tests__/SmartProxyContentTest.js +++ b/webpack/scenes/SmartProxy/__tests__/SmartProxyContentTest.js @@ -1,11 +1,11 @@ import React from 'react'; -import { renderWithRedux, patientlyWaitFor } from 'react-testing-lib-wrapper'; +import { renderWithRedux, patientlyWaitFor, within } from 'react-testing-lib-wrapper'; import { nockInstance, assertNockRequest } from '../../../test-utils/nockWrapper'; import api from '../../../services/api'; import SmartProxyExpandableTable from '../SmartProxyExpandableTable'; -const smartProxyContentData = require('./SmartProxyContentResult.fixtures.json'); +const smartProxyContentData = require('./SmartProxyContentTest.fixtures.json'); const smartProxyContentPath = api.getApiUrl('/capsules/1/content/sync'); @@ -13,25 +13,38 @@ const smartProxyContent = { ...smartProxyContentData }; const contentTable = ; -test('Can display Smart proxy content table', async (done) => { +test('Can display Smart proxy content table and expand env and cv details', async (done) => { const detailsScope = nockInstance .get(smartProxyContentPath) .query(true) .reply(200, smartProxyContent); - const { getByText, getAllByText, getAllByLabelText } = renderWithRedux(contentTable); + const { + getByText, getAllByText, getByLabelText, + } = renderWithRedux(contentTable); await patientlyWaitFor(() => expect(getByText('Environment')).toBeInTheDocument()); - expect(getAllByText('Content view')[0]).toBeInTheDocument(); + const tdEnvExpand = getByLabelText('expand-env-1'); + const envExpansion = within(tdEnvExpand).getByLabelText('Details'); + envExpansion.click(); + await patientlyWaitFor(() => expect(getAllByText('Content view')[0]).toBeInTheDocument()); expect(getAllByText('Last published')[0]).toBeInTheDocument(); - expect(getAllByText('Repositories')[0]).toBeInTheDocument(); - expect(getAllByText('Synced to smart proxy')[0]).toBeInTheDocument(); - expect(getAllByLabelText('Details')[0]).toHaveAttribute('aria-expanded', 'false'); - getAllByLabelText('Details')[0].click(); - expect(getAllByLabelText('Details')[0]).toHaveAttribute('aria-expanded', 'true'); + expect(getAllByText('Repository')[0]).toBeInTheDocument(); + expect(getAllByText('Synced')[0]).toBeInTheDocument(); + const tdCvExpand = getByLabelText('expand-cv-1'); + const cvExpansion = within(tdCvExpand).getByLabelText('Details'); + expect(cvExpansion).toHaveAttribute('aria-expanded', 'false'); + cvExpansion.click(); + await patientlyWaitFor(() => expect(cvExpansion).toHaveAttribute('aria-expanded', 'true')); expect(getByText('Library')).toBeInTheDocument(); expect(getByText('Default Organization View')).toBeInTheDocument(); - expect(getByText('dev')).toBeInTheDocument(); - - + expect(getAllByText('dev')[0]).toBeInTheDocument(); + expect(getAllByText('Repository')[0]).toBeInTheDocument(); + expect(getAllByText('Packages')[0]).toBeInTheDocument(); + expect(getAllByText('Additional content')[0]).toBeInTheDocument(); + expect(getAllByText('repo1')[0]).toBeInTheDocument(); + expect(getAllByText('22 Packages')[0]).toBeInTheDocument(); + expect(getAllByText(/7 errata/i)[0]).toBeInTheDocument(); + expect(getAllByText(/14 Module streams/i)[0]).toBeInTheDocument(); + expect(getAllByText(/2 Package groups/i)[0]).toBeInTheDocument(); assertNockRequest(detailsScope, done); });