katello 4.9.0 → 4.9.1
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of katello might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/app/controllers/katello/api/v2/capsule_content_controller.rb +1 -1
- data/app/controllers/katello/api/v2/content_views_controller.rb +2 -1
- data/app/lib/actions/katello/content_view_version/auto_create_redhat_repositories.rb +2 -1
- data/app/lib/actions/katello/orphan_cleanup/remove_orphaned_content_units.rb +22 -0
- data/app/lib/actions/katello/orphan_cleanup/remove_orphans.rb +4 -15
- data/app/lib/actions/katello/repository_set/enable_repository.rb +3 -1
- data/app/models/katello/concerns/pulp_database_unit.rb +2 -2
- data/app/models/katello/content_view.rb +7 -0
- data/app/models/katello/repository.rb +0 -1
- data/app/models/katello/root_repository.rb +1 -1
- data/app/services/katello/pulp3/content_view_version/importable_repositories.rb +10 -6
- data/app/services/katello/pulp3/repository/docker.rb +1 -1
- data/app/services/katello/pulp3/repository.rb +3 -2
- data/lib/katello/tasks/upgrades/4.9/update_custom_products_enablement.rake +4 -2
- data/lib/katello/version.rb +1 -1
- data/webpack/components/extensions/HostDetails/Cards/ContentViewDetailsCard/ContentViewDetailsCard.js +2 -2
- data/webpack/components/extensions/HostDetails/Tabs/PackagesTab/PackagesTab.js +1 -1
- data/webpack/scenes/Content/Details/ContentRepositories.js +1 -1
- data/webpack/scenes/Content/Table/ContentTable.js +1 -1
- data/webpack/scenes/ContentViews/Details/ComponentContentViews/ComponentEnvironments.js +1 -1
- data/webpack/scenes/ContentViews/Details/ContentViewDetails.js +2 -1
- data/webpack/scenes/ContentViews/Details/Histories/ContentViewHistories.js +2 -2
- data/webpack/scenes/ContentViews/Details/Versions/Delete/affectedActivationKeys.js +1 -1
- data/webpack/scenes/ContentViews/Details/Versions/Delete/affectedHosts.js +1 -1
- data/webpack/scenes/ContentViews/Details/Versions/__tests__/contentViewVersions.test.js +7 -0
- data/webpack/scenes/ContentViews/Details/__tests__/contentViewDetails.fixtures.json +1 -0
- data/webpack/scenes/ContentViews/Publish/CVPublishReview.js +13 -2
- data/webpack/scenes/ContentViews/Publish/PublishContentViewWizard.js +4 -1
- data/webpack/scenes/ContentViews/Publish/__tests__/publishContentView.test.js +33 -10
- data/webpack/scenes/ContentViews/expansions/RelatedContentViewComponentsModal.js +1 -1
- data/webpack/scenes/Hosts/ChangeContentSource/components/ContentSourceForm.js +7 -2
- data/webpack/scenes/Subscriptions/Manifest/DeleteManifestModalText.js +38 -21
- data/webpack/scenes/Subscriptions/Manifest/ManageManifestModal.js +4 -1
- data/webpack/scenes/Subscriptions/SubscriptionsPage.js +1 -0
- data/webpack/scenes/Subscriptions/__tests__/__snapshots__/SubscriptionsPage.test.js.snap +1 -0
- metadata +7 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2725e254c69e3a52955ec2e85605097ad13751770b46de79a14bb6153451e5b7
|
4
|
+
data.tar.gz: a59ffe3ac6b743b36ac2207a44c333dc504975b54d23efb2d9d04aa13741c575
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 78951d090b4e05283c65ebb11d2c18475ffc2afa26cb2c1f981a425f18cd85bf9a82c84c06e33dc8c05348a1b9f8af845adb482fd0075116f75da85c847e5d31
|
7
|
+
data.tar.gz: 5c2acdd95ed253669357a68142f42c8b09ba5b34603bfa9acf1e029cdc32c9696d1edb40a8e616e032747c6771110ca54bd048b4108a342d755b1d000270766c
|
@@ -87,7 +87,7 @@ module Katello
|
|
87
87
|
end
|
88
88
|
end
|
89
89
|
|
90
|
-
api :POST, '/capsules/:id/reclaim_space', N_('Reclaim space from all On Demand repositories on a smart proxy')
|
90
|
+
api :POST, '/capsules/:id/content/reclaim_space', N_('Reclaim space from all On Demand repositories on a smart proxy')
|
91
91
|
param :id, :number, :required => true, :desc => N_('Id of the smart proxy')
|
92
92
|
def reclaim_space
|
93
93
|
find_capsule(true)
|
@@ -258,7 +258,8 @@ module Katello
|
|
258
258
|
fail HttpErrors::BadRequest, _("Both major and minor parameters have to be used to override a CV version")
|
259
259
|
end
|
260
260
|
|
261
|
-
|
261
|
+
cv_needs_publish = @content_view.needs_publish?
|
262
|
+
if (::Foreman::Cast.to_bool(params[:publish_only_if_needed]) && !cv_needs_publish.nil? && !cv_needs_publish)
|
262
263
|
fail HttpErrors::BadRequest, _("Content view does not need a publish since there are no audited changes since the last publish." \
|
263
264
|
" Pass check_needs_publish parameter as false if you don't want to check if content view needs a publish.")
|
264
265
|
end
|
@@ -15,7 +15,8 @@ module Actions
|
|
15
15
|
helper.creatable.each do |root|
|
16
16
|
plan_action(::Actions::Katello::RepositorySet::EnableRepository,
|
17
17
|
root[:product], root[:content], root[:substitutions],
|
18
|
-
override_url: root[:override_url]
|
18
|
+
override_url: root[:override_url],
|
19
|
+
override_arch: root[:override_arch])
|
19
20
|
end
|
20
21
|
helper.updatable.each do |root|
|
21
22
|
plan_action(::Actions::Katello::Repository::Update, root[:repository], root[:options])
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Actions
|
2
|
+
module Katello
|
3
|
+
module OrphanCleanup
|
4
|
+
class RemoveOrphanedContentUnits < Actions::Base
|
5
|
+
def run
|
6
|
+
models = []
|
7
|
+
|
8
|
+
::Katello::RepositoryTypeManager.enabled_repository_types.each_value do |repo_type|
|
9
|
+
models << repo_type.content_types_to_index
|
10
|
+
end
|
11
|
+
models.flatten.each do |content_type|
|
12
|
+
content_type.model_class.orphaned.destroy_all
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def rescue_strategy
|
17
|
+
Dynflow::Action::Rescue::Skip
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -7,27 +7,16 @@ module Actions
|
|
7
7
|
end
|
8
8
|
def plan(proxy)
|
9
9
|
sequence do
|
10
|
+
if proxy.pulp_primary?
|
11
|
+
::Katello::RootRepository.orphaned.destroy_all
|
12
|
+
plan_action(RemoveOrphanedContentUnits)
|
13
|
+
end
|
10
14
|
if proxy.pulp3_enabled?
|
11
15
|
plan_action(
|
12
16
|
Actions::Pulp3::Orchestration::OrphanCleanup::RemoveOrphans,
|
13
17
|
proxy)
|
14
18
|
end
|
15
|
-
plan_self
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
|
-
def run
|
20
|
-
models = []
|
21
|
-
::Katello::RepositoryTypeManager.enabled_repository_types.each_value do |repo_type|
|
22
|
-
indexable_types = repo_type.content_types_to_index
|
23
|
-
models += indexable_types&.map(&:model_class)
|
24
|
-
models.select! { |model| model.many_repository_associations }
|
25
|
-
end
|
26
|
-
models.each do |model|
|
27
|
-
model.joins("left join katello_#{model.repository_association} on #{model.table_name}.id = katello_#{model.repository_association}.#{model.unit_id_field}").where("katello_#{model.repository_association}.#{model.unit_id_field} IS NULL").destroy_all
|
28
19
|
end
|
29
|
-
|
30
|
-
::Katello::RootRepository.orphaned.destroy_all
|
31
20
|
end
|
32
21
|
end
|
33
22
|
end
|
@@ -6,7 +6,8 @@ module Actions
|
|
6
6
|
_("Enable")
|
7
7
|
end
|
8
8
|
|
9
|
-
def plan(product, content, substitutions, override_url: nil
|
9
|
+
def plan(product, content, substitutions, override_url: nil,
|
10
|
+
override_arch: nil)
|
10
11
|
mapper = ::Katello::Candlepin::RepositoryMapper.new(product,
|
11
12
|
content,
|
12
13
|
substitutions)
|
@@ -15,6 +16,7 @@ module Actions
|
|
15
16
|
fail ::Katello::Errors::ConflictException, _("The repository is already enabled")
|
16
17
|
end
|
17
18
|
repository = mapper.build_repository
|
19
|
+
repository.root.arch = override_arch if override_arch.present?
|
18
20
|
if override_url
|
19
21
|
repository.root.url = override_url
|
20
22
|
repository.root.download_policy = ::Katello::RootRepository::DOWNLOAD_IMMEDIATE if URI(override_url).scheme == 'file'
|
@@ -169,9 +169,9 @@ module Katello
|
|
169
169
|
|
170
170
|
def orphaned
|
171
171
|
if many_repository_associations
|
172
|
-
where.not(:id => repository_association_class.
|
172
|
+
where.not(:id => repository_association_class.select(unit_id_field))
|
173
173
|
else
|
174
|
-
where.not(:repository_id => ::Katello::Repository.
|
174
|
+
where.not(:repository_id => ::Katello::Repository.select(:id))
|
175
175
|
end
|
176
176
|
end
|
177
177
|
|
@@ -799,6 +799,7 @@ module Katello
|
|
799
799
|
# True:
|
800
800
|
# a) When content/repo/filter change audit records exist
|
801
801
|
# b) CV hasn't ever been published
|
802
|
+
# c) CV dependency_solving != latest_version.applied_filters.dependency_solving
|
802
803
|
# nil:
|
803
804
|
# a) When CV version creation audit is missing(Indicating audit cleanup)
|
804
805
|
# b) Version doesn't have audited_filters set indicating
|
@@ -815,6 +816,8 @@ module Katello
|
|
815
816
|
# return true if the audit records clearly show we have unpublished changes
|
816
817
|
return true if (audited_cv_repositories_since_last_publish.present? || audited_cv_repository_changed.present? ||
|
817
818
|
audited_cv_filters_changed.present? || audited_cv_filter_rules_changed.present?)
|
819
|
+
# return true if the dependency solving changed for CV between last publish and now
|
820
|
+
return true if dependency_solving_changed?
|
818
821
|
# if we didn't return `true` already, either the audit records show that we don't need to publish, or we may
|
819
822
|
# have insufficient data to make the determination (either audits were cleaned, or never got created at all).
|
820
823
|
# first, check for the `create` audit record; its absence indicates that audits were cleaned some time after
|
@@ -831,6 +834,10 @@ module Katello
|
|
831
834
|
latest_version_object.applied_filters.nil? ? nil : false
|
832
835
|
end
|
833
836
|
|
837
|
+
def dependency_solving_changed?
|
838
|
+
latest_version_object.applied_filters && solve_dependencies != latest_version_object.applied_filters['dependency_solving']
|
839
|
+
end
|
840
|
+
|
834
841
|
def filtered?
|
835
842
|
filters.present?
|
836
843
|
end
|
@@ -935,7 +935,6 @@ module Katello
|
|
935
935
|
repository_type.content_types_to_index.each do |type|
|
936
936
|
Katello::Logging.time("CONTENT_INDEX", data: {type: type.model_class}) do
|
937
937
|
Katello::ContentUnitIndexer.new(content_type: type, repository: self, optimized: !full_index).import_all
|
938
|
-
type.model_class.orphaned.destroy_all
|
939
938
|
end
|
940
939
|
end
|
941
940
|
repository_type.index_additional_data_proc&.call(self)
|
@@ -130,7 +130,7 @@ module Katello
|
|
130
130
|
where(:http_proxy_policy => RootRepository::USE_SELECTED_HTTP_PROXY).
|
131
131
|
where(:http_proxy_id => http_proxy_id)
|
132
132
|
}
|
133
|
-
scope :orphaned, -> { where.not(id: Katello::Repository.
|
133
|
+
scope :orphaned, -> { where.not(id: Katello::Repository.select(:root_id)) }
|
134
134
|
scope :redhat, -> { joins(:provider).merge(Katello::Provider.redhat) }
|
135
135
|
scope :custom, -> { where.not(:id => self.redhat) }
|
136
136
|
delegate :redhat?, :provider, :organization, to: :product
|
@@ -29,10 +29,12 @@ module Katello
|
|
29
29
|
|
30
30
|
root = product.root_repositories.find do |r|
|
31
31
|
if repo.content&.id && repo.redhat
|
32
|
-
r.content.cp_content_id == repo.content.id &&
|
32
|
+
repo_exists = r.content.cp_content_id == repo.content.id &&
|
33
33
|
r.arch == repo.arch &&
|
34
34
|
r.major == repo.major &&
|
35
35
|
r.minor == repo.minor
|
36
|
+
|
37
|
+
repo_exists || r.label == repo.label
|
36
38
|
else
|
37
39
|
r.label == repo.label
|
38
40
|
end
|
@@ -46,11 +48,13 @@ module Katello
|
|
46
48
|
basearch: repo.arch,
|
47
49
|
releasever: repo.minor
|
48
50
|
}
|
49
|
-
creatable << {
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
51
|
+
creatable << {
|
52
|
+
product: product,
|
53
|
+
content: product_content,
|
54
|
+
substitutions: substitutions,
|
55
|
+
override_url: fetch_feed_url(repo),
|
56
|
+
override_arch: repo.arch
|
57
|
+
}
|
54
58
|
else
|
55
59
|
creatable << { repository: product.add_repo(create_repo_params(repo, product)) }
|
56
60
|
end
|
@@ -70,7 +70,7 @@ module Katello
|
|
70
70
|
rescue api.client_module::ApiError => e
|
71
71
|
if e.message.include? 'Could not find the following content units'
|
72
72
|
raise ::Katello::Errors::Pulp3Error, "Content units that do not exist in Pulp were requested to be copied."\
|
73
|
-
" Please run
|
73
|
+
" Please run `foreman-rake katello:delete_orphaned_content` to fix the following repository: #{repository_reference.root_repository.name}. Original error: #{e.message}"
|
74
74
|
else
|
75
75
|
raise e
|
76
76
|
end
|
@@ -497,7 +497,8 @@ module Katello
|
|
497
497
|
rescue api.client_module::ApiError => e
|
498
498
|
if e.message.include? 'Could not find the following content units'
|
499
499
|
raise ::Katello::Errors::Pulp3Error, "Content units that do not exist in Pulp were requested to be copied."\
|
500
|
-
|
500
|
+
" Please run `foreman-rake katello:delete_orphaned_content` to fix the following repository:"\
|
501
|
+
" #{repository_reference.root_repository.name}. Original error: #{e.message}"
|
501
502
|
else
|
502
503
|
raise e
|
503
504
|
end
|
@@ -509,7 +510,7 @@ module Katello
|
|
509
510
|
rescue api.client_module::ApiError => e
|
510
511
|
if e.message.include? 'Could not find the following content units'
|
511
512
|
raise ::Katello::Errors::Pulp3Error, "Content units that do not exist in Pulp were requested to be copied."\
|
512
|
-
" Please run
|
513
|
+
" Please run `foreman-rake katello:delete_orphaned_content` to fix the following repository:"\
|
513
514
|
" #{::Katello::Pulp3::RepositoryReference.find_by(repository_href: repository_href).root_repository.name}. Original error: #{e.message}"
|
514
515
|
else
|
515
516
|
raise e
|
@@ -3,8 +3,10 @@ namespace :katello do
|
|
3
3
|
namespace '4.9' do
|
4
4
|
desc "Update custom products enablement"
|
5
5
|
task :update_custom_products_enablement => ['environment'] do
|
6
|
-
|
7
|
-
|
6
|
+
if ::Katello::ProductContent.custom.where(enabled: true).exists?
|
7
|
+
migrator = Katello::Util::DefaultEnablementMigrator.new
|
8
|
+
migrator.execute!
|
9
|
+
end
|
8
10
|
end
|
9
11
|
end
|
10
12
|
end
|
data/lib/katello/version.rb
CHANGED
@@ -106,7 +106,7 @@ const HostContentViewDetails = ({
|
|
106
106
|
<h3>{__('Content view')}</h3>
|
107
107
|
</Flex>
|
108
108
|
<Flex direction={{ default: 'row', sm: 'row' }} flexWrap={{ default: 'wrap' }}>
|
109
|
-
<a style={{ fontSize: '14px' }} href={`/content_views/${contentView.id}`}>{
|
109
|
+
<a style={{ fontSize: '14px' }} href={`/content_views/${contentView.id}`}>{contentView.name}</a>
|
110
110
|
<Tooltip
|
111
111
|
position="top"
|
112
112
|
enableFlip
|
@@ -119,7 +119,7 @@ const HostContentViewDetails = ({
|
|
119
119
|
}}
|
120
120
|
/>}
|
121
121
|
>
|
122
|
-
<Label isTruncated color="purple" href={`/lifecycle_environments/${lifecycleEnvironment.id}`}>{
|
122
|
+
<Label isTruncated color="purple" href={`/lifecycle_environments/${lifecycleEnvironment.id}`}>{lifecycleEnvironment.name}</Label>
|
123
123
|
</Tooltip>
|
124
124
|
</Flex>
|
125
125
|
</Flex>
|
@@ -59,7 +59,7 @@ const ContentRepositories = ({ contentType, id, tabKey }) => {
|
|
59
59
|
</Thead>
|
60
60
|
<Tbody>
|
61
61
|
{results?.map((details, idx) => (
|
62
|
-
<Tr key={
|
62
|
+
<Tr key={details.id} ouiaId={`content-repositories-row-${idx}`}>
|
63
63
|
{columnHeaders.map((col, index) =>
|
64
64
|
<Td key={index}>{col.getProperty(details, typeSingularLabel)}</Td>)
|
65
65
|
}
|
@@ -65,7 +65,7 @@ const ContentTable = ({
|
|
65
65
|
</Thead>
|
66
66
|
<Tbody>
|
67
67
|
{results?.map(details => (
|
68
|
-
<Tr key={
|
68
|
+
<Tr key={details.id} ouiaId={`${details.id}-tr`}>
|
69
69
|
{columnHeaders.map((col, index) =>
|
70
70
|
<Td key={index}>{col.getProperty(details)}</Td>)
|
71
71
|
}
|
@@ -37,7 +37,7 @@ import { hasPermission } from '../helpers';
|
|
37
37
|
import CopyContentViewModal from '../Copy/CopyContentViewModal';
|
38
38
|
import ContentViewDeleteWizard from '../Delete/ContentViewDeleteWizard';
|
39
39
|
import EmptyStateMessage from '../../../components/Table/EmptyStateMessage';
|
40
|
-
import { cvVersionTaskPollingKey } from '../ContentViewsConstants';
|
40
|
+
import { CONTENT_VIEW_NEEDS_PUBLISH_RESET, cvVersionTaskPollingKey } from '../ContentViewsConstants';
|
41
41
|
import { clearPollTaskData, stopPollingTask } from '../../Tasks/TaskActions';
|
42
42
|
|
43
43
|
export default () => {
|
@@ -73,6 +73,7 @@ export default () => {
|
|
73
73
|
|
74
74
|
useEffect(() => {
|
75
75
|
dispatch(getContentViewDetails(cvId));
|
76
|
+
dispatch({ type: CONTENT_VIEW_NEEDS_PUBLISH_RESET });
|
76
77
|
}, [cvId, dispatch]);
|
77
78
|
|
78
79
|
|
@@ -49,9 +49,9 @@ const ContentViewHistories = ({ cvId }) => {
|
|
49
49
|
const taskType = task ? task.label : taskTypes[action];
|
50
50
|
|
51
51
|
if (taskType === taskTypes.removal) {
|
52
|
-
return <>{__('Deleted from ')} <Label isTruncated key="1" color="blue" href={`/lifecycle_environments/${environment?.id}`}>{
|
52
|
+
return <>{__('Deleted from ')} <Label isTruncated key="1" color="blue" href={`/lifecycle_environments/${environment?.id}`}>{environment?.name ?? __('all environments')}</Label></>;
|
53
53
|
} else if (action === 'promotion' || taskType === taskTypes.promotion) {
|
54
|
-
return <>{__('Promoted to ')}<Label isTruncated key="2" color="blue" href={`/lifecycle_environments/${environment?.id}`}>{
|
54
|
+
return <>{__('Promoted to ')}<Label isTruncated key="2" color="blue" href={`/lifecycle_environments/${environment?.id}`}>{environment?.name}</Label></>;
|
55
55
|
} else if (taskType === taskTypes.publish) {
|
56
56
|
return __('Published new version');
|
57
57
|
} else if (taskType === taskTypes.export) {
|
@@ -66,7 +66,7 @@ const AffectedActivationKeys = ({
|
|
66
66
|
</Thead>
|
67
67
|
<Tbody>
|
68
68
|
{results?.map(({ name, id, environment }) => (
|
69
|
-
<Tr ouiaId={
|
69
|
+
<Tr ouiaId={id} key={id}>
|
70
70
|
<Td>
|
71
71
|
<a rel="noreferrer" target="_blank" href={urlBuilder(`activation_keys/${id}`, '')}>{name}</a>
|
72
72
|
</Td>
|
@@ -65,7 +65,7 @@ const AffectedHosts = ({
|
|
65
65
|
id,
|
66
66
|
content_facet_attributes: { lifecycle_environment: environment },
|
67
67
|
}) => (
|
68
|
-
<Tr ouiaId={
|
68
|
+
<Tr ouiaId={id} key={id}>
|
69
69
|
<Td>
|
70
70
|
<a rel="noreferrer" target="_blank" href={urlBuilder(`new/hosts/${id}`, '')}>{name}</a>
|
71
71
|
</Td>
|
@@ -13,6 +13,7 @@ import contentViewTaskResponseData from './contentViewTaskResponse.fixtures.json
|
|
13
13
|
import cvDetailData from '../../../../ContentViews/__tests__/mockDetails.fixtures.json';
|
14
14
|
import environmentPathsData from '../../../Publish/__tests__/environmentPaths.fixtures.json';
|
15
15
|
import cvIndexData from '../../../__tests__/contentViewList.fixtures.json';
|
16
|
+
import contentViewFilterData from '../../Filters/__tests__/contentViewFilters.fixtures.json';
|
16
17
|
|
17
18
|
const cvPromotePath = api.getApiUrl('/content_view_versions/10/promote');
|
18
19
|
const cvIndexPath = api.getApiUrl('/content_views');
|
@@ -34,6 +35,7 @@ const renderOptions = {
|
|
34
35
|
const cvVersions = api.getApiUrl('/content_view_versions');
|
35
36
|
const autocompleteUrl = '/content_view_versions/auto_complete_search';
|
36
37
|
const taskPollingUrl = '/foreman_tasks/api/tasks/6b900ff8-62bb-42ac-8c45-da86b7258520';
|
38
|
+
const cvFiltersPath = api.getApiUrl('/content_view_filters?content_view_id=5');
|
37
39
|
|
38
40
|
let firstVersion;
|
39
41
|
let envScope;
|
@@ -330,6 +332,10 @@ test('Shows call-to-action when there are no versions', async (done) => {
|
|
330
332
|
.query(true)
|
331
333
|
.reply(200, environmentPathsData);
|
332
334
|
|
335
|
+
const filterScope = nockInstance
|
336
|
+
.get(cvFiltersPath)
|
337
|
+
.reply(200, contentViewFilterData);
|
338
|
+
|
333
339
|
const { getByText, queryByText } = renderWithRedux(
|
334
340
|
withCVRoute(<ContentViewVersions cvId={5} details={cvDetailData} />),
|
335
341
|
renderOptions,
|
@@ -347,6 +353,7 @@ test('Shows call-to-action when there are no versions', async (done) => {
|
|
347
353
|
assertNockRequest(scopeWizard);
|
348
354
|
assertNockRequest(autocompleteScope);
|
349
355
|
assertNockRequest(scope);
|
356
|
+
assertNockRequest(filterScope);
|
350
357
|
act(done);
|
351
358
|
});
|
352
359
|
|
@@ -15,16 +15,20 @@ import InactiveText from '../components/InactiveText';
|
|
15
15
|
import ComponentEnvironments from '../Details/ComponentContentViews/ComponentEnvironments';
|
16
16
|
import { selectEnvironmentPaths, selectEnvironmentPathsStatus } from '../components/EnvironmentPaths/EnvironmentPathSelectors';
|
17
17
|
import WizardHeader from '../components/WizardHeader';
|
18
|
+
import { selectCVFilters, selectCVFiltersStatus } from '../Details/ContentViewDetailSelectors';
|
18
19
|
|
19
20
|
const CVPublishReview = ({
|
20
21
|
details: {
|
21
|
-
id, name, composite,
|
22
|
+
id, name, composite, next_version: nextVersion,
|
22
23
|
},
|
23
24
|
userCheckedItems,
|
24
25
|
}) => {
|
25
26
|
const environmentPathResponse = useSelector(selectEnvironmentPaths);
|
26
27
|
const environmentPathStatus = useSelector(selectEnvironmentPathsStatus);
|
28
|
+
const cvFiltersResponse = useSelector(state => selectCVFilters(state, id));
|
29
|
+
const cvFiltersStatus = useSelector(state => selectCVFiltersStatus(state, id));
|
27
30
|
const environmentPathLoading = environmentPathStatus === STATUS.PENDING;
|
31
|
+
const cvFiltersLoading = cvFiltersStatus === STATUS.PENDING;
|
28
32
|
|
29
33
|
const promotedToEnvironments = useMemo(() => {
|
30
34
|
if (!environmentPathLoading) {
|
@@ -35,6 +39,14 @@ const CVPublishReview = ({
|
|
35
39
|
return [];
|
36
40
|
}, [environmentPathResponse, environmentPathLoading, userCheckedItems]);
|
37
41
|
|
42
|
+
const filtered = useMemo(() => {
|
43
|
+
if (!cvFiltersLoading) {
|
44
|
+
const { results } = cvFiltersResponse || {};
|
45
|
+
return results.length > 0;
|
46
|
+
}
|
47
|
+
return [];
|
48
|
+
}, [cvFiltersResponse, cvFiltersLoading]);
|
49
|
+
|
38
50
|
return (
|
39
51
|
<>
|
40
52
|
<WizardHeader
|
@@ -94,7 +106,6 @@ CVPublishReview.propTypes = {
|
|
94
106
|
]).isRequired,
|
95
107
|
name: PropTypes.string.isRequired,
|
96
108
|
composite: PropTypes.bool.isRequired,
|
97
|
-
filtered: PropTypes.bool.isRequired,
|
98
109
|
next_version: PropTypes.oneOfType([
|
99
110
|
PropTypes.number,
|
100
111
|
PropTypes.string,
|
@@ -14,6 +14,7 @@ import {
|
|
14
14
|
} from '../components/EnvironmentPaths/EnvironmentPathSelectors';
|
15
15
|
import { stopPollingTask } from '../../Tasks/TaskActions';
|
16
16
|
import { cvVersionTaskPollingKey } from '../ContentViewsConstants';
|
17
|
+
import { getContentViewFilters } from '../Details/ContentViewDetailActions';
|
17
18
|
|
18
19
|
const PublishContentViewWizard = ({
|
19
20
|
details, show, onClose,
|
@@ -30,6 +31,7 @@ const PublishContentViewWizard = ({
|
|
30
31
|
const environmentPathStatus = useSelector(selectEnvironmentPathsStatus);
|
31
32
|
const environmentPathLoading = environmentPathStatus === STATUS.PENDING;
|
32
33
|
|
34
|
+
|
33
35
|
const steps = [
|
34
36
|
{
|
35
37
|
id: 1,
|
@@ -71,8 +73,9 @@ const PublishContentViewWizard = ({
|
|
71
73
|
useEffect(
|
72
74
|
() => {
|
73
75
|
dispatch(getEnvironmentPaths());
|
76
|
+
dispatch(getContentViewFilters(cvId, {}));
|
74
77
|
},
|
75
|
-
[dispatch],
|
78
|
+
[dispatch, cvId],
|
76
79
|
);
|
77
80
|
|
78
81
|
const envPathFlat = useMemo(() => {
|
@@ -7,18 +7,21 @@ import PublishContentViewWizard from '../PublishContentViewWizard';
|
|
7
7
|
import cvDetailData from '../../Details/__tests__/contentViewDetails.fixtures.json';
|
8
8
|
import publishResponseData from './publishResponse.fixture.json';
|
9
9
|
import environmentPathsData from './environmentPaths.fixtures.json';
|
10
|
+
import contentViewFilterData from './../../Details/Filters/__tests__/contentViewFilters.fixtures.json';
|
10
11
|
|
11
12
|
const cvPublishPath = api.getApiUrl('/content_views/1/publish');
|
12
13
|
|
13
14
|
const environmentPathsPath = api.getApiUrl('/organizations/1/environments/paths');
|
15
|
+
const cvFiltersPath = api.getApiUrl('/content_view_filters?content_view_id=1');
|
14
16
|
|
15
17
|
test('Can call API and show Wizard', async (done) => {
|
16
18
|
const scope = nockInstance
|
17
19
|
.get(environmentPathsPath)
|
18
20
|
.query(true)
|
19
21
|
.reply(200, environmentPathsData);
|
20
|
-
const
|
21
|
-
|
22
|
+
const filterScope = nockInstance
|
23
|
+
.get(cvFiltersPath)
|
24
|
+
.reply(200, contentViewFilterData);
|
22
25
|
|
23
26
|
const { getByText } = renderWithRedux(<PublishContentViewWizard
|
24
27
|
details={cvDetailData}
|
@@ -26,9 +29,13 @@ test('Can call API and show Wizard', async (done) => {
|
|
26
29
|
onClose={() => { }}
|
27
30
|
/>);
|
28
31
|
|
29
|
-
await patientlyWaitFor(() =>
|
30
|
-
|
31
|
-
|
32
|
+
await patientlyWaitFor(() => {
|
33
|
+
expect(getByText('Publish new version - 6.0')).toBeInTheDocument();
|
34
|
+
expect(getByText('Newly published version will be the same as the previous version.')).toBeTruthy();
|
35
|
+
});
|
36
|
+
|
37
|
+
assertNockRequest(scope);
|
38
|
+
assertNockRequest(filterScope, done);
|
32
39
|
});
|
33
40
|
|
34
41
|
test('Can show Wizard and show environment paths', async (done) => {
|
@@ -36,6 +43,9 @@ test('Can show Wizard and show environment paths', async (done) => {
|
|
36
43
|
.get(environmentPathsPath)
|
37
44
|
.query(true)
|
38
45
|
.reply(200, environmentPathsData);
|
46
|
+
const filterScope = nockInstance
|
47
|
+
.get(cvFiltersPath)
|
48
|
+
.reply(200, contentViewFilterData);
|
39
49
|
const useSelectorMock = jest.spyOn(reactRedux, 'useSelector');
|
40
50
|
useSelectorMock.mockReturnValue(environmentPathsData);
|
41
51
|
|
@@ -55,7 +65,8 @@ test('Can show Wizard and show environment paths', async (done) => {
|
|
55
65
|
expect(getByText('dev1')).toBeTruthy();
|
56
66
|
});
|
57
67
|
useSelectorMock.mockClear();
|
58
|
-
assertNockRequest(scope
|
68
|
+
assertNockRequest(scope);
|
69
|
+
assertNockRequest(filterScope, done);
|
59
70
|
});
|
60
71
|
|
61
72
|
test('Can show and hide force promotion alert', async (done) => {
|
@@ -63,6 +74,9 @@ test('Can show and hide force promotion alert', async (done) => {
|
|
63
74
|
.get(environmentPathsPath)
|
64
75
|
.query(true)
|
65
76
|
.reply(200, environmentPathsData);
|
77
|
+
const filterScope = nockInstance
|
78
|
+
.get(cvFiltersPath)
|
79
|
+
.reply(200, contentViewFilterData);
|
66
80
|
const useSelectorMock = jest.spyOn(reactRedux, 'useSelector');
|
67
81
|
useSelectorMock.mockReturnValue(environmentPathsData);
|
68
82
|
|
@@ -109,15 +123,18 @@ test('Can show and hide force promotion alert', async (done) => {
|
|
109
123
|
expect(queryByText('Force promotion')).not.toBeInTheDocument();
|
110
124
|
|
111
125
|
useSelectorMock.mockClear();
|
112
|
-
assertNockRequest(scope
|
126
|
+
assertNockRequest(scope);
|
127
|
+
assertNockRequest(filterScope, done);
|
113
128
|
});
|
114
129
|
|
115
|
-
|
116
130
|
test('Can show Wizard form and move to review', async (done) => {
|
117
131
|
const scope = nockInstance
|
118
132
|
.get(environmentPathsPath)
|
119
133
|
.query(true)
|
120
134
|
.reply(200, environmentPathsData);
|
135
|
+
const filterScope = nockInstance
|
136
|
+
.get(cvFiltersPath)
|
137
|
+
.reply(200, contentViewFilterData);
|
121
138
|
|
122
139
|
const { getByText } = renderWithRedux(<PublishContentViewWizard
|
123
140
|
details={cvDetailData}
|
@@ -134,9 +151,12 @@ test('Can show Wizard form and move to review', async (done) => {
|
|
134
151
|
expect(getByText('Newly published')).toBeInTheDocument();
|
135
152
|
expect(getByText('Version 6.0')).toBeInTheDocument();
|
136
153
|
expect(getByText('Library')).toBeTruthy();
|
154
|
+
expect(getByText('Filters')).toBeTruthy();
|
155
|
+
expect(getByText('Filters will be applied to this content view version.')).toBeTruthy();
|
137
156
|
});
|
138
157
|
useSelectorMock.mockClear();
|
139
|
-
assertNockRequest(scope
|
158
|
+
assertNockRequest(scope);
|
159
|
+
assertNockRequest(filterScope, done);
|
140
160
|
});
|
141
161
|
|
142
162
|
test('Can move to Finish step and publish CV', async (done) => {
|
@@ -144,7 +164,9 @@ test('Can move to Finish step and publish CV', async (done) => {
|
|
144
164
|
.get(environmentPathsPath)
|
145
165
|
.query(true)
|
146
166
|
.reply(200, environmentPathsData);
|
147
|
-
|
167
|
+
const filterScope = nockInstance
|
168
|
+
.get(cvFiltersPath)
|
169
|
+
.reply(200, contentViewFilterData);
|
148
170
|
const cvPublishParams = {
|
149
171
|
id: 1, versionCount: 5, description: '', environment_ids: [], is_force_promote: false,
|
150
172
|
};
|
@@ -166,6 +188,7 @@ test('Can move to Finish step and publish CV', async (done) => {
|
|
166
188
|
fireEvent.click(getByText('Finish'));
|
167
189
|
|
168
190
|
assertNockRequest(scope);
|
191
|
+
assertNockRequest(filterScope);
|
169
192
|
assertNockRequest(publishScope, done);
|
170
193
|
act(done); // stop listening for nocks
|
171
194
|
});
|
@@ -83,7 +83,7 @@ const RelatedContentViewsModal = ({ cvName, cvId, relatedCVCount }) => {
|
|
83
83
|
</Thead>
|
84
84
|
<Tbody>
|
85
85
|
{results?.map(details => (
|
86
|
-
<Tr key={
|
86
|
+
<Tr key={details.content_view.id} ouiaId={details.content_view.id}>
|
87
87
|
<Td>
|
88
88
|
<Link to={urlBuilder(`content_views/${details.content_view.id}`, '')}>{details.content_view.name}</Link>
|
89
89
|
</Td>
|
@@ -54,7 +54,7 @@ const ContentSourceSelect = ({
|
|
54
54
|
{contentSources.map(cs => (
|
55
55
|
<SelectOption
|
56
56
|
key={cs.id}
|
57
|
-
value={
|
57
|
+
value={cs.id}
|
58
58
|
>
|
59
59
|
{cs.name}
|
60
60
|
</SelectOption>
|
@@ -218,7 +218,12 @@ const ContentSourceForm = ({
|
|
218
218
|
className="set-select-width"
|
219
219
|
placeholderText={cvPlaceholderText}
|
220
220
|
>
|
221
|
-
{!environmentIsDisabled && contentViews?.map(cv => <ContentViewSelectOption
|
221
|
+
{!environmentIsDisabled && contentViews?.map(cv => (<ContentViewSelectOption
|
222
|
+
key={cv.id}
|
223
|
+
value={cv.name}
|
224
|
+
cv={cv}
|
225
|
+
env={environments[0]}
|
226
|
+
/>))}
|
222
227
|
</ContentViewSelect>
|
223
228
|
<ActionGroup style={{ display: 'block' }}>
|
224
229
|
<Button
|
@@ -1,29 +1,46 @@
|
|
1
1
|
import React from 'react';
|
2
|
+
import PropTypes from 'prop-types';
|
2
3
|
import { translate as __ } from 'foremanReact/common/I18n';
|
3
4
|
|
4
|
-
const
|
5
|
-
const note = __(`Note: Deleting a subscription manifest is STRONGLY discouraged.
|
6
|
-
Deleting a manifest will:`);
|
7
|
-
const l1 = __('Delete all subscriptions that are attached to running hosts.');
|
8
|
-
const l2 = __('Delete all subscriptions attached to activation keys.');
|
9
|
-
const l3 = __('Disable Red Hat Insights.');
|
10
|
-
const l4 = __(`Require you to upload the subscription-manifest and re-attach
|
11
|
-
subscriptions to hosts and activation keys.`);
|
12
|
-
const debug = __(`This action should only be taken in extreme circumstances or
|
13
|
-
for debugging purposes.`);
|
14
|
-
|
15
|
-
const DeleteManifestModalText = () => (
|
5
|
+
const DeleteManifestModalText = ({ simpleContentAccess }) => (
|
16
6
|
<React.Fragment>
|
17
|
-
<p>{
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
7
|
+
<p>{__('Are you sure you want to delete the manifest?')}</p>
|
8
|
+
{simpleContentAccess && (
|
9
|
+
<>
|
10
|
+
<p>{__('Note: Deleting a subscription manifest is STRONGLY discouraged.')}</p>
|
11
|
+
<p>{__('This action should only be taken for debugging purposes.')}</p>
|
12
|
+
</>
|
13
|
+
)}
|
14
|
+
{!simpleContentAccess && (
|
15
|
+
<>
|
16
|
+
<p>
|
17
|
+
{__(`Note: Deleting a subscription manifest is STRONGLY discouraged.
|
18
|
+
Deleting a manifest will:`)}
|
19
|
+
</p>
|
20
|
+
<ul className="list-aligned">
|
21
|
+
<li>{__('Delete all subscriptions that are attached to running hosts.')}</li>
|
22
|
+
<li>{__('Delete all subscriptions attached to activation keys.')}</li>
|
23
|
+
<li>{__('Disable Red Hat Insights.')}</li>
|
24
|
+
<li>
|
25
|
+
{__(`Require you to upload the subscription-manifest and re-attach
|
26
|
+
subscriptions to hosts and activation keys.`)}
|
27
|
+
</li>
|
28
|
+
</ul>
|
29
|
+
<p>
|
30
|
+
{__(`This action should only be taken in extreme circumstances or
|
31
|
+
for debugging purposes.`)}
|
32
|
+
</p>
|
33
|
+
</>
|
34
|
+
)}
|
26
35
|
</React.Fragment>
|
27
36
|
);
|
28
37
|
|
38
|
+
DeleteManifestModalText.propTypes = {
|
39
|
+
simpleContentAccess: PropTypes.bool,
|
40
|
+
};
|
41
|
+
|
42
|
+
DeleteManifestModalText.defaultProps = {
|
43
|
+
simpleContentAccess: false,
|
44
|
+
};
|
45
|
+
|
29
46
|
export default DeleteManifestModalText;
|
@@ -84,6 +84,7 @@ class ManageManifestModal extends Component {
|
|
84
84
|
disabledReason,
|
85
85
|
canImportManifest,
|
86
86
|
canDeleteManifest,
|
87
|
+
simpleContentAccess,
|
87
88
|
isManifestImported,
|
88
89
|
canEditOrganizations,
|
89
90
|
taskInProgress,
|
@@ -196,7 +197,7 @@ class ManageManifestModal extends Component {
|
|
196
197
|
}
|
197
198
|
</div>
|
198
199
|
<ForemanModal title={__('Confirm delete manifest')} id={DELETE_MANIFEST_MODAL_ID}>
|
199
|
-
<DeleteManifestModalText />
|
200
|
+
<DeleteManifestModalText simpleContentAccess={simpleContentAccess} />
|
200
201
|
<ForemanModal.Footer>
|
201
202
|
<Button ouiaId="cancel-button" bsStyle="default" onClick={this.hideDeleteManifestModal}>
|
202
203
|
{__('Cancel')}
|
@@ -282,6 +283,7 @@ ManageManifestModal.propTypes = {
|
|
282
283
|
}).isRequired,
|
283
284
|
canImportManifest: PropTypes.bool,
|
284
285
|
canDeleteManifest: PropTypes.bool,
|
286
|
+
simpleContentAccess: PropTypes.bool,
|
285
287
|
isManifestImported: PropTypes.bool,
|
286
288
|
deleteManifestModalExists: PropTypes.bool,
|
287
289
|
canEditOrganizations: PropTypes.bool,
|
@@ -309,6 +311,7 @@ ManageManifestModal.defaultProps = {
|
|
309
311
|
disabledReason: '',
|
310
312
|
canImportManifest: false,
|
311
313
|
canDeleteManifest: false,
|
314
|
+
simpleContentAccess: true,
|
312
315
|
isManifestImported: false,
|
313
316
|
deleteManifestModalExists: false,
|
314
317
|
canEditOrganizations: false,
|
@@ -264,6 +264,7 @@ class SubscriptionsPage extends Component {
|
|
264
264
|
canImportManifest={canImportManifest}
|
265
265
|
canDeleteManifest={canDeleteManifest}
|
266
266
|
canEditOrganizations={canEditOrganizations}
|
267
|
+
simpleContentAccess={simpleContentAccess}
|
267
268
|
taskInProgress={!!task}
|
268
269
|
disableManifestActions={disableManifestActions}
|
269
270
|
disabledReason={this.getDisabledReason()}
|
@@ -64,6 +64,7 @@ exports[`subscriptions page should render 1`] = `
|
|
64
64
|
disableManifestActions={true}
|
65
65
|
disabledReason="This is disabled because no connection could be made to the upstream Manifest."
|
66
66
|
refresh={[Function]}
|
67
|
+
simpleContentAccess={false}
|
67
68
|
taskInProgress={false}
|
68
69
|
upload={[Function]}
|
69
70
|
/>
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: katello
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 4.9.
|
4
|
+
version: 4.9.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- N/A
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-07-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -406,16 +406,16 @@ dependencies:
|
|
406
406
|
name: pulp_ostree_client
|
407
407
|
requirement: !ruby/object:Gem::Requirement
|
408
408
|
requirements:
|
409
|
-
- - "
|
409
|
+
- - "<"
|
410
410
|
- !ruby/object:Gem::Version
|
411
|
-
version:
|
411
|
+
version: 2.1.1
|
412
412
|
type: :runtime
|
413
413
|
prerelease: false
|
414
414
|
version_requirements: !ruby/object:Gem::Requirement
|
415
415
|
requirements:
|
416
|
-
- - "
|
416
|
+
- - "<"
|
417
417
|
- !ruby/object:Gem::Version
|
418
|
-
version:
|
418
|
+
version: 2.1.1
|
419
419
|
- !ruby/object:Gem::Dependency
|
420
420
|
name: deface
|
421
421
|
requirement: !ruby/object:Gem::Requirement
|
@@ -948,6 +948,7 @@ files:
|
|
948
948
|
- app/lib/actions/katello/organization/simple_content_access/disable.rb
|
949
949
|
- app/lib/actions/katello/organization/simple_content_access/enable.rb
|
950
950
|
- app/lib/actions/katello/organization/simple_content_access/toggle.rb
|
951
|
+
- app/lib/actions/katello/orphan_cleanup/remove_orphaned_content_units.rb
|
951
952
|
- app/lib/actions/katello/orphan_cleanup/remove_orphans.rb
|
952
953
|
- app/lib/actions/katello/product/content_create.rb
|
953
954
|
- app/lib/actions/katello/product/content_destroy.rb
|