katello 4.17.0 → 4.18.0.rc1

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 (299) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/javascripts/katello/locale/bn/katello.js +148 -4
  3. data/app/assets/javascripts/katello/locale/bn_IN/katello.js +148 -4
  4. data/app/assets/javascripts/katello/locale/ca/katello.js +148 -4
  5. data/app/assets/javascripts/katello/locale/cs/katello.js +148 -4
  6. data/app/assets/javascripts/katello/locale/cs_CZ/katello.js +148 -4
  7. data/app/assets/javascripts/katello/locale/de/katello.js +147 -3
  8. data/app/assets/javascripts/katello/locale/de_AT/katello.js +148 -4
  9. data/app/assets/javascripts/katello/locale/de_DE/katello.js +148 -4
  10. data/app/assets/javascripts/katello/locale/el/katello.js +148 -4
  11. data/app/assets/javascripts/katello/locale/en/katello.js +148 -4
  12. data/app/assets/javascripts/katello/locale/en_GB/katello.js +148 -4
  13. data/app/assets/javascripts/katello/locale/en_US/katello.js +148 -4
  14. data/app/assets/javascripts/katello/locale/es/katello.js +147 -3
  15. data/app/assets/javascripts/katello/locale/et_EE/katello.js +148 -4
  16. data/app/assets/javascripts/katello/locale/fr/katello.js +147 -3
  17. data/app/assets/javascripts/katello/locale/gl/katello.js +148 -4
  18. data/app/assets/javascripts/katello/locale/gu/katello.js +148 -4
  19. data/app/assets/javascripts/katello/locale/he_IL/katello.js +148 -4
  20. data/app/assets/javascripts/katello/locale/hi/katello.js +148 -4
  21. data/app/assets/javascripts/katello/locale/id/katello.js +148 -4
  22. data/app/assets/javascripts/katello/locale/it/katello.js +148 -4
  23. data/app/assets/javascripts/katello/locale/ja/katello.js +147 -3
  24. data/app/assets/javascripts/katello/locale/ka/katello.js +147 -3
  25. data/app/assets/javascripts/katello/locale/kn/katello.js +148 -4
  26. data/app/assets/javascripts/katello/locale/ko/katello.js +147 -3
  27. data/app/assets/javascripts/katello/locale/ml_IN/katello.js +148 -4
  28. data/app/assets/javascripts/katello/locale/mr/katello.js +148 -4
  29. data/app/assets/javascripts/katello/locale/nl_NL/katello.js +148 -4
  30. data/app/assets/javascripts/katello/locale/or/katello.js +148 -4
  31. data/app/assets/javascripts/katello/locale/pa/katello.js +148 -4
  32. data/app/assets/javascripts/katello/locale/pl/katello.js +148 -4
  33. data/app/assets/javascripts/katello/locale/pl_PL/katello.js +148 -4
  34. data/app/assets/javascripts/katello/locale/pt/katello.js +148 -4
  35. data/app/assets/javascripts/katello/locale/pt_BR/katello.js +147 -3
  36. data/app/assets/javascripts/katello/locale/ro/katello.js +148 -4
  37. data/app/assets/javascripts/katello/locale/ro_RO/katello.js +148 -4
  38. data/app/assets/javascripts/katello/locale/ru/katello.js +148 -4
  39. data/app/assets/javascripts/katello/locale/sl/katello.js +148 -4
  40. data/app/assets/javascripts/katello/locale/sv_SE/katello.js +148 -4
  41. data/app/assets/javascripts/katello/locale/ta/katello.js +148 -4
  42. data/app/assets/javascripts/katello/locale/ta_IN/katello.js +148 -4
  43. data/app/assets/javascripts/katello/locale/te/katello.js +148 -4
  44. data/app/assets/javascripts/katello/locale/tr/katello.js +148 -4
  45. data/app/assets/javascripts/katello/locale/vi/katello.js +148 -4
  46. data/app/assets/javascripts/katello/locale/vi_VN/katello.js +148 -4
  47. data/app/assets/javascripts/katello/locale/zh/katello.js +148 -4
  48. data/app/assets/javascripts/katello/locale/zh_CN/katello.js +147 -3
  49. data/app/assets/javascripts/katello/locale/zh_TW/katello.js +148 -4
  50. data/app/controllers/katello/api/registry/registry_proxies_controller.rb +14 -14
  51. data/app/controllers/katello/api/v2/content_uploads_controller.rb +2 -1
  52. data/app/controllers/katello/api/v2/errata_controller.rb +16 -0
  53. data/app/controllers/katello/api/v2/flatpak_remote_repositories_controller.rb +20 -3
  54. data/app/controllers/katello/api/v2/flatpak_remotes_controller.rb +7 -3
  55. data/app/controllers/katello/api/v2/generic_content_units_controller.rb +1 -2
  56. data/app/controllers/katello/api/v2/products_controller.rb +18 -0
  57. data/app/controllers/katello/api/v2/repositories_controller.rb +5 -3
  58. data/app/controllers/katello/concerns/api/v2/hosts_bulk_actions_controller_extensions.rb +10 -0
  59. data/app/controllers/katello/concerns/api/v2/registration_commands_controller_extensions.rb +1 -0
  60. data/app/controllers/katello/concerns/filtered_auto_complete_search.rb +17 -0
  61. data/app/lib/actions/helpers/smart_proxy_sync_helper.rb +12 -0
  62. data/app/lib/actions/katello/content_view/add_rolling_repo_clone.rb +4 -4
  63. data/app/lib/actions/katello/content_view/capsule_sync.rb +8 -3
  64. data/app/lib/actions/katello/content_view/publish.rb +39 -12
  65. data/app/lib/actions/katello/flatpak/mirror_remote_repository.rb +2 -1
  66. data/app/lib/actions/katello/repository/capsule_sync.rb +11 -1
  67. data/app/lib/actions/katello/repository/destroy.rb +3 -0
  68. data/app/lib/actions/katello/repository/import_upload.rb +2 -3
  69. data/app/lib/actions/katello/repository/sync.rb +2 -1
  70. data/app/lib/actions/katello/repository/upload_files.rb +2 -1
  71. data/app/lib/katello/concerns/bookmark_controller_validator_extensions.rb +1 -1
  72. data/app/lib/katello/resources/cdn.rb +1 -1
  73. data/app/models/katello/concerns/smart_proxy_extensions.rb +46 -8
  74. data/app/models/katello/content_view_environment.rb +1 -1
  75. data/app/models/katello/erratum.rb +26 -1
  76. data/app/models/katello/flatpak_remote.rb +0 -1
  77. data/app/models/katello/flatpak_remote_repository.rb +24 -0
  78. data/app/models/katello/glue/pulp/repos.rb +1 -1
  79. data/app/models/katello/root_repository.rb +1 -0
  80. data/app/presenters/katello/flatpak_remote_mirror_status_presenter.rb +41 -0
  81. data/app/services/katello/repository_type_manager.rb +2 -0
  82. data/app/views/foreman/job_templates/flatpak_install.erb +17 -2
  83. data/app/views/foreman/job_templates/flatpak_login_action.erb +17 -3
  84. data/app/views/foreman/job_templates/install_errata_by_search_query.erb +1 -1
  85. data/app/views/foreman/job_templates/install_errata_by_search_query_-_katello_ansible_default.erb +1 -1
  86. data/app/views/foreman/job_templates/install_packages_by_search_query_-_katello_ansible_default.erb +6 -14
  87. data/app/views/foreman/job_templates/remove_packages_by_search_query_-_katello_ansible_default.erb +2 -15
  88. data/app/views/foreman/job_templates/update_packages_by_search_query_-_katello_ansible_default.erb +5 -13
  89. data/app/views/katello/api/v2/flatpak_remote_repositories/base.json.rabl +3 -1
  90. data/app/views/katello/api/v2/flatpak_remotes/base.json.rabl +5 -0
  91. data/app/views/katello/api/v2/flatpak_remotes/permissions.json.rabl +1 -0
  92. data/config/routes/api/v2.rb +1 -1
  93. data/config/routes.rb +3 -0
  94. data/db/migrate/20240924161240_katello_recreate_evr_constructs.rb +20 -0
  95. data/db/migrate/20250613210050_use_big_int_for_erratum_packages_id.rb +11 -0
  96. data/db/migrate/20250714190050_add_missing_rpms_evr_index.rb +14 -0
  97. data/db/seeds.d/108-subcription-bookmarks.rb +2 -0
  98. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/content-hosts/views/content-hosts.html +1 -1
  99. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/errata/views/errata.html +5 -1
  100. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/i18n/bastion_katello.pot +4 -5
  101. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/i18n/locale/bn.po +6 -0
  102. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/i18n/locale/bn_IN.po +6 -0
  103. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/i18n/locale/ca.po +6 -0
  104. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/i18n/locale/cs_CZ.po +6 -0
  105. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/i18n/locale/de.po +6 -0
  106. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/i18n/locale/de_AT.po +6 -0
  107. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/i18n/locale/de_DE.po +6 -0
  108. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/i18n/locale/el.po +6 -0
  109. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/i18n/locale/en_GB.po +6 -0
  110. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/i18n/locale/en_US.po +6 -0
  111. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/i18n/locale/es.po +6 -0
  112. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/i18n/locale/et_EE.po +6 -0
  113. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/i18n/locale/fr.po +6 -0
  114. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/i18n/locale/gl.po +6 -0
  115. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/i18n/locale/gu.po +6 -0
  116. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/i18n/locale/he_IL.po +6 -0
  117. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/i18n/locale/hi.po +6 -0
  118. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/i18n/locale/id.po +6 -0
  119. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/i18n/locale/it.po +6 -0
  120. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/i18n/locale/ja.po +6 -0
  121. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/i18n/locale/ka.po +6 -0
  122. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/i18n/locale/kn.po +6 -0
  123. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/i18n/locale/ko.po +6 -0
  124. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/i18n/locale/ml_IN.po +6 -0
  125. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/i18n/locale/mr.po +6 -0
  126. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/i18n/locale/nl_NL.po +6 -0
  127. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/i18n/locale/or.po +6 -0
  128. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/i18n/locale/pa.po +6 -0
  129. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/i18n/locale/pl.po +6 -0
  130. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/i18n/locale/pl_PL.po +6 -0
  131. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/i18n/locale/pt.po +6 -0
  132. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/i18n/locale/pt_BR.po +6 -0
  133. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/i18n/locale/ro.po +6 -0
  134. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/i18n/locale/ro_RO.po +6 -0
  135. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/i18n/locale/ru.po +6 -0
  136. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/i18n/locale/sl.po +6 -0
  137. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/i18n/locale/sv_SE.po +6 -0
  138. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/i18n/locale/ta.po +6 -0
  139. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/i18n/locale/ta_IN.po +6 -0
  140. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/i18n/locale/te.po +6 -0
  141. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/i18n/locale/tr.po +6 -0
  142. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/i18n/locale/vi.po +6 -0
  143. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/i18n/locale/vi_VN.po +6 -0
  144. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/i18n/locale/zh.po +6 -0
  145. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/i18n/locale/zh_CN.po +6 -0
  146. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/i18n/locale/zh_TW.po +6 -0
  147. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/products/details/repositories/new/new-repository.controller.js +13 -3
  148. data/engines/bastion_katello/lib/bastion_katello/engine.rb +2 -0
  149. data/lib/katello/engine.rb +0 -1
  150. data/lib/katello/permission_creator.rb +1 -1
  151. data/lib/katello/plugin.rb +26 -2
  152. data/lib/katello/version.rb +1 -1
  153. data/lib/katello.rb +0 -1
  154. data/lib/proxy_api/container_gateway.rb +24 -0
  155. data/locale/bn/LC_MESSAGES/katello.mo +0 -0
  156. data/locale/bn/katello.po +148 -4
  157. data/locale/bn_IN/LC_MESSAGES/katello.mo +0 -0
  158. data/locale/bn_IN/katello.po +148 -4
  159. data/locale/ca/LC_MESSAGES/katello.mo +0 -0
  160. data/locale/ca/katello.po +148 -4
  161. data/locale/cs/LC_MESSAGES/katello.mo +0 -0
  162. data/locale/cs/katello.po +148 -4
  163. data/locale/cs_CZ/LC_MESSAGES/katello.mo +0 -0
  164. data/locale/cs_CZ/katello.po +148 -4
  165. data/locale/de/katello.po +147 -3
  166. data/locale/de_AT/LC_MESSAGES/katello.mo +0 -0
  167. data/locale/de_AT/katello.po +148 -4
  168. data/locale/de_DE/LC_MESSAGES/katello.mo +0 -0
  169. data/locale/de_DE/katello.po +148 -4
  170. data/locale/el/LC_MESSAGES/katello.mo +0 -0
  171. data/locale/el/katello.po +148 -4
  172. data/locale/en/LC_MESSAGES/katello.mo +0 -0
  173. data/locale/en/katello.po +148 -4
  174. data/locale/en_GB/LC_MESSAGES/katello.mo +0 -0
  175. data/locale/en_GB/katello.po +148 -4
  176. data/locale/en_US/LC_MESSAGES/katello.mo +0 -0
  177. data/locale/en_US/katello.po +148 -4
  178. data/locale/es/katello.po +147 -3
  179. data/locale/et_EE/LC_MESSAGES/katello.mo +0 -0
  180. data/locale/et_EE/katello.po +148 -4
  181. data/locale/fr/katello.po +147 -3
  182. data/locale/gl/LC_MESSAGES/katello.mo +0 -0
  183. data/locale/gl/katello.po +148 -4
  184. data/locale/gu/LC_MESSAGES/katello.mo +0 -0
  185. data/locale/gu/katello.po +148 -4
  186. data/locale/he_IL/LC_MESSAGES/katello.mo +0 -0
  187. data/locale/he_IL/katello.po +148 -4
  188. data/locale/hi/LC_MESSAGES/katello.mo +0 -0
  189. data/locale/hi/katello.po +148 -4
  190. data/locale/id/LC_MESSAGES/katello.mo +0 -0
  191. data/locale/id/katello.po +148 -4
  192. data/locale/it/LC_MESSAGES/katello.mo +0 -0
  193. data/locale/it/katello.po +148 -4
  194. data/locale/ja/katello.po +147 -3
  195. data/locale/ka/katello.po +147 -3
  196. data/locale/katello.pot +703 -428
  197. data/locale/kn/LC_MESSAGES/katello.mo +0 -0
  198. data/locale/kn/katello.po +148 -4
  199. data/locale/ko/katello.po +147 -3
  200. data/locale/ml_IN/LC_MESSAGES/katello.mo +0 -0
  201. data/locale/ml_IN/katello.po +148 -4
  202. data/locale/mr/LC_MESSAGES/katello.mo +0 -0
  203. data/locale/mr/katello.po +148 -4
  204. data/locale/nl_NL/LC_MESSAGES/katello.mo +0 -0
  205. data/locale/nl_NL/katello.po +148 -4
  206. data/locale/or/LC_MESSAGES/katello.mo +0 -0
  207. data/locale/or/katello.po +148 -4
  208. data/locale/pa/LC_MESSAGES/katello.mo +0 -0
  209. data/locale/pa/katello.po +148 -4
  210. data/locale/pl/LC_MESSAGES/katello.mo +0 -0
  211. data/locale/pl/katello.po +148 -4
  212. data/locale/pl_PL/LC_MESSAGES/katello.mo +0 -0
  213. data/locale/pl_PL/katello.po +148 -4
  214. data/locale/pt/LC_MESSAGES/katello.mo +0 -0
  215. data/locale/pt/katello.po +148 -4
  216. data/locale/pt_BR/katello.po +147 -3
  217. data/locale/ro/LC_MESSAGES/katello.mo +0 -0
  218. data/locale/ro/katello.po +148 -4
  219. data/locale/ro_RO/LC_MESSAGES/katello.mo +0 -0
  220. data/locale/ro_RO/katello.po +148 -4
  221. data/locale/ru/LC_MESSAGES/katello.mo +0 -0
  222. data/locale/ru/katello.po +148 -4
  223. data/locale/sl/LC_MESSAGES/katello.mo +0 -0
  224. data/locale/sl/katello.po +148 -4
  225. data/locale/sv_SE/LC_MESSAGES/katello.mo +0 -0
  226. data/locale/sv_SE/katello.po +148 -4
  227. data/locale/ta/LC_MESSAGES/katello.mo +0 -0
  228. data/locale/ta/katello.po +148 -4
  229. data/locale/ta_IN/LC_MESSAGES/katello.mo +0 -0
  230. data/locale/ta_IN/katello.po +148 -4
  231. data/locale/te/LC_MESSAGES/katello.mo +0 -0
  232. data/locale/te/katello.po +148 -4
  233. data/locale/tr/LC_MESSAGES/katello.mo +0 -0
  234. data/locale/tr/katello.po +148 -4
  235. data/locale/vi/LC_MESSAGES/katello.mo +0 -0
  236. data/locale/vi/katello.po +148 -4
  237. data/locale/vi_VN/LC_MESSAGES/katello.mo +0 -0
  238. data/locale/vi_VN/katello.po +148 -4
  239. data/locale/zh/LC_MESSAGES/katello.mo +0 -0
  240. data/locale/zh/katello.po +148 -4
  241. data/locale/zh_CN/katello.po +147 -3
  242. data/locale/zh_TW/LC_MESSAGES/katello.mo +0 -0
  243. data/locale/zh_TW/katello.po +148 -4
  244. data/package.json +0 -1
  245. data/webpack/components/Content/Details/__tests__/__snapshots__/ContentDetails.test.js.snap +2 -2
  246. data/webpack/components/OptionTooltip/OptionTooltip.scss +1 -1
  247. data/webpack/components/extensions/HostDetails/Tabs/PackagesTab/PackagesTab.js +6 -1
  248. data/webpack/components/extensions/Hosts/ActionsBar/index.js +11 -0
  249. data/webpack/components/extensions/Hosts/BulkActions/BulkChangeHostCVModal/BulkChangeHostCVModal.js +1 -1
  250. data/webpack/components/extensions/Hosts/BulkActions/BulkChangeHostCVModal/actions.js +1 -2
  251. data/webpack/components/extensions/Hosts/BulkActions/BulkErrataWizard/04_ReviewFooter.js +1 -1
  252. data/webpack/components/extensions/Hosts/BulkActions/BulkPackagesWizard/04_ReviewFooter.js +1 -1
  253. data/webpack/components/extensions/Hosts/BulkActions/BulkRepositorySetsWizard/01_BulkRepositorySetsTable.js +371 -0
  254. data/webpack/components/extensions/Hosts/BulkActions/BulkRepositorySetsWizard/03_Review.js +79 -0
  255. data/webpack/components/extensions/Hosts/BulkActions/BulkRepositorySetsWizard/03_ReviewFooter.js +73 -0
  256. data/webpack/components/extensions/Hosts/BulkActions/BulkRepositorySetsWizard/BulkRepositorySetsWizard.js +170 -0
  257. data/webpack/components/extensions/Hosts/BulkActions/BulkRepositorySetsWizard/actions.js +23 -0
  258. data/webpack/components/extensions/Hosts/BulkActions/BulkRepositorySetsWizard/helpers.js +28 -0
  259. data/webpack/components/extensions/Hosts/BulkActions/BulkRepositorySetsWizard/index.js +23 -0
  260. data/webpack/components/extensions/RegistrationCommands/fields/SetupContainerRegistryCerts.js +39 -0
  261. data/webpack/components/extensions/RegistrationCommands/index.js +7 -0
  262. data/webpack/components/pf3Table/formatters/ellipsisCellFormatter.js +3 -2
  263. data/webpack/containers/Application/config.js +11 -0
  264. data/webpack/global_index.js +3 -1
  265. data/webpack/scenes/FlatpakRemotes/CreateEdit/CreateFlatpakRemoteModal.js +30 -0
  266. data/webpack/scenes/FlatpakRemotes/CreateEdit/EditFlatpakRemotesModal.js +38 -0
  267. data/webpack/scenes/FlatpakRemotes/CreateEdit/FlatpakRemoteform.js +218 -0
  268. data/webpack/scenes/FlatpakRemotes/CreateEdit/__tests__/flatpakRemoteform.test.js +117 -0
  269. data/webpack/scenes/FlatpakRemotes/Delete/DeleteFlatpakModal.js +59 -0
  270. data/webpack/scenes/FlatpakRemotes/Details/FlatpakRemoteDetailActions.js +87 -0
  271. data/webpack/scenes/FlatpakRemotes/Details/FlatpakRemoteDetailReducers.js +23 -0
  272. data/webpack/scenes/FlatpakRemotes/Details/FlatpakRemoteDetailSelectors.js +16 -0
  273. data/webpack/scenes/FlatpakRemotes/Details/FlatpakRemoteDetails.js +167 -0
  274. data/webpack/scenes/FlatpakRemotes/Details/Mirror/MirrorRepositoryModal.js +135 -0
  275. data/webpack/scenes/FlatpakRemotes/Details/Mirror/__tests__/mirrorRepositoryModal.test.js +167 -0
  276. data/webpack/scenes/FlatpakRemotes/Details/RemoteRepositories/RemoteRepositoriesTable.js +171 -0
  277. data/webpack/scenes/FlatpakRemotes/Details/__tests__/flatpakRemoteDetails.fixtures.json +58 -0
  278. data/webpack/scenes/FlatpakRemotes/Details/__tests__/flatpakRemoteDetails.test.js +90 -0
  279. data/webpack/scenes/FlatpakRemotes/Details/index.js +4 -0
  280. data/webpack/scenes/FlatpakRemotes/FlatpakRemotesActions.js +26 -0
  281. data/webpack/scenes/FlatpakRemotes/FlatpakRemotesConstants.js +15 -0
  282. data/webpack/scenes/FlatpakRemotes/FlatpakRemotesPage.js +202 -0
  283. data/webpack/scenes/FlatpakRemotes/FlatpakRemotesSelectors.js +24 -0
  284. data/webpack/scenes/FlatpakRemotes/__tests__/flatpakRemotesList.fixtures.json +55 -0
  285. data/webpack/scenes/FlatpakRemotes/__tests__/flatpakRemotesPage.test.js +74 -0
  286. data/webpack/scenes/FlatpakRemotes/index.js +4 -0
  287. data/webpack/scenes/Hosts/ChangeContentSource/components/Hosts.js +2 -2
  288. data/webpack/scenes/RedHatRepositories/components/RecommendedRepositorySetsToggler.scss +1 -1
  289. data/webpack/scenes/RedHatRepositories/index.scss +1 -1
  290. data/webpack/scenes/Settings/SettingsSelectors.js +3 -3
  291. data/webpack/scenes/Settings/index.js +1 -3
  292. data/webpack/scenes/Subscriptions/Details/__tests__/__snapshots__/SubscriptionDetails.test.js.snap +2 -2
  293. data/webpack/scenes/Subscriptions/SubscriptionsPage.scss +1 -1
  294. data/webpack/scenes/Subscriptions/__tests__/SubscriptionsReducer.test.js +0 -21
  295. data/webpack/scenes/Subscriptions/__tests__/__snapshots__/SubscriptionsReducer.test.js.snap +0 -44
  296. data/webpack/scenes/Subscriptions/index.js +1 -3
  297. metadata +39 -21
  298. data/webpack/scenes/Settings/SettingsConstants.js +0 -2
  299. data/webpack/scenes/Settings/SettingsReducer.js +0 -19
