katello 4.18.1 → 4.19.0.rc2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of katello might be problematic. Click here for more details.

Files changed (388) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/javascripts/katello/locale/bn/katello.js +379 -109
  3. data/app/assets/javascripts/katello/locale/bn_IN/katello.js +379 -109
  4. data/app/assets/javascripts/katello/locale/ca/katello.js +379 -109
  5. data/app/assets/javascripts/katello/locale/cs/katello.js +380 -110
  6. data/app/assets/javascripts/katello/locale/cs_CZ/katello.js +382 -112
  7. data/app/assets/javascripts/katello/locale/de/katello.js +385 -115
  8. data/app/assets/javascripts/katello/locale/de_AT/katello.js +379 -109
  9. data/app/assets/javascripts/katello/locale/de_DE/katello.js +379 -109
  10. data/app/assets/javascripts/katello/locale/el/katello.js +380 -110
  11. data/app/assets/javascripts/katello/locale/en/katello.js +380 -110
  12. data/app/assets/javascripts/katello/locale/en_GB/katello.js +379 -109
  13. data/app/assets/javascripts/katello/locale/en_US/katello.js +379 -109
  14. data/app/assets/javascripts/katello/locale/es/katello.js +383 -113
  15. data/app/assets/javascripts/katello/locale/et_EE/katello.js +379 -109
  16. data/app/assets/javascripts/katello/locale/fr/katello.js +388 -118
  17. data/app/assets/javascripts/katello/locale/gl/katello.js +379 -109
  18. data/app/assets/javascripts/katello/locale/gu/katello.js +379 -109
  19. data/app/assets/javascripts/katello/locale/he_IL/katello.js +379 -109
  20. data/app/assets/javascripts/katello/locale/hi/katello.js +379 -109
  21. data/app/assets/javascripts/katello/locale/id/katello.js +379 -109
  22. data/app/assets/javascripts/katello/locale/it/katello.js +379 -109
  23. data/app/assets/javascripts/katello/locale/ja/katello.js +388 -118
  24. data/app/assets/javascripts/katello/locale/ka/katello.js +385 -115
  25. data/app/assets/javascripts/katello/locale/kn/katello.js +379 -109
  26. data/app/assets/javascripts/katello/locale/ko/katello.js +388 -118
  27. data/app/assets/javascripts/katello/locale/ml_IN/katello.js +379 -109
  28. data/app/assets/javascripts/katello/locale/mr/katello.js +379 -109
  29. data/app/assets/javascripts/katello/locale/nl_NL/katello.js +379 -109
  30. data/app/assets/javascripts/katello/locale/or/katello.js +379 -109
  31. data/app/assets/javascripts/katello/locale/pa/katello.js +379 -109
  32. data/app/assets/javascripts/katello/locale/pl/katello.js +379 -109
  33. data/app/assets/javascripts/katello/locale/pl_PL/katello.js +379 -109
  34. data/app/assets/javascripts/katello/locale/pt/katello.js +379 -109
  35. data/app/assets/javascripts/katello/locale/pt_BR/katello.js +383 -113
  36. data/app/assets/javascripts/katello/locale/ro/katello.js +379 -109
  37. data/app/assets/javascripts/katello/locale/ro_RO/katello.js +379 -109
  38. data/app/assets/javascripts/katello/locale/ru/katello.js +381 -111
  39. data/app/assets/javascripts/katello/locale/sl/katello.js +379 -109
  40. data/app/assets/javascripts/katello/locale/sv_SE/katello.js +379 -109
  41. data/app/assets/javascripts/katello/locale/ta/katello.js +379 -109
  42. data/app/assets/javascripts/katello/locale/ta_IN/katello.js +379 -109
  43. data/app/assets/javascripts/katello/locale/te/katello.js +379 -109
  44. data/app/assets/javascripts/katello/locale/tr/katello.js +379 -109
  45. data/app/assets/javascripts/katello/locale/vi/katello.js +379 -109
  46. data/app/assets/javascripts/katello/locale/vi_VN/katello.js +379 -109
  47. data/app/assets/javascripts/katello/locale/zh/katello.js +379 -109
  48. data/app/assets/javascripts/katello/locale/zh_CN/katello.js +388 -118
  49. data/app/assets/javascripts/katello/locale/zh_TW/katello.js +381 -111
  50. data/app/controllers/katello/api/registry/registry_proxies_controller.rb +41 -12
  51. data/app/controllers/katello/api/rhsm/candlepin_proxies_controller.rb +1 -1
  52. data/app/controllers/katello/api/v2/activation_keys_controller.rb +3 -65
  53. data/app/controllers/katello/api/v2/content_view_filter_rules_controller.rb +1 -1
  54. data/app/controllers/katello/api/v2/content_views_controller.rb +18 -3
  55. data/app/controllers/katello/api/v2/debs_controller.rb +21 -11
  56. data/app/controllers/katello/api/v2/docker_tags_controller.rb +7 -0
  57. data/app/controllers/katello/api/v2/flatpak_remote_repositories_controller.rb +21 -19
  58. data/app/controllers/katello/api/v2/host_debs_controller.rb +16 -1
  59. data/app/controllers/katello/api/v2/host_subscriptions_controller.rb +3 -60
  60. data/app/controllers/katello/api/v2/hosts_bulk_actions_controller.rb +10 -53
  61. data/app/controllers/katello/api/v2/repositories_controller.rb +0 -1
  62. data/app/controllers/katello/concerns/api/v2/hosts_controller_extensions.rb +10 -0
  63. data/app/controllers/katello/concerns/organizations_controller_extensions.rb +3 -0
  64. data/app/lib/actions/candlepin/activation_key/create.rb +0 -2
  65. data/app/lib/actions/candlepin/activation_key/update.rb +0 -2
  66. data/app/lib/actions/candlepin/product/content_create.rb +3 -5
  67. data/app/lib/actions/candlepin/product/content_update.rb +2 -3
  68. data/app/lib/actions/helpers/rolling_cv_repos.rb +1 -1
  69. data/app/lib/actions/katello/activation_key/create.rb +0 -1
  70. data/app/lib/actions/katello/activation_key/update.rb +0 -2
  71. data/app/lib/actions/katello/capsule_content/sync_capsule.rb +1 -6
  72. data/app/lib/actions/katello/content_credential/update.rb +1 -1
  73. data/app/lib/actions/katello/content_view/add_rolling_repo_clone.rb +18 -24
  74. data/app/lib/actions/katello/content_view/create.rb +9 -4
  75. data/app/lib/actions/katello/content_view/refresh_rolling_repo.rb +6 -1
  76. data/app/lib/actions/katello/content_view/remove_rolling_repo_clone.rb +16 -11
  77. data/app/lib/actions/katello/content_view/update.rb +34 -7
  78. data/app/lib/actions/katello/product/content_create.rb +2 -2
  79. data/app/lib/actions/katello/product/content_destroy.rb +1 -1
  80. data/app/lib/actions/katello/repository/check_matching_content.rb +1 -1
  81. data/app/lib/actions/katello/repository/clone_contents.rb +1 -1
  82. data/app/lib/actions/katello/repository/create.rb +1 -1
  83. data/app/lib/actions/katello/repository/destroy.rb +4 -4
  84. data/app/lib/actions/katello/repository/finish_upload.rb +1 -1
  85. data/app/lib/actions/katello/repository/sync.rb +1 -1
  86. data/app/lib/actions/pulp3/orchestration/repository/copy_all_units.rb +2 -2
  87. data/app/lib/actions/pulp3/orchestration/repository/generate_metadata.rb +1 -1
  88. data/app/lib/actions/pulp3/orchestration/repository/multi_copy_all_units.rb +1 -1
  89. data/app/lib/actions/pulp3/repository/save_publication.rb +3 -1
  90. data/app/lib/actions/pulp3/repository/save_version.rb +45 -24
  91. data/app/lib/actions/pulp3/repository/save_versions.rb +2 -1
  92. data/app/lib/katello/resources/candlepin/activation_key.rb +3 -4
  93. data/app/lib/katello/resources/candlepin/upstream_job.rb +9 -1
  94. data/app/lib/katello/resources/candlepin.rb +4 -0
  95. data/app/models/katello/authorization/repository.rb +17 -4
  96. data/app/models/katello/concerns/subscription_facet_host_extensions.rb +0 -7
  97. data/app/models/katello/content_view_deb_filter.rb +10 -0
  98. data/app/models/katello/content_view_deb_filter_rule.rb +7 -0
  99. data/app/models/katello/deb.rb +10 -10
  100. data/app/models/katello/erratum.rb +1 -1
  101. data/app/models/katello/glue/provider.rb +14 -3
  102. data/app/models/katello/host/content_facet.rb +1 -1
  103. data/app/models/katello/host/subscription_facet.rb +1 -7
  104. data/app/models/katello/product_content.rb +2 -2
  105. data/app/models/katello/repository.rb +4 -23
  106. data/app/models/katello/root_repository.rb +2 -5
  107. data/app/services/katello/candlepin/event_handler.rb +0 -33
  108. data/app/services/katello/candlepin/message_handler.rb +0 -41
  109. data/app/services/katello/content_unit_indexer.rb +59 -13
  110. data/app/services/katello/product_content_finder.rb +5 -4
  111. data/app/services/katello/pulp3/alternate_content_source.rb +2 -2
  112. data/app/services/katello/pulp3/ansible_collection.rb +1 -0
  113. data/app/services/katello/pulp3/api/content_guard.rb +5 -5
  114. data/app/services/katello/pulp3/api/core.rb +10 -0
  115. data/app/services/katello/pulp3/deb.rb +1 -0
  116. data/app/services/katello/pulp3/docker_manifest.rb +1 -0
  117. data/app/services/katello/pulp3/docker_manifest_list.rb +1 -0
  118. data/app/services/katello/pulp3/docker_tag.rb +1 -0
  119. data/app/services/katello/pulp3/file_unit.rb +1 -0
  120. data/app/services/katello/pulp3/generic_content_unit.rb +1 -0
  121. data/app/services/katello/pulp3/module_stream.rb +1 -0
  122. data/app/services/katello/pulp3/package_group.rb +1 -0
  123. data/app/services/katello/pulp3/repository/apt.rb +41 -16
  124. data/app/services/katello/pulp3/repository.rb +59 -10
  125. data/app/services/katello/pulp3/rpm.rb +3 -2
  126. data/app/services/katello/pulp3/srpm.rb +3 -2
  127. data/app/services/katello/pulp3/task_group.rb +1 -1
  128. data/app/services/katello/registration_manager.rb +19 -17
  129. data/app/services/katello/repository_type_manager.rb +7 -5
  130. data/app/services/katello/smart_proxy_helper.rb +1 -6
  131. data/app/views/foreman/job_templates/upload_profile.erb +5 -0
  132. data/app/views/katello/api/v2/activation_keys/base.json.rabl +1 -1
  133. data/app/views/katello/api/v2/content_views/base.json.rabl +1 -0
  134. data/app/views/katello/api/v2/debs/thindex.json.rabl +6 -0
  135. data/app/views/katello/api/v2/docker_tags/_base.json.rabl +32 -0
  136. data/app/views/katello/api/v2/docker_tags/show.json.rabl +0 -5
  137. data/app/views/katello/api/v2/flatpak_remotes/index.json.rabl +6 -0
  138. data/app/views/katello/api/v2/host_debs/installed_debs.json.rabl +6 -0
  139. data/app/views/katello/api/v2/hosts_bulk_actions/applicable_errata.json.rabl +1 -1
  140. data/app/views/katello/api/v2/hosts_bulk_actions/applicable_erratum.json.rabl +9 -0
  141. data/app/views/katello/api/v2/hosts_bulk_actions/installable_errata.json.rabl +1 -1
  142. data/app/views/katello/api/v2/hosts_bulk_actions/{erratum.json.rabl → installable_erratum.json.rabl} +3 -3
  143. data/app/views/katello/api/v2/subscription_facet/base.json.rabl +1 -1
  144. data/config/initializers/monkeys.rb +1 -0
  145. data/config/routes/api/v2.rb +2 -2
  146. data/config/routes/overrides.rb +2 -7
  147. data/config/routes.rb +2 -0
  148. data/db/migrate/20211019192121_create_cdn_configuration.katello.rb +1 -1
  149. data/db/migrate/20250912000000_add_pulp_prn_fields.rb +73 -0
  150. data/db/migrate/20250912000001_populate_pulp_prn_fields.rb +403 -0
  151. data/db/migrate/20251009142516_remove_auto_attach_from_activation_keys.rb +5 -0
  152. data/db/migrate/20251009142517_remove_autoheal_from_subscription_facets.rb +5 -0
  153. data/db/seeds.d/111-upgrade_tasks.rb +2 -0
  154. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/i18n/locale/bn.po +10 -0
  155. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/i18n/locale/bn_IN.po +10 -0
  156. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/i18n/locale/ca.po +10 -0
  157. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/i18n/locale/cs_CZ.po +10 -0
  158. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/i18n/locale/de.po +10 -0
  159. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/i18n/locale/de_AT.po +10 -0
  160. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/i18n/locale/de_DE.po +10 -0
  161. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/i18n/locale/el.po +12 -2
  162. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/i18n/locale/en_GB.po +10 -0
  163. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/i18n/locale/en_US.po +10 -0
  164. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/i18n/locale/es.po +10 -0
  165. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/i18n/locale/et_EE.po +10 -0
  166. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/i18n/locale/fr.po +10 -0
  167. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/i18n/locale/gl.po +10 -0
  168. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/i18n/locale/gu.po +10 -0
  169. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/i18n/locale/he_IL.po +10 -0
  170. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/i18n/locale/hi.po +10 -0
  171. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/i18n/locale/id.po +10 -0
  172. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/i18n/locale/it.po +10 -0
  173. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/i18n/locale/ja.po +10 -0
  174. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/i18n/locale/ka.po +10 -0
  175. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/i18n/locale/kn.po +10 -0
  176. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/i18n/locale/ko.po +10 -0
  177. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/i18n/locale/ml_IN.po +10 -0
  178. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/i18n/locale/mr.po +10 -0
  179. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/i18n/locale/nl_NL.po +10 -0
  180. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/i18n/locale/or.po +10 -0
  181. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/i18n/locale/pa.po +10 -0
  182. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/i18n/locale/pl.po +10 -0
  183. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/i18n/locale/pl_PL.po +10 -0
  184. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/i18n/locale/pt.po +10 -0
  185. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/i18n/locale/pt_BR.po +10 -0
  186. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/i18n/locale/ro.po +10 -0
  187. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/i18n/locale/ro_RO.po +10 -0
  188. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/i18n/locale/ru.po +10 -0
  189. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/i18n/locale/sl.po +10 -0
  190. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/i18n/locale/sv_SE.po +10 -0
  191. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/i18n/locale/ta.po +10 -0
  192. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/i18n/locale/ta_IN.po +10 -0
  193. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/i18n/locale/te.po +10 -0
  194. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/i18n/locale/tr.po +10 -0
  195. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/i18n/locale/vi.po +10 -0
  196. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/i18n/locale/vi_VN.po +10 -0
  197. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/i18n/locale/zh.po +10 -0
  198. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/i18n/locale/zh_CN.po +10 -0
  199. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/i18n/locale/zh_TW.po +10 -0
  200. data/lib/katello/permission_creator.rb +2 -2
  201. data/lib/katello/permissions/host_permissions.rb +0 -6
  202. data/lib/katello/plugin.rb +17 -8
  203. data/lib/katello/tasks/jenkins.rake +1 -1
  204. data/lib/katello/tasks/upgrades/4.19/enable_structured_apt_for_deb.rake +87 -0
  205. data/lib/katello/tasks/upgrades/4.19/populate_repository_version_prns.rake +32 -0
  206. data/lib/katello/version.rb +1 -1
  207. data/lib/monkeys/fix_rpm_repository_gpgcheck.rb +38 -0
  208. data/locale/action_names.rb +0 -6
  209. data/locale/bn/LC_MESSAGES/katello.mo +0 -0
  210. data/locale/bn/katello.po +379 -109
  211. data/locale/bn_IN/LC_MESSAGES/katello.mo +0 -0
  212. data/locale/bn_IN/katello.po +379 -109
  213. data/locale/ca/LC_MESSAGES/katello.mo +0 -0
  214. data/locale/ca/katello.po +379 -109
  215. data/locale/cs/LC_MESSAGES/katello.mo +0 -0
  216. data/locale/cs/katello.po +380 -111
  217. data/locale/cs_CZ/LC_MESSAGES/katello.mo +0 -0
  218. data/locale/cs_CZ/katello.po +382 -112
  219. data/locale/de/LC_MESSAGES/katello.mo +0 -0
  220. data/locale/de/katello.po +386 -116
  221. data/locale/de_AT/LC_MESSAGES/katello.mo +0 -0
  222. data/locale/de_AT/katello.po +379 -109
  223. data/locale/de_DE/LC_MESSAGES/katello.mo +0 -0
  224. data/locale/de_DE/katello.po +379 -109
  225. data/locale/el/LC_MESSAGES/katello.mo +0 -0
  226. data/locale/el/katello.po +381 -111
  227. data/locale/en/LC_MESSAGES/katello.mo +0 -0
  228. data/locale/en/katello.po +380 -111
  229. data/locale/en_GB/LC_MESSAGES/katello.mo +0 -0
  230. data/locale/en_GB/katello.po +379 -109
  231. data/locale/en_US/LC_MESSAGES/katello.mo +0 -0
  232. data/locale/en_US/katello.po +379 -109
  233. data/locale/es/LC_MESSAGES/katello.mo +0 -0
  234. data/locale/es/katello.po +383 -113
  235. data/locale/et_EE/LC_MESSAGES/katello.mo +0 -0
  236. data/locale/et_EE/katello.po +379 -109
  237. data/locale/fr/LC_MESSAGES/katello.mo +0 -0
  238. data/locale/fr/katello.po +389 -119
  239. data/locale/gl/LC_MESSAGES/katello.mo +0 -0
  240. data/locale/gl/katello.po +379 -109
  241. data/locale/gu/LC_MESSAGES/katello.mo +0 -0
  242. data/locale/gu/katello.po +379 -109
  243. data/locale/he_IL/LC_MESSAGES/katello.mo +0 -0
  244. data/locale/he_IL/katello.po +379 -109
  245. data/locale/hi/LC_MESSAGES/katello.mo +0 -0
  246. data/locale/hi/katello.po +379 -109
  247. data/locale/id/LC_MESSAGES/katello.mo +0 -0
  248. data/locale/id/katello.po +379 -109
  249. data/locale/it/LC_MESSAGES/katello.mo +0 -0
  250. data/locale/it/katello.po +379 -109
  251. data/locale/ja/LC_MESSAGES/katello.mo +0 -0
  252. data/locale/ja/katello.po +390 -119
  253. data/locale/ka/LC_MESSAGES/katello.mo +0 -0
  254. data/locale/ka/katello.po +385 -115
  255. data/locale/katello.pot +1273 -831
  256. data/locale/kn/LC_MESSAGES/katello.mo +0 -0
  257. data/locale/kn/katello.po +379 -109
  258. data/locale/ko/LC_MESSAGES/katello.mo +0 -0
  259. data/locale/ko/katello.po +389 -119
  260. data/locale/ml_IN/LC_MESSAGES/katello.mo +0 -0
  261. data/locale/ml_IN/katello.po +379 -109
  262. data/locale/mr/LC_MESSAGES/katello.mo +0 -0
  263. data/locale/mr/katello.po +379 -109
  264. data/locale/nl_NL/LC_MESSAGES/katello.mo +0 -0
  265. data/locale/nl_NL/katello.po +379 -109
  266. data/locale/or/LC_MESSAGES/katello.mo +0 -0
  267. data/locale/or/katello.po +379 -109
  268. data/locale/pa/LC_MESSAGES/katello.mo +0 -0
  269. data/locale/pa/katello.po +379 -109
  270. data/locale/pl/LC_MESSAGES/katello.mo +0 -0
  271. data/locale/pl/katello.po +379 -109
  272. data/locale/pl_PL/LC_MESSAGES/katello.mo +0 -0
  273. data/locale/pl_PL/katello.po +379 -109
  274. data/locale/pt/LC_MESSAGES/katello.mo +0 -0
  275. data/locale/pt/katello.po +379 -109
  276. data/locale/pt_BR/LC_MESSAGES/katello.mo +0 -0
  277. data/locale/pt_BR/katello.po +383 -113
  278. data/locale/ro/LC_MESSAGES/katello.mo +0 -0
  279. data/locale/ro/katello.po +379 -109
  280. data/locale/ro_RO/LC_MESSAGES/katello.mo +0 -0
  281. data/locale/ro_RO/katello.po +379 -109
  282. data/locale/ru/LC_MESSAGES/katello.mo +0 -0
  283. data/locale/ru/katello.po +381 -111
  284. data/locale/sl/LC_MESSAGES/katello.mo +0 -0
  285. data/locale/sl/katello.po +379 -109
  286. data/locale/sv_SE/LC_MESSAGES/katello.mo +0 -0
  287. data/locale/sv_SE/katello.po +379 -109
  288. data/locale/ta/LC_MESSAGES/katello.mo +0 -0
  289. data/locale/ta/katello.po +379 -109
  290. data/locale/ta_IN/LC_MESSAGES/katello.mo +0 -0
  291. data/locale/ta_IN/katello.po +379 -109
  292. data/locale/te/LC_MESSAGES/katello.mo +0 -0
  293. data/locale/te/katello.po +379 -109
  294. data/locale/tr/LC_MESSAGES/katello.mo +0 -0
  295. data/locale/tr/katello.po +379 -109
  296. data/locale/vi/LC_MESSAGES/katello.mo +0 -0
  297. data/locale/vi/katello.po +379 -109
  298. data/locale/vi_VN/LC_MESSAGES/katello.mo +0 -0
  299. data/locale/vi_VN/katello.po +379 -109
  300. data/locale/zh/LC_MESSAGES/katello.mo +0 -0
  301. data/locale/zh/katello.po +379 -109
  302. data/locale/zh_CN/LC_MESSAGES/katello.mo +0 -0
  303. data/locale/zh_CN/katello.po +389 -119
  304. data/locale/zh_TW/LC_MESSAGES/katello.mo +0 -0
  305. data/locale/zh_TW/katello.po +381 -111
  306. data/webpack/ForemanColumnExtensions/index.js +46 -2
  307. data/webpack/components/Content/Details/__tests__/__snapshots__/ContentDetails.test.js.snap +2 -2
  308. data/webpack/components/extensions/HostDetails/Cards/SystemPurposeCard/SystemPurposeEditModal.js +0 -2
  309. data/webpack/components/extensions/HostDetails/Cards/SystemPurposeCard/__tests__/SystemPurposeEditModal.test.js +0 -2
  310. data/webpack/components/extensions/Hosts/ActionsBar/index.js +1 -0
  311. data/webpack/components/extensions/Hosts/BulkActions/BulkActionsConstants.js +7 -0
  312. data/webpack/components/extensions/Hosts/BulkActions/BulkChangeHostCollectionsModal/BulkChangeHostCollectionsModal.js +388 -0
  313. data/webpack/components/extensions/Hosts/BulkActions/BulkChangeHostCollectionsModal/__tests__/BulkChangeHostCollectionsModal.test.js +640 -0
  314. data/webpack/components/extensions/Hosts/BulkActions/BulkChangeHostCollectionsModal/actions.js +28 -0
  315. data/webpack/components/extensions/Hosts/BulkActions/BulkChangeHostCollectionsModal/index.js +71 -0
  316. data/webpack/components/extensions/Hosts/BulkActions/BulkErrataWizard/02_BulkErrataTable.js +1 -1
  317. data/webpack/components/extensions/Hosts/BulkActions/BulkErrataWizard/BulkErrataWizard.js +1 -1
  318. data/webpack/components/extensions/Hosts/BulkActions/BulkPackagesWizard/02_BulkPackagesTable.js +11 -4
  319. data/webpack/components/extensions/Hosts/BulkActions/BulkPackagesWizard/BulkPackagesWizard.js +51 -24
  320. data/webpack/components/extensions/Hosts/BulkActions/BulkRepositorySetsWizard/01_BulkRepositorySetsTable.js +1 -1
  321. data/webpack/components/extensions/Hosts/BulkActions/HostReview.js +8 -1
  322. data/webpack/containers/Application/config.js +11 -1
  323. data/webpack/global_index.js +3 -0
  324. data/webpack/scenes/{BootedContainerImages → ContainerImages/Booted}/BootedContainerImagesConstants.js +1 -1
  325. data/webpack/scenes/{BootedContainerImages → ContainerImages/Booted}/BootedContainerImagesPage.js +7 -43
  326. data/webpack/scenes/{BootedContainerImages → ContainerImages/Booted}/__tests__/bootedContainerImagesPage.test.js +1 -1
  327. data/webpack/scenes/ContainerImages/ContainerImagesPage.js +86 -0
  328. data/webpack/scenes/ContainerImages/LabelsAnnotationsModal.js +105 -0
  329. data/webpack/scenes/ContainerImages/Synced/Details/ManifestDetails.js +218 -0
  330. data/webpack/scenes/ContainerImages/Synced/Details/ManifestDetailsActions.js +15 -0
  331. data/webpack/scenes/ContainerImages/Synced/Details/ManifestDetailsSelectors.js +16 -0
  332. data/webpack/scenes/ContainerImages/Synced/Details/__tests__/ManifestDetails.test.js +395 -0
  333. data/webpack/scenes/ContainerImages/Synced/Details/__tests__/manifestDetails.fixtures.json +43 -0
  334. data/webpack/scenes/ContainerImages/Synced/Details/__tests__/manifestList.fixtures.json +58 -0
  335. data/webpack/scenes/ContainerImages/Synced/Details/index.js +4 -0
  336. data/webpack/scenes/ContainerImages/Synced/SyncedContainerImagesPage.js +359 -0
  337. data/webpack/scenes/ContainerImages/Synced/SyncedContainerImagesPage.scss +21 -0
  338. data/webpack/scenes/ContainerImages/Synced/__tests__/LabelsAnnotationsModal.test.js +69 -0
  339. data/webpack/scenes/ContainerImages/Synced/__tests__/SyncedContainerImagesPage.test.js +335 -0
  340. data/webpack/scenes/ContainerImages/Synced/__tests__/syncedContainerImages.fixtures.json +105 -0
  341. data/webpack/scenes/ContainerImages/TableEmptyState.js +67 -0
  342. data/webpack/scenes/ContainerImages/containerImagesHelpers.js +48 -0
  343. data/webpack/scenes/ContainerImages/index.js +4 -0
  344. data/webpack/scenes/ContentViews/Create/CreateContentViewForm.js +29 -3
  345. data/webpack/scenes/ContentViews/Create/__tests__/contentViewCreateResult.fixtures.json +1 -0
  346. data/webpack/scenes/ContentViews/Create/__tests__/createContentView.test.js +45 -1
  347. data/webpack/scenes/ContentViews/Delete/__tests__/affectedHosts.fixtures.json +0 -1
  348. data/webpack/scenes/ContentViews/Details/ContentViewInfo.js +59 -1
  349. data/webpack/scenes/ContentViews/Details/Filters/CVDebFilterContent.js +1 -0
  350. data/webpack/scenes/ContentViews/Details/Filters/Rules/DebPackage/AddEditDebPackageRuleModal.js +164 -24
  351. data/webpack/scenes/ContentViews/Details/Filters/__tests__/CVDebFilterContent.test.js +268 -0
  352. data/webpack/scenes/ContentViews/Details/Filters/__tests__/cvDebFilterDetail.fixtures.json +95 -0
  353. data/webpack/scenes/ContentViews/Details/Filters/__tests__/cvDebFilterRules.fixtures.json +31 -0
  354. data/webpack/scenes/ContentViews/Details/Filters/__tests__/emptyCVDebFilterRules.fixtures.json +10 -0
  355. data/webpack/scenes/ContentViews/Details/Versions/BulkDelete/__tests__/hosts.fixtures.json +0 -1
  356. data/webpack/scenes/ContentViews/Details/Versions/Delete/__tests__/cvAffectedHosts.fixture.json +0 -1
  357. data/webpack/scenes/ContentViews/Details/__tests__/contentViewRollingDetail.test.js +15 -0
  358. data/webpack/scenes/ContentViews/Details/__tests__/contentViewRollingDetails.fixtures.json +1 -0
  359. data/webpack/scenes/ContentViews/__tests__/contentViewPage.test.js +9 -0
  360. data/webpack/scenes/FlatpakRemotes/CreateEdit/CreateFlatpakRemoteModal.js +5 -3
  361. data/webpack/scenes/FlatpakRemotes/CreateEdit/EditFlatpakRemotesModal.js +1 -1
  362. data/webpack/scenes/FlatpakRemotes/CreateEdit/FlatpakRemoteform.js +35 -3
  363. data/webpack/scenes/FlatpakRemotes/Details/FlatpakRemoteDetails.js +1 -1
  364. data/webpack/scenes/FlatpakRemotes/Details/RemoteRepositories/RemoteRepositoriesTable.css +3 -0
  365. data/webpack/scenes/FlatpakRemotes/Details/RemoteRepositories/RemoteRepositoriesTable.js +63 -132
  366. data/webpack/scenes/FlatpakRemotes/FlatpakRemotesPage.js +67 -143
  367. data/webpack/scenes/SmartProxy/ExpandableCvDetails.js +10 -2
  368. data/webpack/scenes/SmartProxy/SmartProxyContentActions.js +13 -2
  369. data/webpack/scenes/SmartProxy/SmartProxyContentConstants.js +1 -0
  370. data/webpack/scenes/SmartProxy/SmartProxyExpandableTable.js +8 -2
  371. data/webpack/scenes/SmartProxy/__tests__/SmartProxyContentTest.js +67 -1
  372. data/webpack/scenes/Subscriptions/Details/__tests__/__snapshots__/SubscriptionDetails.test.js.snap +2 -2
  373. data/webpack/scenes/Subscriptions/SubscriptionConstants.js +0 -2
  374. data/webpack/scenes/Subscriptions/SubscriptionReducer.js +15 -5
  375. data/webpack/scenes/Subscriptions/SubscriptionsPage.js +22 -5
  376. data/webpack/scenes/Subscriptions/__tests__/SubscriptionsPage.test.js +141 -0
  377. data/webpack/scenes/Subscriptions/__tests__/__snapshots__/SubscriptionsActions.test.js.snap +2 -2
  378. metadata +82 -56
  379. data/app/lib/actions/katello/host/attach_subscriptions.rb +0 -59
  380. data/app/lib/actions/katello/host/auto_attach_subscriptions.rb +0 -22
  381. data/app/lib/actions/katello/host/remove_subscriptions.rb +0 -50
  382. data/app/lib/actions/katello/organization/simple_content_access/disable.rb +0 -25
  383. data/app/lib/actions/katello/organization/simple_content_access/enable.rb +0 -25
  384. data/app/lib/actions/katello/organization/simple_content_access/toggle.rb +0 -42
  385. data/lib/katello/tasks/migrate_structure_content_for_deb.rake +0 -105
  386. data/lib/katello/tasks/upgrades/4.2/remove_checksum_values.rake +0 -17
  387. /data/webpack/scenes/{BootedContainerImages → ContainerImages/Booted}/__tests__/bootedContainerImages.fixtures.js +0 -0
  388. /data/webpack/scenes/{BootedContainerImages → ContainerImages/Booted}/index.js +0 -0
