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.

Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/katello/api/v2/capsule_content_controller.rb +1 -1
  3. data/app/controllers/katello/api/v2/content_views_controller.rb +2 -1
  4. data/app/lib/actions/katello/content_view_version/auto_create_redhat_repositories.rb +2 -1
  5. data/app/lib/actions/katello/orphan_cleanup/remove_orphaned_content_units.rb +22 -0
  6. data/app/lib/actions/katello/orphan_cleanup/remove_orphans.rb +4 -15
  7. data/app/lib/actions/katello/repository_set/enable_repository.rb +3 -1
  8. data/app/models/katello/concerns/pulp_database_unit.rb +2 -2
  9. data/app/models/katello/content_view.rb +7 -0
  10. data/app/models/katello/repository.rb +0 -1
  11. data/app/models/katello/root_repository.rb +1 -1
  12. data/app/services/katello/pulp3/content_view_version/importable_repositories.rb +10 -6
  13. data/app/services/katello/pulp3/repository/docker.rb +1 -1
  14. data/app/services/katello/pulp3/repository.rb +3 -2
  15. data/lib/katello/tasks/upgrades/4.9/update_custom_products_enablement.rake +4 -2
  16. data/lib/katello/version.rb +1 -1
  17. data/webpack/components/extensions/HostDetails/Cards/ContentViewDetailsCard/ContentViewDetailsCard.js +2 -2
  18. data/webpack/components/extensions/HostDetails/Tabs/PackagesTab/PackagesTab.js +1 -1
  19. data/webpack/scenes/Content/Details/ContentRepositories.js +1 -1
  20. data/webpack/scenes/Content/Table/ContentTable.js +1 -1
  21. data/webpack/scenes/ContentViews/Details/ComponentContentViews/ComponentEnvironments.js +1 -1
  22. data/webpack/scenes/ContentViews/Details/ContentViewDetails.js +2 -1
  23. data/webpack/scenes/ContentViews/Details/Histories/ContentViewHistories.js +2 -2
  24. data/webpack/scenes/ContentViews/Details/Versions/Delete/affectedActivationKeys.js +1 -1
  25. data/webpack/scenes/ContentViews/Details/Versions/Delete/affectedHosts.js +1 -1
  26. data/webpack/scenes/ContentViews/Details/Versions/__tests__/contentViewVersions.test.js +7 -0
  27. data/webpack/scenes/ContentViews/Details/__tests__/contentViewDetails.fixtures.json +1 -0
  28. data/webpack/scenes/ContentViews/Publish/CVPublishReview.js +13 -2
  29. data/webpack/scenes/ContentViews/Publish/PublishContentViewWizard.js +4 -1
  30. data/webpack/scenes/ContentViews/Publish/__tests__/publishContentView.test.js +33 -10
  31. data/webpack/scenes/ContentViews/expansions/RelatedContentViewComponentsModal.js +1 -1
  32. data/webpack/scenes/Hosts/ChangeContentSource/components/ContentSourceForm.js +7 -2
  33. data/webpack/scenes/Subscriptions/Manifest/DeleteManifestModalText.js +38 -21
  34. data/webpack/scenes/Subscriptions/Manifest/ManageManifestModal.js +4 -1
  35. data/webpack/scenes/Subscriptions/SubscriptionsPage.js +1 -0
  36. data/webpack/scenes/Subscriptions/__tests__/__snapshots__/SubscriptionsPage.test.js.snap +1 -0
  37. metadata +7 -6
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 41a12235a03808c77522d0fbc1c0fe8394ee1bfd6735b4a9671909ccc341bd3d
4
- data.tar.gz: 4b6514b149494a537a41b1b944c1133328591aa9c950bf9ff343679a76532100
3
+ metadata.gz: 2725e254c69e3a52955ec2e85605097ad13751770b46de79a14bb6153451e5b7
4
+ data.tar.gz: a59ffe3ac6b743b36ac2207a44c333dc504975b54d23efb2d9d04aa13741c575
5
5
  SHA512:
