katello 4.12.1 → 4.13.0

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 (245) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/javascripts/katello/locale/bn/katello.js +3365 -3350
  3. data/app/assets/javascripts/katello/locale/bn_IN/katello.js +3136 -3121
  4. data/app/assets/javascripts/katello/locale/ca/katello.js +3588 -3576
  5. data/app/assets/javascripts/katello/locale/cs/katello.js +3499 -3487
  6. data/app/assets/javascripts/katello/locale/cs_CZ/katello.js +4186 -4186
  7. data/app/assets/javascripts/katello/locale/de/katello.js +5553 -5562
  8. data/app/assets/javascripts/katello/locale/de_AT/katello.js +3008 -2993
  9. data/app/assets/javascripts/katello/locale/de_DE/katello.js +3066 -3051
  10. data/app/assets/javascripts/katello/locale/el/katello.js +3376 -3370
  11. data/app/assets/javascripts/katello/locale/en/katello.js +3008 -2993
  12. data/app/assets/javascripts/katello/locale/en_GB/katello.js +3076 -3073
  13. data/app/assets/javascripts/katello/locale/en_US/katello.js +3008 -2993
  14. data/app/assets/javascripts/katello/locale/es/katello.js +5366 -5372
  15. data/app/assets/javascripts/katello/locale/et_EE/katello.js +3008 -2993
  16. data/app/assets/javascripts/katello/locale/fr/katello.js +5975 -5984
  17. data/app/assets/javascripts/katello/locale/gl/katello.js +3125 -3113
  18. data/app/assets/javascripts/katello/locale/gu/katello.js +3119 -3104
  19. data/app/assets/javascripts/katello/locale/he_IL/katello.js +3020 -3005
  20. data/app/assets/javascripts/katello/locale/hi/katello.js +3137 -3122
  21. data/app/assets/javascripts/katello/locale/id/katello.js +3008 -2993
  22. data/app/assets/javascripts/katello/locale/it/katello.js +4469 -4466
  23. data/app/assets/javascripts/katello/locale/ja/katello.js +5969 -5978
  24. data/app/assets/javascripts/katello/locale/ka/katello.js +5649 -5652
  25. data/app/assets/javascripts/katello/locale/kn/katello.js +3136 -3121
  26. data/app/assets/javascripts/katello/locale/ko/katello.js +4717 -4720
  27. data/app/assets/javascripts/katello/locale/locale/katello.js +1050 -1084
  28. data/app/assets/javascripts/katello/locale/ml_IN/katello.js +3008 -2993
  29. data/app/assets/javascripts/katello/locale/mr/katello.js +3136 -3121
  30. data/app/assets/javascripts/katello/locale/nl_NL/katello.js +3116 -3101
  31. data/app/assets/javascripts/katello/locale/or/katello.js +3137 -3122
  32. data/app/assets/javascripts/katello/locale/pa/katello.js +3136 -3121
  33. data/app/assets/javascripts/katello/locale/pl/katello.js +3210 -3195
  34. data/app/assets/javascripts/katello/locale/pl_PL/katello.js +3008 -2993
  35. data/app/assets/javascripts/katello/locale/pt/katello.js +3009 -2994
  36. data/app/assets/javascripts/katello/locale/pt_BR/katello.js +5362 -5368
  37. data/app/assets/javascripts/katello/locale/ro/katello.js +3008 -2993
  38. data/app/assets/javascripts/katello/locale/ro_RO/katello.js +3008 -2993
  39. data/app/assets/javascripts/katello/locale/ru/katello.js +4638 -4641
  40. data/app/assets/javascripts/katello/locale/sl/katello.js +3051 -3036
  41. data/app/assets/javascripts/katello/locale/sv_SE/katello.js +3156 -3144
  42. data/app/assets/javascripts/katello/locale/ta/katello.js +3365 -3350
  43. data/app/assets/javascripts/katello/locale/ta_IN/katello.js +3121 -3106
  44. data/app/assets/javascripts/katello/locale/te/katello.js +3136 -3121
  45. data/app/assets/javascripts/katello/locale/tr/katello.js +3025 -3010
  46. data/app/assets/javascripts/katello/locale/vi/katello.js +3008 -2993
  47. data/app/assets/javascripts/katello/locale/vi_VN/katello.js +3008 -2993
  48. data/app/assets/javascripts/katello/locale/zh/katello.js +3008 -2993
  49. data/app/assets/javascripts/katello/locale/zh_CN/katello.js +5968 -5977
  50. data/app/assets/javascripts/katello/locale/zh_TW/katello.js +4694 -4697
  51. data/app/controllers/katello/api/registry/registry_proxies_controller.rb +370 -132
  52. data/app/controllers/katello/api/rhsm/candlepin_dynflow_proxy_controller.rb +12 -20
  53. data/app/controllers/katello/api/v2/activation_keys_controller.rb +10 -4
  54. data/app/controllers/katello/api/v2/capsule_content_controller.rb +24 -0
  55. data/app/controllers/katello/api/v2/content_view_versions_controller.rb +9 -2
  56. data/app/controllers/katello/api/v2/debs_controller.rb +1 -1
  57. data/app/controllers/katello/api/v2/errata_controller.rb +1 -1
  58. data/app/controllers/katello/api/v2/host_subscriptions_controller.rb +12 -4
  59. data/app/controllers/katello/api/v2/hosts_bulk_actions_controller.rb +3 -3
  60. data/app/controllers/katello/api/v2/organizations_controller.rb +0 -11
  61. data/app/controllers/katello/api/v2/packages_controller.rb +1 -1
  62. data/app/controllers/katello/api/v2/repositories_controller.rb +19 -13
  63. data/app/controllers/katello/api/v2/repository_sets_controller.rb +2 -1
  64. data/app/controllers/katello/api/v2/simple_content_access_controller.rb +9 -22
  65. data/app/controllers/katello/concerns/api/v2/authorization.rb +1 -1
  66. data/app/helpers/katello/subscription_mailer_helper.rb +1 -1
  67. data/app/jobs/create_manifest_expire_soon_warning_notifications.rb +11 -0
  68. data/app/lib/actions/candlepin/owner/regenerate_upstream_identity_cert.rb +21 -0
  69. data/app/lib/actions/katello/capsule_content/sync.rb +1 -1
  70. data/app/lib/actions/katello/capsule_content/sync_capsule.rb +7 -2
  71. data/app/lib/actions/katello/capsule_content/verify_checksum.rb +75 -0
  72. data/app/lib/actions/katello/content_view/promote.rb +1 -1
  73. data/app/lib/actions/katello/content_view/publish.rb +1 -1
  74. data/app/lib/actions/katello/content_view_version/verify_checksum.rb +29 -0
  75. data/app/lib/actions/katello/host/hypervisors_update.rb +1 -0
  76. data/app/lib/actions/katello/host/update_content_view.rb +2 -2
  77. data/app/lib/actions/katello/organization/manifest_delete.rb +6 -1
  78. data/app/lib/actions/katello/organization/manifest_import.rb +5 -0
  79. data/app/lib/actions/katello/organization/manifest_refresh.rb +3 -0
  80. data/app/lib/actions/katello/repository/create.rb +17 -11
  81. data/app/lib/actions/katello/repository/create_root.rb +4 -2
  82. data/app/lib/actions/katello/repository/metadata_generate.rb +7 -1
  83. data/app/lib/actions/katello/repository/remove_content.rb +1 -0
  84. data/app/lib/actions/katello/repository/sync.rb +2 -1
  85. data/app/lib/actions/katello/repository/upload_files.rb +1 -0
  86. data/app/lib/actions/katello/upstream_subscriptions/bind_entitlement.rb +1 -1
  87. data/app/lib/actions/pulp3/capsule_content/verify_checksum.rb +27 -0
  88. data/app/lib/actions/pulp3/orchestration/content_view_version/export_repository.rb +7 -9
  89. data/app/lib/actions/pulp3/orchestration/content_view_version/syncable_export.rb +5 -4
  90. data/app/lib/actions/pulp3/orchestration/orphan_cleanup/remove_orphans.rb +1 -0
  91. data/app/lib/actions/pulp3/orphan_cleanup/purge_completed_tasks.rb +15 -0
  92. data/app/lib/katello/concerns/base_template_scope_extensions.rb +7 -2
  93. data/app/lib/katello/http_resource.rb +6 -1
  94. data/app/lib/katello/resources/candlepin/consumer.rb +1 -1
  95. data/app/lib/katello/resources/candlepin/upstream_consumer.rb +18 -6
  96. data/app/lib/katello/resources/candlepin/upstream_job.rb +1 -1
  97. data/app/lib/katello/resources/registry.rb +25 -0
  98. data/app/mailers/katello/subscription_mailer.rb +3 -6
  99. data/app/models/katello/concerns/organization_extensions.rb +42 -3
  100. data/app/models/katello/content_view.rb +30 -0
  101. data/app/models/katello/content_view_environment_content_facet.rb +4 -2
  102. data/app/models/katello/glue/provider.rb +19 -12
  103. data/app/models/katello/glue/pulp/repos.rb +11 -3
  104. data/app/models/katello/host/content_facet.rb +1 -1
  105. data/app/models/katello/host/subscription_facet.rb +1 -1
  106. data/app/models/katello/ping.rb +1 -1
  107. data/app/models/katello/repository.rb +32 -1
  108. data/app/models/katello/root_repository.rb +4 -6
  109. data/app/models/katello/trace_status.rb +1 -1
  110. data/app/services/katello/content_unit_indexer.rb +9 -0
  111. data/app/services/katello/pulp3/alternate_content_source.rb +4 -6
  112. data/app/services/katello/pulp3/api/core.rb +21 -0
  113. data/app/services/katello/pulp3/api/docker.rb +4 -0
  114. data/app/services/katello/pulp3/api/yum.rb +11 -0
  115. data/app/services/katello/pulp3/docker_manifest.rb +5 -1
  116. data/app/services/katello/pulp3/repository/generic.rb +1 -1
  117. data/app/services/katello/pulp3/repository/yum.rb +1 -6
  118. data/app/services/katello/pulp3/repository.rb +26 -6
  119. data/app/services/katello/pulp3/repository_mirror.rb +13 -12
  120. data/app/services/katello/pulp3/service_common.rb +2 -10
  121. data/app/services/katello/pulp3/smart_proxy_repository.rb +0 -2
  122. data/app/services/katello/ui_notifications/subscriptions/manifest_expire_soon_warning.rb +75 -0
  123. data/app/views/foreman/job_templates/update_package_-_katello_ansible_default.erb +5 -1
  124. data/app/views/foreman/job_templates/update_packages_by_search_query_-_katello_ansible_default.erb +2 -2
  125. data/app/views/foreman/job_templates/upload_profile.erb +16 -0
  126. data/app/views/foreman/smart_proxies/_content_tab.html.erb +3 -1
  127. data/app/views/katello/api/v2/content_view_filter_rules/show.json.rabl +9 -0
  128. data/app/views/katello/api/v2/docker_manifests/show.json.rabl +1 -0
  129. data/app/views/katello/api/v2/organizations/show.json.rabl +9 -1
  130. data/app/views/overrides/activation_keys/_host_environment_select.html.erb +1 -1
  131. data/app/views/overrides/activation_keys/_host_media_type_select.html.erb +15 -5
  132. data/config/routes/api/registry.rb +4 -8
  133. data/config/routes/api/v2.rb +2 -0
  134. data/db/migrate/20240423112842_add_fields_to_katello_docker_manifest.rb +8 -0
  135. data/db/migrate/20240502192021_change_katello_repository_rpms_id_seq_to_big_int.rb +9 -0
  136. data/db/migrate/20240520142245_add_container_push_props_to_repo.rb +7 -0
  137. data/db/migrate/20240531193030_remove_sha1_repository_checksum_type.rb +10 -0
  138. data/db/seeds.d/109-katello-notification-blueprints.rb +6 -0
  139. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/activation-keys/details/activation-key-repository-sets.controller.js +3 -3
  140. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/content-credentials/new/views/new-content-credential.html +2 -1
  141. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/content-hosts/details/content-host-repository-sets.controller.js +3 -3
  142. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/i18n/bastion_katello.pot +0 -15
  143. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/products/details/repositories/checksum.service.js +6 -1
  144. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/products/details/repositories/details/views/repository-info.html +8 -6
  145. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/products/details/repositories/new/views/new-repository.html +12 -13
  146. data/lib/katello/permission_creator.rb +3 -3
  147. data/lib/katello/permissions/registry_permissions.rb +4 -7
  148. data/lib/katello/plugin.rb +21 -8
  149. data/lib/katello/repository_types/ostree.rb +7 -0
  150. data/lib/katello/scheduled_jobs.rb +7 -1
  151. data/lib/katello/tasks/clean_backend_objects.rake +1 -1
  152. data/lib/katello/tasks/repository.rake +22 -0
  153. data/lib/katello/version.rb +1 -1
  154. data/locale/action_names.rb +4 -3
  155. data/locale/bn/katello.po +166 -151
  156. data/locale/bn_IN/katello.po +166 -151
  157. data/locale/ca/katello.po +166 -151
  158. data/locale/cs/katello.po +166 -151
  159. data/locale/cs_CZ/LC_MESSAGES/katello.mo +0 -0
  160. data/locale/cs_CZ/katello.po +172 -157
  161. data/locale/de/LC_MESSAGES/katello.mo +0 -0
  162. data/locale/de/katello.po +178 -163
  163. data/locale/de_AT/katello.po +166 -151
  164. data/locale/de_DE/katello.po +166 -151
  165. data/locale/el/katello.po +166 -151
  166. data/locale/en/katello.po +166 -151
  167. data/locale/en_GB/katello.po +166 -151
  168. data/locale/en_US/katello.po +166 -151
  169. data/locale/es/LC_MESSAGES/katello.mo +0 -0
  170. data/locale/es/katello.po +178 -163
  171. data/locale/et_EE/katello.po +166 -151
  172. data/locale/fr/LC_MESSAGES/katello.mo +0 -0
  173. data/locale/fr/katello.po +179 -164
  174. data/locale/gl/katello.po +166 -151
  175. data/locale/gu/katello.po +166 -151
  176. data/locale/he_IL/katello.po +166 -151
  177. data/locale/hi/katello.po +166 -151
  178. data/locale/id/katello.po +166 -151
  179. data/locale/it/LC_MESSAGES/katello.mo +0 -0
  180. data/locale/it/katello.po +169 -154
  181. data/locale/ja/LC_MESSAGES/katello.mo +0 -0
  182. data/locale/ja/katello.po +179 -164
  183. data/locale/ka/LC_MESSAGES/katello.mo +0 -0
  184. data/locale/ka/katello.po +177 -162
  185. data/locale/katello.pot +1119 -1062
  186. data/locale/kn/katello.po +166 -151
  187. data/locale/ko/LC_MESSAGES/katello.mo +0 -0
  188. data/locale/ko/katello.po +174 -159
  189. data/locale/ml_IN/katello.po +166 -151
  190. data/locale/mr/katello.po +166 -151
  191. data/locale/nl_NL/katello.po +166 -151
  192. data/locale/or/katello.po +166 -151
  193. data/locale/pa/katello.po +166 -151
  194. data/locale/pl/katello.po +166 -151
  195. data/locale/pl_PL/katello.po +166 -151
  196. data/locale/pt/katello.po +166 -151
  197. data/locale/pt_BR/LC_MESSAGES/katello.mo +0 -0
  198. data/locale/pt_BR/katello.po +178 -163
  199. data/locale/ro/katello.po +166 -151
  200. data/locale/ro_RO/katello.po +166 -151
  201. data/locale/ru/LC_MESSAGES/katello.mo +0 -0
  202. data/locale/ru/katello.po +171 -156
  203. data/locale/sl/katello.po +166 -151
  204. data/locale/sv_SE/katello.po +166 -151
  205. data/locale/ta/katello.po +166 -151
  206. data/locale/ta_IN/katello.po +166 -151
  207. data/locale/te/katello.po +166 -151
  208. data/locale/tr/katello.po +166 -151
  209. data/locale/vi/katello.po +166 -151
  210. data/locale/vi_VN/katello.po +166 -151
  211. data/locale/zh/katello.po +166 -151
  212. data/locale/zh_CN/LC_MESSAGES/katello.mo +0 -0
  213. data/locale/zh_CN/katello.po +179 -164
  214. data/locale/zh_TW/LC_MESSAGES/katello.mo +0 -0
  215. data/locale/zh_TW/katello.po +171 -156
  216. data/package.json +0 -1
  217. data/webpack/ForemanColumnExtensions/index.js +129 -0
  218. data/webpack/components/Content/ContentTable.js +0 -1
  219. data/webpack/components/Content/__tests__/__snapshots__/ContentTable.test.js.snap +0 -1
  220. data/webpack/components/Table/TableWrapper.js +14 -0
  221. data/webpack/components/extensions/HostDetails/ActionsBar/index.js +1 -1
  222. data/webpack/components/extensions/Hosts/ActionsBar/index.js +20 -1
  223. data/webpack/components/extensions/Hosts/BulkActions/BulkChangeHostCVModal/BulkChangeHostCVModal.js +220 -0
  224. data/webpack/components/extensions/Hosts/BulkActions/BulkChangeHostCVModal/actions.js +23 -0
  225. data/webpack/components/extensions/Hosts/BulkActions/BulkChangeHostCVModal/index.js +25 -0
  226. data/webpack/components/extensions/Hosts/BulkActions/__tests__/bulkChangeHostCVModal.test.js +133 -0
  227. data/webpack/global_index.js +9 -0
  228. data/webpack/scenes/Hosts/ChangeContentSource/actions.js +3 -1
  229. data/webpack/scenes/Hosts/ChangeContentSource/components/ContentSourceForm.js +62 -24
  230. data/webpack/scenes/Hosts/ChangeContentSource/index.js +24 -16
  231. data/webpack/scenes/ModuleStreams/ModuleStreamsPage.js +33 -39
  232. data/webpack/scenes/ModuleStreams/__tests__/ModuleStreamPage.test.js +4 -2
  233. data/webpack/scenes/ModuleStreams/__tests__/__snapshots__/ModuleStreamsTable.test.js.snap +0 -1
  234. data/webpack/scenes/RedHatRepositories/__tests__/__snapshots__/RedHatRepositoriesPage.test.js.snap +1 -0
  235. data/webpack/scenes/Subscriptions/Manifest/ManageManifestModal.js +66 -5
  236. data/webpack/scenes/Subscriptions/UpstreamSubscriptions/UpstreamSubscriptionsPage.js +16 -13
  237. data/webpack/scenes/Subscriptions/UpstreamSubscriptions/__tests__/__snapshots__/UpstreamSubscriptionsPage.test.js.snap +14 -8
  238. data/webpack/scenes/Subscriptions/__tests__/__snapshots__/SubscriptionsPage.test.js.snap +1 -0
  239. data/webpack/scenes/Subscriptions/components/SubscriptionsToolbar/SubscriptionsToolbar.js +1 -1
  240. metadata +60 -42
  241. data/app/lib/actions/katello/host/upload_package_profile.rb +0 -45
  242. data/app/lib/actions/katello/host/upload_profiles.rb +0 -47
  243. data/webpack/utils/__tests__/useParamsWithHash.test.js +0 -22
  244. data/webpack/utils/paramsFromHash.js +0 -16
  245. data/webpack/utils/useUrlParams.js +0 -14