@@ -0,0 +1,640 @@
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 { addToast } from 'foremanReact/components/ToastsList';
5
+ import { nockInstance, assertNockRequest } from '../../../../../../test-utils/nockWrapper';
6
+ import katelloApi from '../../../../../../services/api';
7
+ import BulkChangeHostCollectionsModal from '../BulkChangeHostCollectionsModal';
8
+
9
+ jest.mock('foremanReact/components/ToastsList', () => ({
10
+ addToast: jest.fn(() => ({ type: 'ADD_TOAST' })),
11
+ }));
12
+
13
+ jest.mock('foremanReact/Root/Context/ForemanContext', () => ({
14
+ useForemanOrganization: () => ({ id: 1, name: 'Test Org' }),
15
+ }));
16
+
17
+ const hostCollectionsApiUrl = katelloApi.getApiUrl('/host_collections');
18
+ const autocompleteUrl = katelloApi.getApiUrl('/host_collections/auto_complete_search');
19
+
20
+ const mockHostCollections = {
21
+ total: 3,
22
+ subtotal: 3,
23
+ page: 1,
24
+ per_page: 5,
25
+ results: [
26
+ {
27
+ id: 1,
28
+ name: 'Test Host Collection 1',
29
+ description: 'First test collection',
30
+ max_hosts: 10,
31
+ unlimited_hosts: false,
32
+ total_hosts: 5,
33
+ },
34
+ {
35
+ id: 2,
36
+ name: 'Test Host Collection 2',
37
+ description: 'Second test collection',
38
+ max_hosts: null,
39
+ unlimited_hosts: true,
40
+ total_hosts: 3,
41
+ },
42
+ {
43
+ id: 3,
44
+ name: 'Test Host Collection 3',
45
+ description: '',
46
+ max_hosts: 20,
47
+ unlimited_hosts: false,
48
+ total_hosts: 0,
49
+ },
50
+ ],
51
+ };
52
+
53
+ const renderOptions = (state = {}) => ({
54
+ apiNamespace: 'BULK_HOST_COLLECTIONS',
55
+ initialState: {
56
+ API: {
57
+ ...state,
58
+ },
59
+ },
60
+ });
61
+
62
+ const defaultProps = {
63
+ isOpen: true,
64
+ closeModal: jest.fn(),
65
+ fetchBulkParams: jest.fn(() => 'name ~ test'),
66
+ selectedCount: 5,
67
+ };
68
+
69
+ beforeEach(() => {
70
+ jest.clearAllMocks();
71
+ });
72
+
73
+ test('renders modal when open', async () => {
74
+ const autocompleteScope = nockInstance
75
+ .get(autocompleteUrl)
76
+ .query(true)
77
+ .reply(200, [])
78
+ .persist();
79
+
80
+ const hostCollectionsScope = nockInstance
81
+ .get(hostCollectionsApiUrl)
82
+ .query(true)
83
+ .reply(200, mockHostCollections)
84
+ .persist();
85
+
86
+ const { getByText } = renderWithRedux(
87
+ <BulkChangeHostCollectionsModal {...defaultProps} />,
88
+ renderOptions(),
89
+ );
90
+
91
+ await patientlyWaitFor(() => {
92
+ expect(getByText('Change host collections')).toBeInTheDocument();
93
+ expect(getByText('Add to host collections')).toBeInTheDocument();
94
+ expect(getByText('Remove from host collections')).toBeInTheDocument();
95
+ });
96
+
97
+ assertNockRequest(hostCollectionsScope, false);
98
+ assertNockRequest(autocompleteScope, false);
99
+ });
100
+
101
+ test('displays host collections table', async () => {
102
+ const autocompleteScope = nockInstance
103
+ .get(autocompleteUrl)
104
+ .query(true)
105
+ .reply(200, [])
106
+ .persist();
107
+
108
+ const hostCollectionsScope = nockInstance
109
+ .get(hostCollectionsApiUrl)
110
+ .query(true)
111
+ .reply(200, mockHostCollections)
112
+ .persist();
113
+
114
+ const { getByText } = renderWithRedux(
115
+ <BulkChangeHostCollectionsModal {...defaultProps} />,
116
+ renderOptions(),
117
+ );
118
+
119
+ await patientlyWaitFor(() => {
120
+ expect(getByText('Test Host Collection 1')).toBeInTheDocument();
121
+ expect(getByText('Test Host Collection 2')).toBeInTheDocument();
122
+ expect(getByText('Test Host Collection 3')).toBeInTheDocument();
123
+ expect(getByText('First test collection')).toBeInTheDocument();
124
+ expect(getByText('Second test collection')).toBeInTheDocument();
125
+ expect(getByText('5/10')).toBeInTheDocument(); // limit display
126
+ expect(getByText('3/unlimited')).toBeInTheDocument(); // unlimited hosts
127
+ });
128
+
129
+ assertNockRequest(hostCollectionsScope, false);
130
+ assertNockRequest(autocompleteScope, false);
131
+ });
132
+
133
+ test('Save button is disabled when no host collections selected', async () => {
134
+ const autocompleteScope = nockInstance
135
+ .get(autocompleteUrl)
136
+ .query(true)
137
+ .reply(200, [])
138
+ .persist();
139
+
140
+ const hostCollectionsScope = nockInstance
141
+ .get(hostCollectionsApiUrl)
142
+ .query(true)
143
+ .reply(200, mockHostCollections)
144
+ .persist();
145
+
146
+ const { getAllByRole } = renderWithRedux(
147
+ <BulkChangeHostCollectionsModal {...defaultProps} />,
148
+ renderOptions(),
149
+ );
150
+
151
+ await patientlyWaitFor(() => {
152
+ const saveButton = getAllByRole('button', { name: 'Save' })[0];
153
+ expect(saveButton).toBeInTheDocument();
154
+ expect(saveButton).toHaveAttribute('aria-disabled', 'true');
155
+ });
156
+
157
+ assertNockRequest(hostCollectionsScope, false);
158
+ assertNockRequest(autocompleteScope, false);
159
+ });
160
+
161
+ test('Save button is enabled when host collection is selected', async () => {
162
+ const autocompleteScope = nockInstance
163
+ .get(autocompleteUrl)
164
+ .query(true)
165
+ .reply(200, [])
166
+ .persist();
167
+
168
+ const hostCollectionsScope = nockInstance
169
+ .get(hostCollectionsApiUrl)
170
+ .query(true)
171
+ .reply(200, mockHostCollections)
172
+ .persist();
173
+
174
+ const { getAllByRole, getByText } = renderWithRedux(
175
+ <BulkChangeHostCollectionsModal {...defaultProps} />,
176
+ renderOptions(),
177
+ );
178
+
179
+ await patientlyWaitFor(() => {
180
+ expect(getByText('Test Host Collection 1')).toBeInTheDocument();
181
+ });
182
+
183
+ let checkboxes;
184
+ await act(async () => {
185
+ checkboxes = getAllByRole('checkbox');
186
+ // Click first host collection checkbox (skip the select all checkbox)
187
+ await userEvent.click(checkboxes[1]);
188
+ });
189
+
190
+ await patientlyWaitFor(() => {
191
+ const saveButton = getAllByRole('button', { name: 'Save' })[0];
192
+ expect(saveButton).toHaveAttribute('aria-disabled', 'false');
193
+ });
194
+
195
+ assertNockRequest(hostCollectionsScope, false);
196
+ assertNockRequest(autocompleteScope, false);
197
+ });
198
+
199
+ // Test loading state of Save button
200
+ test('Save button is disabled and shows loading indicator while fetching host collections', async () => {
201
+ const autocompleteScope = nockInstance
202
+ .get(autocompleteUrl)
203
+ .query(true)
204
+ .reply(200, [])
205
+ .persist();
206
+
207
+ const hostCollectionsScope = nockInstance
208
+ .get(hostCollectionsApiUrl)
209
+ .query(true)
210
+ .reply(200, mockHostCollections)
211
+ .persist();
212
+
213
+ const { getAllByRole } = renderWithRedux(
214
+ <BulkChangeHostCollectionsModal {...defaultProps} />,
215
+ renderOptions(),
216
+ );
217
+
218
+ // Immediately after render, before host collections resolve
219
+ const saveButton = getAllByRole('button', { name: /Save/ })[0];
220
+ expect(saveButton).toBeInTheDocument();
221
+ expect(saveButton).toHaveAttribute('aria-disabled', 'true');
222
+
223
+ // Check for loading indicator (adjust selector as needed for your implementation)
224
+ // Example: spinner, loading text, etc.
225
+ // If your Save button shows a spinner or "Loading..." text, check for it:
226
+ // expect(getByText(/loading/i)).toBeInTheDocument();
227
+ // Or if you use a spinner with a test id:
228
+ // expect(saveButton.querySelector('[data-testid="loading-spinner"]')).toBeInTheDocument();
229
+
230
+ // Wait for host collections to load
231
+ await patientlyWaitFor(() => {
232
+ expect(saveButton).toBeInTheDocument();
233
+ // After loading, Save button should still be disabled if no selection
234
+ expect(saveButton).toHaveAttribute('aria-disabled', 'true');
235
+ });
236
+
237
+ assertNockRequest(hostCollectionsScope, false);
238
+ assertNockRequest(autocompleteScope, false);
239
+ });
240
+
241
+ test('switches between Add and Remove radio buttons', async () => {
242
+ const autocompleteScope = nockInstance
243
+ .get(autocompleteUrl)
244
+ .query(true)
245
+ .reply(200, [])
246
+ .persist();
247
+
248
+ const hostCollectionsScope = nockInstance
249
+ .get(hostCollectionsApiUrl)
250
+ .query(true)
251
+ .reply(200, mockHostCollections)
252
+ .persist();
253
+
254
+ const { getByLabelText } = renderWithRedux(
255
+ <BulkChangeHostCollectionsModal {...defaultProps} />,
256
+ renderOptions(),
257
+ );
258
+
259
+ await patientlyWaitFor(() => {
260
+ const addRadio = getByLabelText('Add to host collections');
261
+ const removeRadio = getByLabelText('Remove from host collections');
262
+
263
+ expect(addRadio).toBeChecked();
264
+ expect(removeRadio).not.toBeChecked();
265
+ });
266
+
267
+ await act(async () => {
268
+ const removeRadio = getByLabelText('Remove from host collections');
269
+ userEvent.click(removeRadio);
270
+ });
271
+
272
+ await patientlyWaitFor(() => {
273
+ const addRadio = getByLabelText('Add to host collections');
274
+ const removeRadio = getByLabelText('Remove from host collections');
275
+
276
+ expect(addRadio).not.toBeChecked();
277
+ expect(removeRadio).toBeChecked();
278
+ });
279
+
280
+ assertNockRequest(hostCollectionsScope, false);
281
+ assertNockRequest(autocompleteScope, false);
282
+ });
283
+
284
+ test('closes modal and clears selections on Cancel', async () => {
285
+ const autocompleteScope = nockInstance
286
+ .get(autocompleteUrl)
287
+ .query(true)
288
+ .reply(200, [])
289
+ .persist();
290
+
291
+ const hostCollectionsScope = nockInstance
292
+ .get(hostCollectionsApiUrl)
293
+ .query(true)
294
+ .reply(200, mockHostCollections)
295
+ .persist();
296
+
297
+ const closeModal = jest.fn();
298
+
299
+ const { getAllByRole } = renderWithRedux(
300
+ <BulkChangeHostCollectionsModal
301
+ {...defaultProps}
302
+ closeModal={closeModal}
303
+ />,
304
+ renderOptions(),
305
+ );
306
+
307
+ await patientlyWaitFor(() => {
308
+ const cancelButton = getAllByRole('button', { name: 'Cancel' })[0];
309
+ expect(cancelButton).toBeInTheDocument();
310
+ });
311
+
312
+ await act(async () => {
313
+ const cancelButton = getAllByRole('button', { name: 'Cancel' })[0];
314
+ userEvent.click(cancelButton);
315
+ });
316
+
317
+ expect(closeModal).toHaveBeenCalled();
318
+ assertNockRequest(hostCollectionsScope, false);
319
+ assertNockRequest(autocompleteScope, false);
320
+ });
321
+
322
+ test('does not fetch data when modal is closed', () => {
323
+ const { queryByText } = renderWithRedux(
324
+ <BulkChangeHostCollectionsModal
325
+ {...defaultProps}
326
+ isOpen={false}
327
+ />,
328
+ renderOptions(),
329
+ );
330
+
331
+ expect(queryByText('Change host collections')).not.toBeInTheDocument();
332
+ });
333
+
334
+ test('shows success toast notification on successful save', async () => {
335
+ const autocompleteScope = nockInstance
336
+ .get(autocompleteUrl)
337
+ .query(true)
338
+ .reply(200, [])
339
+ .persist();
340
+
341
+ const hostCollectionsScope = nockInstance
342
+ .get(hostCollectionsApiUrl)
343
+ .query(true)
344
+ .reply(200, mockHostCollections)
345
+ .persist();
346
+
347
+ const saveScope = nockInstance
348
+ .put('/api/v2/hosts/bulk/add_host_collections')
349
+ .reply(200, { displayMessages: ['Host collections updated'] });
350
+
351
+ const hostsRefreshScope = nockInstance
352
+ .get('/api/hosts')
353
+ .query(true)
354
+ .reply(200, { results: [] });
355
+
356
+ const { getAllByRole, getByText } = renderWithRedux(
357
+ <BulkChangeHostCollectionsModal {...defaultProps} />,
358
+ renderOptions(),
359
+ );
360
+
361
+ await patientlyWaitFor(() => {
362
+ expect(getByText('Test Host Collection 1')).toBeInTheDocument();
363
+ });
364
+
365
+ // Simulate selecting a host collection
366
+ let checkboxes;
367
+ await act(async () => {
368
+ checkboxes = getAllByRole('checkbox');
369
+ // Click first host collection checkbox (skip the select all checkbox)
370
+ await userEvent.click(checkboxes[1]);
371
+ });
372
+
373
+ // Save button should be enabled now
374
+ await patientlyWaitFor(() => {
375
+ const saveButton = getAllByRole('button', { name: 'Save' })[0];
376
+ expect(saveButton).toHaveAttribute('aria-disabled', 'false');
377
+ });
378
+
379
+ // Click Save
380
+ await act(async () => {
381
+ const saveButton = getAllByRole('button', { name: 'Save' })[0];
382
+ await userEvent.click(saveButton);
383
+ });
384
+
385
+ await patientlyWaitFor(() => {
386
+ expect(addToast).toHaveBeenCalledWith(expect.objectContaining({ type: 'success' }));
387
+ });
388
+
389
+ assertNockRequest(saveScope, false);
390
+ assertNockRequest(hostsRefreshScope, false);
391
+ assertNockRequest(hostCollectionsScope, false);
392
+ assertNockRequest(autocompleteScope, false);
393
+ });
394
+
395
+ // Test for error toast notification
396
+ test('shows error toast notification on failed save', async () => {
397
+ const autocompleteScope = nockInstance
398
+ .get(autocompleteUrl)
399
+ .query(true)
400
+ .reply(200, [])
401
+ .persist();
402
+
403
+ const hostCollectionsScope = nockInstance
404
+ .get(hostCollectionsApiUrl)
405
+ .query(true)
406
+ .reply(200, mockHostCollections)
407
+ .persist();
408
+
409
+ const saveScope = nockInstance
410
+ .put('/api/v2/hosts/bulk/add_host_collections')
411
+ .reply(500, { error: { message: 'Internal Server Error' } });
412
+
413
+ const { getAllByRole, getByText } = renderWithRedux(
414
+ <BulkChangeHostCollectionsModal {...defaultProps} />,
415
+ renderOptions(),
416
+ );
417
+
418
+ await patientlyWaitFor(() => {
419
+ expect(getByText('Test Host Collection 1')).toBeInTheDocument();
420
+ });
421
+
422
+ // Simulate selecting a host collection
423
+ let checkboxes;
424
+ await act(async () => {
425
+ checkboxes = getAllByRole('checkbox');
426
+ // Click first host collection checkbox (skip the select all checkbox)
427
+ await userEvent.click(checkboxes[1]);
428
+ });
429
+
430
+ // Save button should be enabled now
431
+ await patientlyWaitFor(() => {
432
+ const saveButton = getAllByRole('button', { name: 'Save' })[0];
433
+ expect(saveButton).toHaveAttribute('aria-disabled', 'false');
434
+ });
435
+
436
+ // Click Save
437
+ await act(async () => {
438
+ const saveButton = getAllByRole('button', { name: 'Save' })[0];
439
+ await userEvent.click(saveButton);
440
+ });
441
+
442
+ await patientlyWaitFor(() => {
443
+ expect(addToast).toHaveBeenCalledWith(expect.objectContaining({ type: 'danger' }));
444
+ });
445
+
446
+ assertNockRequest(saveScope, false);
447
+ assertNockRequest(hostCollectionsScope, false);
448
+ assertNockRequest(autocompleteScope, false);
449
+ });
450
+
451
+ // Test for Add action API call
452
+ test('makes correct API call when Add action is selected', async () => {
453
+ const autocompleteScope = nockInstance
454
+ .get(autocompleteUrl)
455
+ .query(true)
456
+ .reply(200, [])
457
+ .persist();
458
+
459
+ const hostCollectionsScope = nockInstance
460
+ .get(hostCollectionsApiUrl)
461
+ .query(true)
462
+ .reply(200, mockHostCollections)
463
+ .persist();
464
+
465
+ const saveScope = nockInstance
466
+ .put('/api/v2/hosts/bulk/add_host_collections')
467
+ .reply(200, { displayMessages: ['Host collections updated'] });
468
+
469
+ const hostsRefreshScope = nockInstance
470
+ .get('/api/hosts')
471
+ .query(true)
472
+ .reply(200, { results: [] });
473
+
474
+ const { getAllByRole, getByText, getByLabelText } = renderWithRedux(
475
+ <BulkChangeHostCollectionsModal {...defaultProps} />,
476
+ renderOptions(),
477
+ );
478
+
479
+ await patientlyWaitFor(() => {
480
+ expect(getByText('Test Host Collection 1')).toBeInTheDocument();
481
+ });
482
+
483
+ // Ensure Add radio is selected (default)
484
+ const addRadio = getByLabelText('Add to host collections');
485
+ expect(addRadio).toBeChecked();
486
+
487
+ // Select a host collection
488
+ let checkboxes;
489
+ await act(async () => {
490
+ checkboxes = getAllByRole('checkbox');
491
+ await userEvent.click(checkboxes[1]);
492
+ });
493
+
494
+ // Click Save
495
+ await act(async () => {
496
+ const saveButton = getAllByRole('button', { name: 'Save' })[0];
497
+ await userEvent.click(saveButton);
498
+ });
499
+
500
+ // Verify the API call was made and wait for all async operations
501
+ await act(async () => {
502
+ await patientlyWaitFor(() => {
503
+ assertNockRequest(saveScope);
504
+ expect(addToast).toHaveBeenCalled();
505
+ });
506
+ });
507
+
508
+ assertNockRequest(hostCollectionsScope, false);
509
+ assertNockRequest(autocompleteScope, false);
510
+ assertNockRequest(hostsRefreshScope, false);
511
+ });
512
+
513
+ // Test for Remove action API call
514
+ test('makes correct API call when Remove action is selected', async () => {
515
+ const autocompleteScope = nockInstance
516
+ .get(autocompleteUrl)
517
+ .query(true)
518
+ .reply(200, [])
519
+ .persist();
520
+
521
+ const hostCollectionsScope = nockInstance
522
+ .get(hostCollectionsApiUrl)
523
+ .query(true)
524
+ .reply(200, mockHostCollections)
525
+ .persist();
526
+
527
+ const saveScope = nockInstance
528
+ .put('/api/v2/hosts/bulk/remove_host_collections')
529
+ .reply(200, { displayMessages: ['Host collections updated'] });
530
+
531
+ const hostsRefreshScope = nockInstance
532
+ .get('/api/hosts')
533
+ .query(true)
534
+ .reply(200, { results: [] });
535
+
536
+ const { getAllByRole, getByText, getByLabelText } = renderWithRedux(
537
+ <BulkChangeHostCollectionsModal {...defaultProps} />,
538
+ renderOptions(),
539
+ );
540
+
541
+ await patientlyWaitFor(() => {
542
+ expect(getByText('Test Host Collection 2')).toBeInTheDocument();
543
+ });
544
+
545
+ // Select Remove radio button
546
+ await act(async () => {
547
+ const removeRadio = getByLabelText('Remove from host collections');
548
+ await userEvent.click(removeRadio);
549
+ });
550
+
551
+ await patientlyWaitFor(() => {
552
+ const removeRadio = getByLabelText('Remove from host collections');
553
+ expect(removeRadio).toBeChecked();
554
+ });
555
+
556
+ // Select a different host collection (2nd one)
557
+ let checkboxes;
558
+ await act(async () => {
559
+ checkboxes = getAllByRole('checkbox');
560
+ await userEvent.click(checkboxes[2]); // Select 2nd host collection
561
+ });
562
+
563
+ // Click Save
564
+ await act(async () => {
565
+ const saveButton = getAllByRole('button', { name: 'Save' })[0];
566
+ await userEvent.click(saveButton);
567
+ });
568
+
569
+ // Verify the API call was made to remove endpoint and wait for all async operations
570
+ await act(async () => {
571
+ await patientlyWaitFor(() => {
572
+ assertNockRequest(saveScope);
573
+ expect(addToast).toHaveBeenCalled();
574
+ });
575
+ });
576
+
577
+ assertNockRequest(hostCollectionsScope, false);
578
+ assertNockRequest(autocompleteScope, false);
579
+ assertNockRequest(hostsRefreshScope, false);
580
+ });
581
+
582
+ // Test for multiple host collections selected
583
+ test('makes correct API call with multiple host collections', async () => {
584
+ const autocompleteScope = nockInstance
585
+ .get(autocompleteUrl)
586
+ .query(true)
587
+ .reply(200, [])
588
+ .persist();
589
+
590
+ const hostCollectionsScope = nockInstance
591
+ .get(hostCollectionsApiUrl)
592
+ .query(true)
593
+ .reply(200, mockHostCollections)
594
+ .persist();
595
+
596
+ const saveScope = nockInstance
597
+ .put('/api/v2/hosts/bulk/add_host_collections')
598
+ .reply(200, { displayMessages: ['Host collections updated'] });
599
+
600
+ const hostsRefreshScope = nockInstance
601
+ .get('/api/hosts')
602
+ .query(true)
603
+ .reply(200, { results: [] });
604
+
605
+ const { getAllByRole, getByText } = renderWithRedux(
606
+ <BulkChangeHostCollectionsModal {...defaultProps} />,
607
+ renderOptions(),
608
+ );
609
+
610
+ await patientlyWaitFor(() => {
611
+ expect(getByText('Test Host Collection 1')).toBeInTheDocument();
612
+ });
613
+
614
+ // Select all three host collections
615
+ let checkboxes;
616
+ await act(async () => {
617
+ checkboxes = getAllByRole('checkbox');
618
+ await userEvent.click(checkboxes[1]); // First HC
619
+ await userEvent.click(checkboxes[2]); // Second HC
620
+ await userEvent.click(checkboxes[3]); // Third HC
621
+ });
622
+
623
+ // Click Save
624
+ await act(async () => {
625
+ const saveButton = getAllByRole('button', { name: 'Save' })[0];
626
+ await userEvent.click(saveButton);
627
+ });
628
+
629
+ // Verify the API call includes all selected host collection IDs and wait for all async operations
630
+ await act(async () => {
631
+ await patientlyWaitFor(() => {
632
+ assertNockRequest(saveScope);
633
+ expect(addToast).toHaveBeenCalled();
634
+ });
635
+ });
636
+
637
+ assertNockRequest(hostCollectionsScope, false);
638
+ assertNockRequest(autocompleteScope, false);
639
+ assertNockRequest(hostsRefreshScope, false);
640
+ });
@@ -0,0 +1,28 @@
1
+ import { API_OPERATIONS, put } from 'foremanReact/redux/API';
2
+ import { errorToast } from '../../../../../scenes/Tasks/helpers';
3
+ import { foremanApi } from '../../../../../services/api';
4
+
5
+ export const BULK_ADD_HOST_COLLECTIONS_KEY = 'BULK_ADD_HOST_COLLECTIONS';
6
+ export const BULK_REMOVE_HOST_COLLECTIONS_KEY = 'BULK_REMOVE_HOST_COLLECTIONS';
7
+
8
+ export const bulkAddHostCollections =
9
+ (params, handleSuccess, handleError) => put({
10
+ type: API_OPERATIONS.PUT,
11
+ key: BULK_ADD_HOST_COLLECTIONS_KEY,
12
+ url: foremanApi.getApiUrl('/hosts/bulk/add_host_collections'),
13
+ handleSuccess,
14
+ handleError,
15
+ errorToast,
16
+ params,
17
+ });
18
+
19
+ export const bulkRemoveHostCollections =
20
+ (params, handleSuccess, handleError) => put({
21
+ type: API_OPERATIONS.PUT,
22
+ key: BULK_REMOVE_HOST_COLLECTIONS_KEY,
23
+ url: foremanApi.getApiUrl('/hosts/bulk/remove_host_collections'),
24
+ handleSuccess,
25
+ handleError,
26
+ errorToast,
27
+ params,
28
+ });