6
- metadata.gz: c7f886b06454dafcf674d3ba7694d8bf7ef79799d3e1ebc21523edb59b1139d4ca3e8ad0733ea2cd2880dcb4eebe1b52c2e675da46935192906c95ce8281f4dc
7
- data.tar.gz: e00320f2a9a0273fd850feda7946c9fa6f2e068358fc7177e406334fa0400cd240fa36c8ed216c46e118ce64302081c9331f8101c63cee78774482836144023e
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
- if (::Foreman::Cast.to_bool(params[:publish_only_if_needed]) && !@content_view.needs_publish?)
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.where(:repository_id => ::Katello::Repository.all).select(unit_id_field))
172
+ where.not(:id => repository_association_class.select(unit_id_field))
173
173
  else
174
- where.not(:repository_id => ::Katello::Repository.all)
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.pluck(:root_id).uniq) }
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 << { product: product,
50
- content: product_content,
51
- substitutions: substitutions,
52
- override_url: fetch_feed_url(repo)
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 a complete sync on the following repository: #{repository_reference.root_repository.name}. Original error: #{e.message}"
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
- " Please run a complete sync on the following repository: #{repository_reference.root_repository.name}. Original error: #{e.message}"
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 a complete sync on the following repository:"\
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
- migrator = Katello::Util::DefaultEnablementMigrator.new
7
- migrator.execute!
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
@@ -1,3 +1,3 @@
1
1
  module Katello
2
- VERSION = "4.9.0".freeze
2
+ VERSION = "4.9.1".freeze
3
3
  end
@@ -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}`}>{`${contentView.name}`}</a>
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}`}>{`${lifecycleEnvironment.name}`}</Label>
122
+ <Label isTruncated color="purple" href={`/lifecycle_environments/${lifecycleEnvironment.id}`}>{lifecycleEnvironment.name}</Label>
123
123
  </Tooltip>
124
124
  </Flex>
125
125
  </Flex>
@@ -580,7 +580,7 @@ export const PackagesTab = () => {
580
580
  }
581
581
 
582
582
  return (
583
- <Tr key={`${id}`} ouiaId={`action-row-${id}`}>
583
+ <Tr key={id} ouiaId={`action-row-${id}`}>
584
584
  {showActions ? (
585
585
  <Td
586
586
  select={{
@@ -59,7 +59,7 @@ const ContentRepositories = ({ contentType, id, tabKey }) => {
59
59
  </Thead>
60
60
  <Tbody>
61
61
  {results?.map((details, idx) => (
62
- <Tr key={`${details.id}`} ouiaId={`content-repositories-row-${idx}`}>
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={`${details.id}`} ouiaId={`${details.id}-tr`}>
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
  }
@@ -10,7 +10,7 @@ const ComponentEnvironments = ({ environments }) => environments.map((env, index
10
10
  href={`/lifecycle_environments/${env.id}`}
11
11
  isTruncated
12
12
  >
13
- {`${env.name}`}
13
+ {env.name}
14
14
  </Label>
15
15
  ));
16
16
 
@@ -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}`}>{`${environment?.name ?? __('all environments')}`}</Label></>;
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}`}>{`${environment?.name}`}</Label></>;
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={`${id}`} key={`${id}`}>
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={`${id}`} key={`${id}`}>
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
 
@@ -7,6 +7,7 @@
7
7
  "latest_version": "5.0",
8
8
  "auto_publish": false,
9
9
  "solve_dependencies": false,
10
+ "needs_publish": false,
10
11
  "generated_for": "none",
11
12
  "repository_ids": [
12
13
  58,
@@ -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, filtered, next_version: nextVersion,
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 useSelectorMock = jest.spyOn(reactRedux, 'useSelector');
21
- useSelectorMock.mockReturnValue(environmentPathsData);
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(() => expect(getByText('Publish new version - 6.0')).toBeInTheDocument());
30
- useSelectorMock.mockClear();
31
- assertNockRequest(scope, done);
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, done);
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, done);
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, done);
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={`${details.content_view.id}`} ouiaId={`${details.content_view.id}`}>
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={`${cs.id}`}
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 key={`${cv.id}`} value={`${cv.name}`} cv={cv} env={environments[0]} />)}
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 question = __('Are you sure you want to delete the manifest?');
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>{question}</p>
18
- <p>{note}</p>
19
- <ul className="list-aligned">
20
- <li>{l1}</li>
21
- <li>{l2}</li>
22
- <li>{l3}</li>
23
- <li>{l4}</li>
24
- </ul>
25
- <p>{debug}</p>
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.0
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-06-21 00:00:00.000000000 Z
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: '0'
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: '0'
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