@@ -0,0 +1,87 @@
1
+ import { API_OPERATIONS, APIActions, get, post, put } from 'foremanReact/redux/API';
2
+ import { translate as __ } from 'foremanReact/common/I18n';
3
+ import { flatpakRemoteDetailsKey,
4
+ flatpakRemoteRepositoriesKey,
5
+ UPDATE_FLATPAK_REMOTE,
6
+ UPDATE_FLATPAK_REMOTE_SUCCESS,
7
+ UPDATE_FLATPAK_REMOTE_FAILURE,
8
+ DELETE_FLATPAK_REMOTE_KEY,
9
+ SCAN_FLATPAK_REMOTE_KEY } from '../FlatpakRemotesConstants';
10
+ import api, { orgId } from '../../../services/api';
11
+ import { getResponseErrorMsgs } from '../../../utils/helpers';
12
+ import { renderTaskStartedToast } from '../../Tasks/helpers';
13
+
14
+ const getFlatpakRemoteDetails = (id, extraParams = {}) => get({
15
+ type: API_OPERATIONS.GET,
16
+ key: flatpakRemoteDetailsKey(id),
17
+ params: { organization_id: orgId(), include_permissions: true, ...extraParams },
18
+ url: api.getApiUrl(`/flatpak_remotes/${id}`),
19
+ });
20
+
21
+ export const getRemoteRepositories = frId => () =>
22
+ get({
23
+ type: API_OPERATIONS.GET,
24
+ key: flatpakRemoteRepositoriesKey(frId),
25
+ url: api.getApiUrl(`/flatpak_remotes/${frId}/flatpak_remote_repositories`),
26
+ params: { organization_id: orgId() },
27
+ });
28
+
29
+ export const updateFlatpakRemote = (frId, params, handleSuccess, handleError) => put({
30
+ type: API_OPERATIONS.PUT,
31
+ key: flatpakRemoteDetailsKey(frId),
32
+ url: api.getApiUrl(`/flatpak_remotes/${frId}`),
33
+ handleSuccess,
34
+ handleError,
35
+ params,
36
+ successToast: () => __('Flatpak remote updated'),
37
+ errorToast: error => getResponseErrorMsgs(error.response),
38
+ updateData: (_prevState, respState) => respState,
39
+ actionTypes: {
40
+ REQUEST: UPDATE_FLATPAK_REMOTE,
41
+ SUCCESS: UPDATE_FLATPAK_REMOTE_SUCCESS,
42
+ FAILURE: UPDATE_FLATPAK_REMOTE_FAILURE,
43
+ },
44
+ });
45
+
46
+ export const mirrorFlatpakRepository = (
47
+ flatpakRepoId,
48
+ productName,
49
+ handleSuccess,
50
+ handleError,
51
+ ) =>
52
+ post({
53
+ type: API_OPERATIONS.POST,
54
+ key: flatpakRemoteRepositoriesKey(flatpakRepoId),
55
+ url: api.getApiUrl(`/flatpak_remote_repositories/${flatpakRepoId}/mirror`),
56
+ params: { product_name: productName, organization_id: orgId() },
57
+ handleSuccess: (response) => {
58
+ if (handleSuccess) handleSuccess(response);
59
+ return renderTaskStartedToast(response.data);
60
+ },
61
+ handleError,
62
+ errorToast: error => getResponseErrorMsgs(error.response),
63
+ });
64
+
65
+ export const deleteFlatpakRemote = (id, handleSuccess) => APIActions.delete({
66
+ type: API_OPERATIONS.DELETE,
67
+ key: DELETE_FLATPAK_REMOTE_KEY,
68
+ url: api.getApiUrl(`/flatpak_remotes/${id}`),
69
+ handleSuccess,
70
+ successToast: () => __('Flatpak remote deleted'),
71
+ errorToast: error => __('Flatpak remote could not be deleted: ') + getResponseErrorMsgs(error.response),
72
+ });
73
+
74
+ export const scanFlatpakRemote = (id, handleSuccess, handleError) => post({
75
+ type: API_OPERATIONS.POST,
76
+ key: SCAN_FLATPAK_REMOTE_KEY,
77
+ url: api.getApiUrl(`/flatpak_remotes/${id}/scan`),
78
+ handleSuccess: (response) => {
79
+ if (handleSuccess) handleSuccess(response);
80
+ return renderTaskStartedToast(response.data);
81
+ },
82
+ handleError,
83
+ errorToast: error => __('Flatpak remote scan could not be started: ') +
84
+ getResponseErrorMsgs(error.response),
85
+ });
86
+
87
+ export default getFlatpakRemoteDetails;
@@ -0,0 +1,23 @@
1
+ import Immutable from 'seamless-immutable';
2
+ import {
3
+ UPDATE_FLATPAK_REMOTE,
4
+ UPDATE_FLATPAK_REMOTE_FAILURE,
5
+ UPDATE_FLATPAK_REMOTE_SUCCESS,
6
+ } from '../FlatpakRemotesConstants';
7
+
8
+ const initialState = Immutable({
9
+ updating: false,
10
+ });
11
+
12
+ export default (state = initialState, action) => {
13
+ switch (action.type) {
14
+ case UPDATE_FLATPAK_REMOTE:
15
+ return state.set('updating', true);
16
+ case UPDATE_FLATPAK_REMOTE_SUCCESS:
17
+ return state.merge({ updating: false });
18
+ case UPDATE_FLATPAK_REMOTE_FAILURE:
19
+ return state.set('updating', false);
20
+ default:
21
+ return state;
22
+ }
23
+ };
@@ -0,0 +1,16 @@
1
+ import { STATUS } from 'foremanReact/constants';
2
+ import {
3
+ selectAPIError,
4
+ selectAPIResponse,
5
+ selectAPIStatus,
6
+ } from 'foremanReact/redux/API/APISelectors';
7
+ import { flatpakRemoteDetailsKey } from '../FlatpakRemotesConstants';
8
+
9
+ export const selectFlatpakRemoteDetails = (state, id) =>
10
+ selectAPIResponse(state, flatpakRemoteDetailsKey(id)) || {};
11
+
12
+ export const selectFlatpakRemoteDetailStatus =
13
+ (state, id) => selectAPIStatus(state, flatpakRemoteDetailsKey(id)) || STATUS.PENDING;
14
+
15
+ export const selectFlatpakRemoteDetailError =
16
+ (state, id) => selectAPIError(state, flatpakRemoteDetailsKey(id));
@@ -0,0 +1,167 @@
1
+ import React, { useState, useEffect } from 'react';
2
+ import { useSelector, useDispatch, shallowEqual } from 'react-redux';
3
+ import { useParams } from 'react-router-dom';
4
+ import {
5
+ Breadcrumb,
6
+ BreadcrumbItem,
7
+ Button,
8
+ Title,
9
+ Grid,
10
+ GridItem,
11
+ Text,
12
+ TextContent,
13
+ TextList,
14
+ TextListVariants,
15
+ Flex,
16
+ FlexItem,
17
+ } from '@patternfly/react-core';
18
+ import {
19
+ Dropdown,
20
+ DropdownItem,
21
+ KebabToggle,
22
+ DropdownPosition,
23
+ } from '@patternfly/react-core/deprecated';
24
+ import { translate as __ } from 'foremanReact/common/I18n';
25
+ import { selectFlatpakRemoteDetails } from './FlatpakRemoteDetailSelectors';
26
+ import getFlatpakRemoteDetails, {
27
+ scanFlatpakRemote,
28
+ updateFlatpakRemote,
29
+ } from './FlatpakRemoteDetailActions';
30
+ import ActionableDetail from '../../../components/ActionableDetail';
31
+ import RemoteRepositoriesTable from './RemoteRepositories/RemoteRepositoriesTable';
32
+ import EditFlatpakRemotesModal from '../CreateEdit/EditFlatpakRemotesModal';
33
+ import DeleteFlatpakModal from '../Delete/DeleteFlatpakModal';
34
+
35
+ export default function FlatpakRemoteDetails() {
36
+ const { id } = useParams();
37
+ const frId = Number(id);
38
+ const [dropDownOpen, setDropdownOpen] = useState(false);
39
+ const [isScanning, setIsScanning] = useState(false);
40
+ const [isEditing, setIsEditing] = useState(false);
41
+ const dispatch = useDispatch();
42
+
43
+ const [currentAttribute, setCurrentAttribute] = useState(null);
44
+ const [isDeleteModalOpen, setDeleteModalOpen] = useState(false);
45
+
46
+ useEffect(() => {
47
+ dispatch(getFlatpakRemoteDetails(frId));
48
+ }, [dispatch, frId]);
49
+
50
+ const frDetails = useSelector(state =>
51
+ selectFlatpakRemoteDetails(state, frId), shallowEqual) || {};
52
+ const name = frDetails.name || '';
53
+ const url = frDetails.url || '';
54
+
55
+ const {
56
+ can_edit: canEdit = false,
57
+ can_delete: canDelete = false,
58
+ can_mirror: canMirror = false,
59
+ } = frDetails || {};
60
+
61
+ const onEdit = (val, attribute) => {
62
+ if (val === frDetails[attribute]) return;
63
+ dispatch(updateFlatpakRemote(frId, { [attribute]: val }));
64
+ };
65
+
66
+ const dropDownItems = [];
67
+ if (canEdit) {
68
+ dropDownItems.push(<DropdownItem key="edit" ouiaId="fr-edit" onClick={() => { setIsEditing(true); }}> {__('Edit')}</DropdownItem>);
69
+ }
70
+ if (canDelete) {
71
+ dropDownItems.push(<DropdownItem key="delete" ouiaId="cv-delete" onClick={() => setDeleteModalOpen(true)}>{__('Delete')}</DropdownItem>);
72
+ }
73
+
74
+ return (
75
+ <Grid hasGutter span={12} style={{ padding: '24px' }}>
76
+ <GridItem span={12}>
77
+ <Breadcrumb ouiaId="flatpak-remote-breadcrumb">
78
+ <BreadcrumbItem to="/flatpak_remotes">Flatpak remotes</BreadcrumbItem>
79
+ <BreadcrumbItem isActive>{name}</BreadcrumbItem>
80
+ </Breadcrumb>
81
+ </GridItem>
82
+
83
+ <GridItem span={12}>
84
+ <Flex>
85
+ <FlexItem>
86
+ <Title headingLevel="h1" size="2xl" ouiaId="flatpak-remote-title">{name}</Title>
87
+ </FlexItem>
88
+ <FlexItem align={{ default: 'alignRight' }}>
89
+ {canEdit &&
90
+ <Button
91
+ ouiaId="fr-details-scan-button"
92
+ style={{ marginLeft: 'auto' }}
93
+ onClick={() => {
94
+ setIsScanning(true);
95
+ dispatch(scanFlatpakRemote(
96
+ frId,
97
+ () => { setIsScanning(false); },
98
+ () => setIsScanning(false),
99
+ ));
100
+ }
101
+ }
102
+ variant="primary"
103
+ aria-label="scan_flatpak_remote"
104
+ isLoading={isScanning}
105
+ isDisabled={isScanning}
106
+ >
107
+ {__('Scan')}
108
+ </Button>
109
+ }
110
+ {dropDownItems.length > 0 &&
111
+ <Dropdown
112
+ position={DropdownPosition.right}
113
+ ouiaId="fr-details-actions"
114
+ style={{ marginLeft: 'auto' }}
115
+ toggle={<KebabToggle onToggle={(_event, val) => setDropdownOpen(val)} id="toggle-dropdown" />}
116
+ isOpen={dropDownOpen}
117
+ isPlain
118
+ dropdownItems={dropDownItems}
119
+ />
120
+ }
121
+ </FlexItem>
122
+ </Flex>
123
+ </GridItem>
124
+
125
+ <GridItem span={12}>
126
+ <TextContent>
127
+ <TextList component={TextListVariants.dl}>
128
+ <ActionableDetail
129
+ key={url}
130
+ label={__('URL:')}
131
+ attribute="url"
132
+ onEdit={onEdit}
133
+ disabled={!canEdit}
134
+ value={url}
135
+ {...{ currentAttribute, setCurrentAttribute }}
136
+ />
137
+ </TextList>
138
+ </TextContent>
139
+ </GridItem>
140
+
141
+ <GridItem span={12}>
142
+ <TextContent>
143
+ <Title headingLevel="h2" size="xl" ouiaId="flatpak-remote-subtitle">Remote repositories</Title>
144
+ <Text component="p" ouiaId="flatpak-remote-description" style={{ color: 'gray' }}>
145
+ This is a list of scanned flatpaks.
146
+ Mirroring a scanned flatpak creates a repository in the product of your choice.
147
+ Sync the repository after mirroring it from this remote to distribute its content.
148
+ </Text>
149
+ </TextContent>
150
+ </GridItem>
151
+
152
+ <GridItem span={12}>
153
+ <RemoteRepositoriesTable frId={frId} canMirror={canMirror} />
154
+ </GridItem>
155
+ { isEditing &&
156
+ <EditFlatpakRemotesModal show={isEditing} remoteData={frDetails} setIsOpen={setIsEditing} />
157
+ }
158
+ { isDeleteModalOpen &&
159
+ <DeleteFlatpakModal
160
+ isModalOpen={isDeleteModalOpen}
161
+ handleModalToggle={() => setDeleteModalOpen(!isDeleteModalOpen)}
162
+ remoteId={frId}
163
+ />
164
+ }
165
+ </Grid>
166
+ );
167
+ }
@@ -0,0 +1,135 @@
1
+ import React, { useState } from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import { useDispatch } from 'react-redux';
4
+ import { translate as __ } from 'foremanReact/common/I18n';
5
+ import {
6
+ Modal,
7
+ ModalVariant,
8
+ Form,
9
+ FormGroup,
10
+ ActionGroup,
11
+ Button,
12
+ TextContent,
13
+ Text,
14
+ TextVariants,
15
+ } from '@patternfly/react-core';
16
+ import { orgId } from '../../../../services/api';
17
+ import SearchText from '../../../../components/Search/SearchText';
18
+ import { getRemoteRepositories, mirrorFlatpakRepository } from '../FlatpakRemoteDetailActions';
19
+
20
+ const MirrorRepositoryModal = ({
21
+ frId,
22
+ closeModal,
23
+ repo,
24
+ onMirrorSuccess,
25
+ }) => {
26
+ const dispatch = useDispatch();
27
+
28
+ const autoCompleteEndpoint = '/katello/api/v2/products/auto_complete_name';
29
+ const searchParams = term => ({
30
+ organization_id: orgId(),
31
+ enabled: true,
32
+ custom: true,
33
+ page: 1,
34
+ per_page: 5,
35
+ term,
36
+ });
37
+
38
+ const [productName, setProductName] = useState('');
39
+ const [loading, setLoading] = useState(false);
40
+
41
+ const handleSearchChange = (rawValue) => {
42
+ setProductName(rawValue.trim());
43
+ };
44
+
45
+ const handleMirror = (e) => {
46
+ e.preventDefault();
47
+ if (!productName || loading) return;
48
+ setLoading(true);
49
+ dispatch(mirrorFlatpakRepository(
50
+ repo.id,
51
+ productName,
52
+ () => {
53
+ dispatch(getRemoteRepositories(frId)());
54
+ setLoading(false);
55
+ if (onMirrorSuccess) onMirrorSuccess();
56
+ closeModal();
57
+ },
58
+ () => {
59
+ setLoading(false);
60
+ closeModal();
61
+ },
62
+ ));
63
+ };
64
+
65
+ return (
66
+ <Modal
67
+ ouiaId="mirror-repo-modal"
68
+ title={__('Mirror Repository')}
69
+ variant={ModalVariant.medium}
70
+ isOpen
71
+ onClose={closeModal}
72
+ appendTo={document.body}
73
+ >
74
+ <TextContent>
75
+ <Text component={TextVariants.p} ouiaId="mirror-text-info">
76
+ {__('Mirroring will import the remote flatpak repository')} <strong>{repo.name}</strong>{' '}
77
+ {__('into a product. Details from the flatpak remote will automatically populate the repository fields. The repository will be available for syncing once it has been mirrored into a product.')}
78
+ </Text>
79
+ <Text component={TextVariants.p} ouiaId="mirror-select-product">
80
+ {__('Select a product to mirror the repository into')}
81
+ </Text>
82
+ </TextContent>
83
+ <Form onSubmit={handleMirror}>
84
+ <FormGroup label={__('Product')} isRequired fieldId="mirror-product">
85
+ <SearchText
86
+ value={productName}
87
+ data={{
88
+ autocomplete: {
89
+ url: autoCompleteEndpoint,
90
+ apiParams: searchParams,
91
+ },
92
+ }}
93
+ onSearchChange={handleSearchChange}
94
+ aria-label="product-search"
95
+ />
96
+ </FormGroup>
97
+
98
+ <ActionGroup>
99
+ <Button
100
+ ouiaId="confirm-mirror-btn"
101
+ variant="primary"
102
+ type="submit"
103
+ isLoading={loading}
104
+ isDisabled={!productName || loading}
105
+ >
106
+ {__('Mirror')}
107
+ </Button>
108
+ <Button
109
+ ouiaId="cancel-mirror-btn"
110
+ variant="link"
111
+ onClick={closeModal}
112
+ >
113
+ {__('Cancel')}
114
+ </Button>
115
+ </ActionGroup>
116
+ </Form>
117
+ </Modal>
118
+ );
119
+ };
120
+
121
+ MirrorRepositoryModal.propTypes = {
122
+ frId: PropTypes.number.isRequired,
123
+ closeModal: PropTypes.func.isRequired,
124
+ repo: PropTypes.shape({
125
+ id: PropTypes.number.isRequired,
126
+ name: PropTypes.string.isRequired,
127
+ }).isRequired,
128
+ onMirrorSuccess: PropTypes.func,
129
+ };
130
+
131
+ MirrorRepositoryModal.defaultProps = {
132
+ onMirrorSuccess: null,
133
+ };
134
+
135
+ export default MirrorRepositoryModal;
@@ -0,0 +1,167 @@
1
+ import React from 'react';
2
+ import { Route } from 'react-router-dom';
3
+ import { renderWithRedux, patientlyWaitFor } from 'react-testing-lib-wrapper';
4
+ import { fireEvent } from '@testing-library/react';
5
+ import { nockInstance, assertNockRequest } from '../../../../../test-utils/nockWrapper';
6
+ import api from '../../../../../services/api';
7
+ import FlatpakRemoteDetails from '../../FlatpakRemoteDetails';
8
+ import FLATPAK_REMOTES_KEY from '../../../FlatpakRemotesConstants';
9
+ import frDetailData from '../../__tests__/flatpakRemoteDetails.fixtures.json';
10
+
11
+ jest.mock('react-intl', () => ({ addLocaleData: () => { }, FormattedDate: () => 'mocked' }));
12
+
13
+ const withFRRoute = component => <Route path="/flatpak_remotes/:id([0-9]+)">{component}</Route>;
14
+
15
+ const renderOptions = {
16
+ apiNamespace: `${FLATPAK_REMOTES_KEY}_1`,
17
+ routerParams: {
18
+ initialEntries: [{ pathname: '/flatpak_remotes/1' }],
19
+ initialIndex: 1,
20
+ },
21
+ };
22
+
23
+ const frDetailsPath = api.getApiUrl('/flatpak_remotes/1');
24
+ const repoApiUrl = api.getApiUrl('/flatpak_remotes/1/flatpak_remote_repositories');
25
+
26
+ test('Can open mirror repository modal', async () => {
27
+ const frDetailsScope = nockInstance
28
+ .get(frDetailsPath)
29
+ .query(true)
30
+ .reply(200, frDetailData);
31
+
32
+ const scopeRepos = nockInstance
33
+ .get(repoApiUrl)
34
+ .query(true)
35
+ .times(2)
36
+ .reply(200, {
37
+ results: frDetailData.repositories,
38
+ subtotal: frDetailData.repositories.length,
39
+ page: 1,
40
+ per_page: 20,
41
+ });
42
+
43
+ const productAutocompleteScope = nockInstance
44
+ .get(api.getApiUrl('/products/auto_complete_name'))
45
+ .query(true)
46
+ .reply(200, ['Product1', 'Product2', 'Product3']);
47
+
48
+ const flatpakRepoAutocompleteScope = nockInstance
49
+ .get(api.getApiUrl('/flatpak_remote_repositories/auto_complete_search'))
50
+ .query(true)
51
+ .reply(200, []);
52
+
53
+ const { getByText, getByRole } = renderWithRedux(
54
+ withFRRoute(<FlatpakRemoteDetails />),
55
+ renderOptions,
56
+ );
57
+
58
+ await patientlyWaitFor(() => {
59
+ expect(getByText(frDetailData.repositories[0].name)).toBeInTheDocument();
60
+ });
61
+
62
+ await patientlyWaitFor(() => {
63
+ expect(getByText('Mirror', {
64
+ selector: '[data-ouia-component-id="mirror-button-1"]',
65
+ })).toBeInTheDocument();
66
+ });
67
+
68
+ getByText('Mirror', {
69
+ selector: '[data-ouia-component-id="mirror-button-1"]',
70
+ }).click();
71
+
72
+ await patientlyWaitFor(() => {
73
+ expect(getByRole('textbox')).toBeInTheDocument();
74
+ expect(getByText('Mirror', {
75
+ selector: '[data-ouia-component-id="confirm-mirror-btn"]',
76
+ })).toBeInTheDocument();
77
+ });
78
+
79
+ assertNockRequest(frDetailsScope);
80
+ assertNockRequest(scopeRepos);
81
+ assertNockRequest(productAutocompleteScope);
82
+ assertNockRequest(flatpakRepoAutocompleteScope);
83
+ });
84
+
85
+ test('Can successfully mirror a repository', async () => {
86
+ const selectedRepo = frDetailData.repositories[0];
87
+ const productName = 'Product1';
88
+
89
+ const frDetailsScope = nockInstance
90
+ .get(frDetailsPath)
91
+ .query(true)
92
+ .reply(200, frDetailData);
93
+
94
+ const scopeRepos = nockInstance
95
+ .get(repoApiUrl)
96
+ .query(true)
97
+ .times(3)
98
+ .reply(200, {
99
+ results: frDetailData.repositories,
100
+ subtotal: frDetailData.repositories.length,
101
+ page: 1,
102
+ per_page: 20,
103
+ });
104
+
105
+ const productAutocompleteScope = nockInstance
106
+ .get(api.getApiUrl('/products/auto_complete_name'))
107
+ .query(true)
108
+ .reply(200, ['Product1', 'Product2', 'Product3']);
109
+
110
+ const flatpakRepoAutocompleteScope = nockInstance
111
+ .get(api.getApiUrl('/flatpak_remote_repositories/auto_complete_search'))
112
+ .query(true)
113
+ .reply(200, []);
114
+
115
+ const mirrorScope = nockInstance
116
+ .post(api.getApiUrl(`/flatpak_remote_repositories/${selectedRepo.id}/mirror`), {
117
+ product_name: productName,
118
+ organization_id: 1,
119
+ })
120
+ .reply(200, { task_id: 'mirror-task-123' });
121
+
122
+ const { getByText, getByRole } = renderWithRedux(
123
+ withFRRoute(<FlatpakRemoteDetails />),
124
+ renderOptions,
125
+ );
126
+
127
+ await patientlyWaitFor(() => {
128
+ expect(getByText(selectedRepo.name)).toBeInTheDocument();
129
+ });
130
+
131
+ await patientlyWaitFor(() => {
132
+ expect(getByText('Mirror', {
133
+ selector: '[data-ouia-component-id="mirror-button-1"]',
134
+ })).toBeInTheDocument();
135
+ });
136
+
137
+
138
+ getByText('Mirror', {
139
+ selector: '[data-ouia-component-id="mirror-button-1"]',
140
+ }).click();
141
+
142
+ await patientlyWaitFor(() => {
143
+ expect(getByText('Mirror Repository')).toBeInTheDocument();
144
+ });
145
+
146
+ const productInput = getByRole('textbox');
147
+ fireEvent.change(productInput, { target: { value: productName } });
148
+
149
+ await patientlyWaitFor(() => {
150
+ expect(getByText('Mirror', {
151
+ selector: '[data-ouia-component-id="confirm-mirror-btn"]',
152
+ })).toBeInTheDocument();
153
+ });
154
+
155
+ getByText('Mirror', {
156
+ selector: '[data-ouia-component-id="confirm-mirror-btn"]',
157
+ }).click();
158
+
159
+ await patientlyWaitFor(() => {
160
+ assertNockRequest(mirrorScope);
161
+ });
162
+
163
+ assertNockRequest(frDetailsScope);
164
+ assertNockRequest(scopeRepos);
165
+ assertNockRequest(productAutocompleteScope);
166
+ assertNockRequest(flatpakRepoAutocompleteScope);
167
+ });