data/package.json CHANGED
@@ -51,7 +51,6 @@
51
51
  "angular": "1.8.2",
52
52
  "bootstrap-select": "1.13.18",
53
53
  "ngreact": "^0.5.0",
54
- "query-string": "^6.1.0",
55
54
  "react-bootstrap": "^0.32.1",
56
55
  "use-deep-compare-effect": "^1.6.1"
57
56
  }
@@ -0,0 +1,129 @@
1
+ /* eslint-disable no-param-reassign */
2
+ import React from 'react';
3
+ import {
4
+ BugIcon,
5
+ SecurityIcon,
6
+ EnhancementIcon,
7
+ PackageIcon,
8
+ } from '@patternfly/react-icons';
9
+ import { Link } from 'react-router-dom';
10
+ import { Flex, FlexItem } from '@patternfly/react-core';
11
+ import { translate as __ } from 'foremanReact/common/I18n';
12
+ import RelativeDateTime from 'foremanReact/components/common/dates/RelativeDateTime';
13
+
14
+ const hostsIndexColumnExtensions = [
15
+ {
16
+ columnName: 'rhel_lifecycle_status',
17
+ title: __('RHEL Lifecycle status'),
18
+ wrapper: (hostDetails) => {
19
+ const rhelLifecycle = hostDetails?.rhel_lifecycle_status_label;
20
+ return rhelLifecycle || '—';
21
+ },
22
+ weight: 2000,
23
+ isSorted: true,
24
+ },
25
+ {
26
+ columnName: 'installable_updates',
27
+ title: __('Installable updates'),
28
+ wrapper: (hostDetails) => {
29
+ const errataCounts = hostDetails?.content_facet_attributes?.errata_counts;
30
+ const registered = !!hostDetails?.subscription_facet_attributes?.uuid;
31
+ const { security, bugfix, enhancement } = errataCounts ?? {};
32
+ const upgradableRpmCount = hostDetails?.content_facet_attributes?.upgradable_package_count;
33
+ if (!registered) return '—';
34
+ const hostErrataUrl = type => `hosts/${hostDetails?.name}#/Content/errata?type=${type}&show=installable`;
35
+ return (
36
+ <Flex alignContent={{ default: 'alignContentSpaceBetween' }}>
37
+ {security !== undefined &&
38
+ <FlexItem>
39
+ <SecurityIcon color="#0066cc" />
40
+ <Link to={hostErrataUrl('security')}>{security}</Link>
41
+ </FlexItem>
42
+ }
43
+ {bugfix !== undefined &&
44
+ <FlexItem>
45
+ <BugIcon color="#8bc1f7" />
46
+ <Link to={hostErrataUrl('bugfix')}>{bugfix}</Link>
47
+ </FlexItem>
48
+ }
49
+ {enhancement !== undefined &&
50
+ <FlexItem>
51
+ <EnhancementIcon color="#002f5d" />
52
+ <Link to={hostErrataUrl('enhancement')}>{enhancement}</Link>
53
+ </FlexItem>
54
+ }
55
+ {upgradableRpmCount !== undefined &&
56
+ <FlexItem>
57
+ <PackageIcon />
58
+ <Link to={`hosts/${hostDetails?.name}#/Content/Packages?status=Upgradable`}>{upgradableRpmCount}</Link>
59
+ </FlexItem>
60
+ }
61
+ </Flex>
62
+ );
63
+ },
64
+ weight: 2100,
65
+ isSorted: false,
66
+ },
67
+ {
68
+ columnName: 'last_checkin',
69
+ title: __('Last seen'),
70
+ wrapper: (hostDetails) => {
71
+ const lastCheckin =
72
+ hostDetails?.subscription_facet_attributes?.last_checkin;
73
+ return <RelativeDateTime defaultValue="—" date={lastCheckin} />;
74
+ },
75
+ weight: 2200,
76
+ isSorted: true,
77
+ },
78
+ {
79
+ columnName: 'lifecycle_environment',
80
+ title: __('Lifecycle environment'),
81
+ wrapper: (hostDetails) => {
82
+ const lifecycleEnvironment =
83
+ hostDetails?.content_facet_attributes?.lifecycle_environment?.name;
84
+ return lifecycleEnvironment || '—';
85
+ },
86
+ weight: 2300,
87
+ isSorted: true,
88
+ },
89
+ {
90
+ columnName: 'content_view',
91
+ title: __('Content view'),
92
+ wrapper: (hostDetails) => {
93
+ const contentView =
94
+ hostDetails?.content_facet_attributes?.content_view?.name;
95
+ return contentView || '—';
96
+ },
97
+ weight: 2400,
98
+ isSorted: true,
99
+ },
100
+ {
101
+ columnName: 'content_source',
102
+ title: __('Content source'),
103
+ wrapper: (hostDetails) => {
104
+ const contentSource =
105
+ hostDetails?.content_facet_attributes?.content_source_name;
106
+ return contentSource || '—';
107
+ },
108
+ weight: 2500,
109
+ isSorted: false,
110
+ },
111
+ {
112
+ columnName: 'registered_at',
113
+ title: __('Registered at'),
114
+ wrapper: (hostDetails) => {
115
+ const registeredAt = hostDetails?.subscription_facet_attributes?.registered_at;
116
+ return <RelativeDateTime defaultValue="—" date={registeredAt} />;
117
+ },
118
+ weight: 2600,
119
+ isSorted: true,
120
+ },
121
+ ];
122
+
123
+ hostsIndexColumnExtensions.forEach((column) => {
124
+ column.tableName = 'hosts';
125
+ column.categoryName = 'Content';
126
+ column.categoryKey = 'content';
127
+ });
128
+
129
+ export default hostsIndexColumnExtensions;
@@ -20,7 +20,6 @@ const ContentTable = ({ content, tableSchema, onPaginationChange }) => {
20
20
  loadingText={__('Loading')}
21
21
  >
22
22
  <Table
23
- ouiaId="content-table-table"
24
23
  columns={tableSchema}
25
24
  rows={results}
26
25
  pagination={pagination}
@@ -15,7 +15,6 @@ exports[`Content Table should render and contain appropriate components 1`] = `
15
15
  }
16
16
  itemCount={2}
17
17
  onPaginationChange={[Function]}
18
- ouiaId="content-table-table"
19
18
  pagination={Object {}}
20
19
  rows={
21
20
  Array [
@@ -36,10 +36,12 @@ const TableWrapper = ({
36
36
  selectAll,
37
37
  selectAllMode,
38
38
  selectNone,
39
+ selectDefault,
39
40
  selectPage,
40
41
  areAllRowsOnPageSelected,
41
42
  areAllRowsSelected,
42
43
  selectedCount,
44
+ selectedDefaultCount,
43
45
  selectedResults,
44
46
  clearSelectedResults,
45
47
  emptySearchBody,
@@ -49,6 +51,8 @@ const TableWrapper = ({
49
51
  nodesBelowSearch,
50
52
  bookmarkController,
51
53
  readOnlyBookmarks,
54
+ inclusionSet,
55
+ exclusionSet,
52
56
  ...allTableProps
53
57
  }) => {
54
58
  const dispatch = useDispatch();
@@ -182,7 +186,9 @@ const TableWrapper = ({
182
186
  selectAll,
183
187
  selectPage,
184
188
  selectNone,
189
+ selectDefault,
185
190
  selectedCount,
191
+ selectedDefaultCount,
186
192
  pageRowCount,
187
193
  }
188
194
  }
@@ -298,11 +304,13 @@ TableWrapper.propTypes = {
298
304
  ])),
299
305
  displaySelectAllCheckbox: PropTypes.bool,
300
306
  selectedCount: PropTypes.number,
307
+ selectedDefaultCount: PropTypes.number,
301
308
  selectedResults: PropTypes.arrayOf(PropTypes.shape({})),
302
309
  clearSelectedResults: PropTypes.func,
303
310
  selectAll: PropTypes.func,
304
311
  selectAllMode: PropTypes.bool,
305
312
  selectNone: PropTypes.func,
313
+ selectDefault: PropTypes.func,
306
314
  selectPage: PropTypes.func,
307
315
  areAllRowsOnPageSelected: PropTypes.func,
308
316
  areAllRowsSelected: PropTypes.func,
@@ -314,6 +322,8 @@ TableWrapper.propTypes = {
314
322
  bookmarkController: PropTypes.string,
315
323
  readOnlyBookmarks: PropTypes.bool,
316
324
  resetFilters: PropTypes.func,
325
+ inclusionSet: PropTypes.oneOfType([PropTypes.array, PropTypes.element, PropTypes.object]),
326
+ exclusionSet: PropTypes.oneOfType([PropTypes.array, PropTypes.element, PropTypes.object]),
317
327
  };
318
328
 
319
329
  TableWrapper.defaultProps = {
@@ -329,11 +339,13 @@ TableWrapper.defaultProps = {
329
339
  toggleGroup: null,
330
340
  displaySelectAllCheckbox: false,
331
341
  selectedCount: 0,
342
+ selectedDefaultCount: 0,
332
343
  selectedResults: [],
333
344
  clearSelectedResults: noop,
334
345
  selectAll: undefined,
335
346
  selectAllMode: false,
336
347
  selectNone: undefined,
348
+ selectDefault: undefined,
337
349
  selectPage: undefined,
338
350
  areAllRowsOnPageSelected: noop,
339
351
  areAllRowsSelected: noop,
@@ -346,6 +358,8 @@ TableWrapper.defaultProps = {
346
358
  readOnlyBookmarks: false,
347
359
  resetFilters: undefined,
348
360
  autocompleteQueryParams: undefined,
361
+ inclusionSet: new Set([]),
362
+ exclusionSet: new Set([]),
349
363
  };
350
364
 
351
365
  export default TableWrapper;
@@ -65,7 +65,7 @@ const HostActionsBar = () => {
65
65
  <DropdownItem
66
66
  ouiaId="katello-change-host-content-source"
67
67
  key="katello-change-host-content-source"
68
- href={foremanUrl(`/change_host_content_source?host_id=${hostDetails?.id}`)}
68
+ href={foremanUrl(`/change_host_content_source?host_id=${hostDetails?.id}&initialContentSourceId=${hostDetails?.content_facet_attributes?.content_source_id}`)}
69
69
  icon={<CubeIcon />}
70
70
  >
71
71
  {__('Change content source')}
@@ -1,8 +1,11 @@
1
- import React, { useContext } from 'react';
1
+ import React, { useContext, useEffect } from 'react';
2
+ import { useDispatch } from 'react-redux';
2
3
  import { DropdownItem } from '@patternfly/react-core';
3
4
  import { translate as __ } from 'foremanReact/common/I18n';
4
5
  import { foremanUrl } from 'foremanReact/common/helpers';
5
6
  import { ForemanHostsIndexActionsBarContext } from 'foremanReact/components/HostsIndex';
7
+ import { useForemanModal } from 'foremanReact/components/ForemanModal/ForemanModalHooks';
8
+ import { addModal } from 'foremanReact/components/ForemanModal/ForemanModalActions';
6
9
 
7
10
  const HostActionsBar = () => {
8
11
  const {
@@ -11,6 +14,14 @@ const HostActionsBar = () => {
11
14
  selectAllMode,
12
15
  } = useContext(ForemanHostsIndexActionsBarContext);
13
16
 
17
+ const dispatch = useDispatch();
18
+ useEffect(() => {
19
+ dispatch(addModal({
20
+ id: 'bulk-change-cv-modal',
21
+ }));
22
+ }, [dispatch]);
23
+ const { setModalOpen } = useForemanModal({ id: 'bulk-change-cv-modal' });
24
+
14
25
  let href = '';
15
26
  if (selectAllMode) {
16
27
  const query = fetchBulkParams({ selectAllQuery: 'created_at < "1 second ago"' });
@@ -29,6 +40,14 @@ const HostActionsBar = () => {
29
40
  >
30
41
  {__('Change content source')}
31
42
  </DropdownItem>
43
+ <DropdownItem
44
+ ouiaId="bulk-change-cv-dropdown-item"
45
+ key="bulk-change-cv-dropdown-item"
46
+ onClick={setModalOpen}
47
+ isDisabled={selectedCount === 0}
48
+ >
49
+ {__('Change content view environments')}
50
+ </DropdownItem>
32
51
  </>
33
52
  );
34
53
  };
@@ -0,0 +1,220 @@
1
+ import React, { useState } from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import { useDispatch, useSelector } from 'react-redux';
4
+ import { FormattedMessage } from 'react-intl';
5
+ import { Modal, Button, Alert, TextContent, Text, TextVariants } from '@patternfly/react-core';
6
+ import { translate as __ } from 'foremanReact/common/I18n';
7
+ import { STATUS } from 'foremanReact/constants';
8
+ import { useAPI } from 'foremanReact/common/hooks/API/APIHooks';
9
+ import { selectAPIStatus } from 'foremanReact/redux/API/APISelectors';
10
+ import { ENVIRONMENT_PATHS_KEY } from '../../../../../scenes/ContentViews/components/EnvironmentPaths/EnvironmentPathConstants';
11
+ import EnvironmentPaths from '../../../../../scenes/ContentViews/components/EnvironmentPaths/EnvironmentPaths';
12
+ import ContentViewSelect from '../../../../../scenes/ContentViews/components/ContentViewSelect/ContentViewSelect';
13
+ import ContentViewSelectOption from '../../../../../scenes/ContentViews/components/ContentViewSelect/ContentViewSelectOption';
14
+ import api from '../../../../../services/api';
15
+ import getContentViews from '../../../../../scenes/ContentViews/ContentViewsActions';
16
+ import { selectContentViews, selectContentViewStatus } from '../../../../../scenes/ContentViews/ContentViewSelectors';
17
+ import { bulkUpdateHostContentViewAndEnvironment } from './actions';
18
+ import { getCVPlaceholderText } from '../../../../../scenes/ContentViews/components/ContentViewSelect/helpers';
19
+ import HOST_CV_AND_ENV_KEY from '../../../HostDetails/Cards/ContentViewDetailsCard/HostContentViewConstants';
20
+
21
+ const ENV_PATH_OPTIONS = { key: ENVIRONMENT_PATHS_KEY };
22
+
23
+ const BulkChangeHostCVModal = ({
24
+ isOpen,
25
+ closeModal,
26
+ selectedCount,
27
+ orgId,
28
+ fetchBulkParams,
29
+ }) => {
30
+ const [selectedLifecycleEnv, setSelectedLifecycleEnv]
31
+ = useState([]);
32
+
33
+ const [selectedContentView, setSelectedContentView] = useState(null);
34
+ const [cvSelectOpen, setCVSelectOpen] = useState(false);
35
+ const dispatch = useDispatch();
36
+ const contentViewsInEnvResponse = useSelector(state => selectContentViews(state, '_FOR_DEFAULT_ENV'));
37
+ const { results } = contentViewsInEnvResponse;
38
+ const contentViewsInEnvStatus = useSelector(state => selectContentViewStatus(state, '_FOR_DEFAULT_ENV'));
39
+ const hostUpdateStatus = useSelector(state => selectAPIStatus(state, HOST_CV_AND_ENV_KEY));
40
+ const pathsUrl = `/organizations/${orgId}/environments/paths?permission_type=promotable`;
41
+ useAPI( // No TableWrapper here, so we can useAPI from Foreman
42
+ 'get',
43
+ api.getApiUrl(pathsUrl),
44
+ ENV_PATH_OPTIONS,
45
+ );
46
+ const selectedContentViewId = results?.find(cv => cv.name === selectedContentView)?.id;
47
+
48
+ const handleModalClose = () => {
49
+ setCVSelectOpen(false);
50
+ setSelectedContentView(null);
51
+ setSelectedLifecycleEnv([]);
52
+ closeModal();
53
+ };
54
+
55
+ const selectedEnv = selectedLifecycleEnv?.[0];
56
+ const selectedEnvId = selectedEnv?.id;
57
+
58
+ const handleCVSelect = (event, selection) => {
59
+ setSelectedContentView(selection);
60
+ setCVSelectOpen(false);
61
+ };
62
+
63
+ const handleEnvSelect = (selection) => {
64
+ dispatch(getContentViews({
65
+ environment_id: selection[0].id,
66
+ include_default: true,
67
+ full_result: true,
68
+ order: 'default DESC', // show Default Organization View first
69
+ }, '_FOR_DEFAULT_ENV'));
70
+ setSelectedContentView(null);
71
+ setSelectedLifecycleEnv(selection);
72
+ };
73
+ const { results: contentViewsInEnv = [] } = contentViewsInEnvResponse;
74
+ const canSave = !!(selectedContentView && selectedLifecycleEnv.length);
75
+
76
+ const handleSave = () => {
77
+ const requestBody = {
78
+ content_view_id: selectedContentViewId,
79
+ environment_id: selectedEnvId,
80
+ organization_id: orgId,
81
+ included: {
82
+ search: fetchBulkParams(),
83
+ },
84
+ };
85
+ dispatch(bulkUpdateHostContentViewAndEnvironment(
86
+ requestBody, fetchBulkParams(),
87
+ handleModalClose, handleModalClose,
88
+ ));
89
+ };
90
+
91
+ const cvPlaceholderText = getCVPlaceholderText({
92
+ environments: selectedLifecycleEnv,
93
+ cvSelectOptions: contentViewsInEnv,
94
+ contentViewsStatus: contentViewsInEnvStatus,
95
+ });
96
+
97
+ const stillLoading =
98
+ (contentViewsInEnvStatus === STATUS.PENDING || hostUpdateStatus === STATUS.PENDING);
99
+ const noContentViewsAvailable =
100
+ (contentViewsInEnv.length === 0 || selectedLifecycleEnv.length === 0);
101
+
102
+ const modalActions = ([
103
+ <Button
104
+ key="add"
105
+ ouiaId="bulk-change-host-cv-modal-add-button"
106
+ variant="primary"
107
+ onClick={handleSave}
108
+ isDisabled={!canSave || hostUpdateStatus === STATUS.PENDING}
109
+ isLoading={hostUpdateStatus === STATUS.PENDING}
110
+ >
111
+ {__('Save')}
112
+ </Button>,
113
+ <Button key="cancel" ouiaId="change-host-cv-modal-cancel-button" variant="link" onClick={handleModalClose}>
114
+ Cancel
115
+ </Button>,
116
+ ]);
117
+ return (
118
+ <Modal
119
+ isOpen={isOpen}
120
+ onClose={handleModalClose}
121
+ onEscapePress={handleModalClose}
122
+ title={__('Edit content view environments')}
123
+ width="50%"
124
+ position="top"
125
+ actions={modalActions}
126
+ id="bulk-change-host-cv-modal"
127
+ key="bulk-change-host-cv-modal"
128
+ ouiaId="bulk-change-host-cv-modal"
129
+ >
130
+ <TextContent>
131
+ <Text
132
+ ouiaId="bulk-change-cv-options-description"
133
+ >
134
+ <FormattedMessage
135
+ defaultMessage={__('This will update the content view environments for {hosts}.')}
136
+ values={{
137
+ hosts: (
138
+ <strong>
139
+ <FormattedMessage
140
+ defaultMessage="{count, plural, one {# {singular}} other {# {plural}}}"
141
+ values={{
142
+ count: selectedCount,
143
+ singular: __('selected host'),
144
+ plural: __('selected hosts'),
145
+ }}
146
+ id="ccs-options-i18n"
147
+ />
148
+ </strong>
149
+ ),
150
+ }}
151
+ id="bulk-change-cv-options-description-i18n"
152
+ />
153
+ </Text>
154
+ </TextContent>
155
+ {contentViewsInEnvStatus === STATUS.RESOLVED &&
156
+ !!selectedLifecycleEnv.length && contentViewsInEnv.length === 0 &&
157
+ <Alert
158
+ ouiaId="no-cv-alert"
159
+ variant="warning"
160
+ isInline
161
+ title={__('No content views available for the selected environment')}
162
+ style={{ marginBottom: '1rem' }}
163
+ >
164
+ <a href="/content_views">{__('View the Content Views page')}</a>
165
+ {__(' to manage and promote content views, or select a different environment.')}
166
+ </Alert>
167
+ }
168
+ <EnvironmentPaths
169
+ userCheckedItems={selectedLifecycleEnv}
170
+ setUserCheckedItems={handleEnvSelect}
171
+ publishing={false}
172
+ multiSelect={false}
173
+ headerText={__('Select environment')}
174
+ isDisabled={hostUpdateStatus === STATUS.PENDING}
175
+ />
176
+ <ContentViewSelect
177
+ selections={selectedContentView}
178
+ onClear={() => setSelectedContentView(null)}
179
+ onSelect={handleCVSelect}
180
+ isOpen={cvSelectOpen}
181
+ isDisabled={stillLoading || noContentViewsAvailable}
182
+ onToggle={isExpanded => setCVSelectOpen(isExpanded)}
183
+ placeholderText={cvPlaceholderText}
184
+ >
185
+ {(contentViewsInEnv.length !== 0 && selectedLifecycleEnv.length !== 0) &&
186
+ contentViewsInEnv?.map(cv => (
187
+ <ContentViewSelectOption
188
+ key={cv.id}
189
+ value={cv.name}
190
+ cv={cv}
191
+ env={selectedLifecycleEnv[0]}
192
+ />
193
+ ))}
194
+ </ContentViewSelect>
195
+ <hr />
196
+ <TextContent>
197
+ <Text component={TextVariants.small} ouiaId="profile-upload-reminder-text">
198
+ {__('Errata and package information will be updated at the next host check-in or package action.')}
199
+ </Text>
200
+ </TextContent>
201
+ <hr />
202
+ </Modal>
203
+ );
204
+ };
205
+
206
+ BulkChangeHostCVModal.propTypes = {
207
+ isOpen: PropTypes.bool,
208
+ closeModal: PropTypes.func,
209
+ selectedCount: PropTypes.number.isRequired,
210
+ orgId: PropTypes.number.isRequired,
211
+ fetchBulkParams: PropTypes.func.isRequired,
212
+ };
213
+
214
+ BulkChangeHostCVModal.defaultProps = {
215
+ isOpen: false,
216
+ closeModal: () => {},
217
+ };
218
+
219
+
220
+ export default BulkChangeHostCVModal;
@@ -0,0 +1,23 @@
1
+ import { translate as __ } from 'foremanReact/common/I18n';
2
+ import { API_OPERATIONS, put } from 'foremanReact/redux/API';
3
+ import { errorToast, renderTaskStartedToast } from '../../../../../scenes/Tasks/helpers';
4
+ import { foremanApi } from '../../../../../services/api';
5
+ import HOST_CV_AND_ENV_KEY from '../../../HostDetails/Cards/ContentViewDetailsCard/HostContentViewConstants';
6
+
7
+ export const bulkUpdateHostContentViewAndEnvironment =
8
+ (params, bulkParams, handleSuccess, handleError) => put({
9
+ type: API_OPERATIONS.PUT,
10
+ key: HOST_CV_AND_ENV_KEY,
11
+ url: foremanApi.getApiUrl('/hosts/bulk/environment_content_view'),
12
+ ...bulkParams,
13
+ successToast: () => __('Host content view environments updating.'),
14
+ handleSuccess: (response) => {
15
+ if (handleSuccess) handleSuccess(response);
16
+ return renderTaskStartedToast(response.data);
17
+ },
18
+ handleError,
19
+ errorToast,
20
+ params,
21
+ });
22
+
23
+ export default bulkUpdateHostContentViewAndEnvironment;
@@ -0,0 +1,25 @@
1
+ import React, { useContext } from 'react';
2
+ import { useForemanOrganization } from 'foremanReact/Root/Context/ForemanContext';
3
+ import { ForemanActionsBarContext } from 'foremanReact/components/HostDetails/ActionsBar';
4
+ import { useForemanModal } from 'foremanReact/components/ForemanModal/ForemanModalHooks';
5
+ import BulkChangeHostCVModal from './BulkChangeHostCVModal';
6
+
7
+ const BulkChangeHostCVModalScene = () => {
8
+ const org = useForemanOrganization();
9
+ const { selectedCount, fetchBulkParams } = useContext(ForemanActionsBarContext);
10
+ const { modalOpen, setModalClosed } = useForemanModal({ id: 'bulk-change-cv-modal' });
11
+
12
+ return (
13
+ <BulkChangeHostCVModal
14
+ key="bulk-change-cv-modal"
15
+ selectedCount={selectedCount}
16
+ fetchBulkParams={fetchBulkParams}
17
+ isOpen={modalOpen}
18
+ closeModal={setModalClosed}
19
+ orgId={org?.id}
20
+ />
21
+
22
+ );
23
+ };
24
+
25
+ export default BulkChangeHostCVModalScene;
@@ -0,0 +1,133 @@
1
+ import React from 'react';
2
+ import { renderWithRedux, patientlyWaitFor, act } from 'react-testing-lib-wrapper';
3
+ import userEvent from '@testing-library/user-event';
4
+ import BulkChangeHostCVModal from '../BulkChangeHostCVModal/BulkChangeHostCVModal.js';
5
+ import mockEnvPaths from '../../../HostDetails/Cards/ContentViewDetailsCard/__tests__/envPaths.fixtures.json';
6
+ import mockContentViews from '../../../HostDetails/Cards/ContentViewDetailsCard/__tests__/contentViews.fixtures.json';
7
+ import HOST_CV_AND_ENV_KEY from '../../../HostDetails/Cards/ContentViewDetailsCard/HostContentViewConstants';
8
+ import { assertNockRequest, nockInstance } from '../../../../../test-utils/nockWrapper';
9
+ import katelloApi from '../../../../../services/api';
10
+
11
+ const contentViews = katelloApi.getApiUrl('/content_views');
12
+ const renderOptions = () => ({
13
+ apiNamespace: HOST_CV_AND_ENV_KEY,
14
+ initialState: {
15
+ API: {
16
+ HOST_DETAILS: {
17
+ response: {
18
+ id: 1,
19
+ name: 'test-host',
20
+ content_facet_attributes: {
21
+ content_view_id: 1,
22
+ lifecycle_environment_id: 1,
23
+ },
24
+ organization_id: 1,
25
+ },
26
+ status: 'RESOLVED',
27
+ },
28
+ ENVIRONMENT_PATHS: {
29
+ response: mockEnvPaths,
30
+ status: 'RESOLVED',
31
+ },
32
+ },
33
+ },
34
+ });
35
+
36
+ let firstEnvPath;
37
+ let firstCV;
38
+ let secondCV;
39
+ let firstEnv;
40
+
41
+ const cvQuery = {
42
+ organization_id: 1,
43
+ include_permissions: true,
44
+ include_default: true,
45
+ environment_id: 1,
46
+ full_result: true,
47
+ order: 'default DESC',
48
+ };
49
+
50
+ beforeEach(() => {
51
+ const { results } = mockEnvPaths;
52
+ [firstEnvPath] = results;
53
+ const { environments: envResults } = firstEnvPath;
54
+ [firstEnv] = envResults;
55
+ const { results: cvResults } = mockContentViews;
56
+ [firstCV, secondCV] = cvResults;
57
+ });
58
+
59
+ jest.mock('foremanReact/common/hooks/API/APIHooks', () => ({
60
+ useAPI: jest.fn(),
61
+ }));
62
+
63
+ test('Displays environment paths', async (done) => {
64
+ const jsx = (
65
+ <BulkChangeHostCVModal
66
+ isOpen
67
+ closeModal={jest.fn()}
68
+ selectedCount={1}
69
+ fetchBulkParams={() => 'id ^ 1'}
70
+ orgId={1}
71
+ />
72
+ );
73
+ const { getAllByText }
74
+ = renderWithRedux(jsx, renderOptions());
75
+
76
+ await patientlyWaitFor(() =>
77
+ expect(getAllByText(firstEnv.name)[0]).toBeInTheDocument());
78
+ done();
79
+ });
80
+
81
+ test('Select an env > call CV API > select a CV > Save button is enabled', async (done) => {
82
+ const contentViewsScope = nockInstance
83
+ .get(contentViews)
84
+ .query(cvQuery)
85
+ .reply(200, mockContentViews);
86
+
87
+ const jsx = (
88
+ <BulkChangeHostCVModal
89
+ isOpen
90
+ closeModal={jest.fn()}
91
+ selectedCount={1}
92
+ fetchBulkParams={() => 'id ^ 1'}
93
+ orgId={1}
94
+ />
95
+ );
96
+ const {
97
+ getAllByText, getByText,
98
+ findByPlaceholderText, getAllByRole,
99
+ } = renderWithRedux(jsx, renderOptions());
100
+
101
+ await patientlyWaitFor(() => {
102
+ const envLabel = getAllByText(firstEnv.name)[0];
103
+ expect(envLabel).toBeInTheDocument();
104
+ });
105
+
106
+ const envRadio = getAllByRole('radio', { name: firstEnv.name })[0];
107
+ expect(envRadio).toBeInTheDocument();
108
+
109
+ await act(async () => {
110
+ userEvent.click(envRadio); // Select the Library environment
111
+
112
+ const cvDropdown = await findByPlaceholderText('Select a content view');
113
+ expect(cvDropdown).toBeInTheDocument();
114
+
115
+ userEvent.click(cvDropdown); // Open the CV dropdown
116
+
117
+
118
+ [firstCV, secondCV].forEach((cv) => {
119
+ expect(getByText(cv.name)).toBeInTheDocument(); // the content view names should be showing
120
+ });
121
+
122
+
123
+ userEvent.click(getByText(secondCV.name)); // Select the second content view
124
+ });
125
+
126
+ // find the Save button and assert that it is enabled
127
+ const saveButton = getAllByRole('button', { name: 'Save' })[0];
128
+ expect(saveButton).toBeInTheDocument();
129
+ expect(saveButton).toHaveAttribute('aria-disabled', 'false');
130
+
131
+ assertNockRequest(contentViewsScope, done);
132
+ act(done);
133
+ });