katello 4.1.4 → 4.2.0.rc1

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 (240) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/katello/api/rhsm/candlepin_proxies_controller.rb +2 -2
  3. data/app/controllers/katello/api/v2/content_credentials_controller.rb +3 -3
  4. data/app/controllers/katello/api/v2/content_uploads_controller.rb +3 -1
  5. data/app/controllers/katello/api/v2/content_view_components_controller.rb +33 -1
  6. data/app/controllers/katello/api/v2/content_views_controller.rb +12 -0
  7. data/app/controllers/katello/api/v2/host_errata_controller.rb +1 -1
  8. data/app/controllers/katello/api/v2/products_bulk_actions_controller.rb +1 -2
  9. data/app/controllers/katello/api/v2/products_controller.rb +4 -4
  10. data/app/controllers/katello/api/v2/repositories_bulk_actions_controller.rb +3 -11
  11. data/app/controllers/katello/api/v2/repositories_controller.rb +68 -47
  12. data/app/controllers/katello/api/v2/upstream_subscriptions_controller.rb +0 -28
  13. data/app/controllers/katello/concerns/api/v2/registration_commands_controller_extensions.rb +26 -5
  14. data/app/controllers/katello/concerns/api/v2/registration_controller_extensions.rb +26 -1
  15. data/app/lib/actions/candlepin/environment/destroy.rb +2 -0
  16. data/app/lib/actions/katello/agent_action.rb +2 -2
  17. data/app/lib/actions/katello/capsule_content/sync_capsule.rb +3 -2
  18. data/app/lib/actions/katello/{gpg_key → content_credential}/update.rb +1 -1
  19. data/app/lib/actions/katello/content_view/publish.rb +6 -1
  20. data/app/lib/actions/katello/content_view_version/create_repos.rb +1 -1
  21. data/app/lib/actions/katello/content_view_version/incremental_update.rb +0 -47
  22. data/app/lib/actions/katello/orphan_cleanup/remove_orphans.rb +1 -1
  23. data/app/lib/actions/katello/repository/clone_contents.rb +1 -7
  24. data/app/lib/actions/katello/repository/clone_to_environment.rb +1 -7
  25. data/app/lib/actions/katello/repository/create.rb +4 -8
  26. data/app/lib/actions/katello/repository/create_root.rb +1 -1
  27. data/app/lib/actions/katello/repository/destroy.rb +1 -3
  28. data/app/lib/actions/katello/repository/import_upload.rb +3 -2
  29. data/app/lib/actions/katello/repository/instance_update.rb +1 -1
  30. data/app/lib/actions/katello/repository/metadata_generate.rb +2 -8
  31. data/app/lib/actions/katello/repository/multi_clone_contents.rb +0 -1
  32. data/app/lib/actions/katello/repository/refresh_repository.rb +1 -4
  33. data/app/lib/actions/katello/repository/remove_content.rb +6 -4
  34. data/app/lib/actions/katello/repository/sync.rb +5 -25
  35. data/app/lib/actions/katello/repository/update.rb +1 -2
  36. data/app/lib/actions/katello/repository/update_http_proxy_details.rb +2 -5
  37. data/app/lib/actions/katello/repository/update_redhat_repository.rb +1 -1
  38. data/app/lib/actions/katello/repository/upload_files.rb +8 -3
  39. data/app/lib/actions/katello/repository/upload_package_group.rb +2 -11
  40. data/app/lib/actions/katello/repository/verify_checksum.rb +0 -1
  41. data/app/lib/actions/katello/repository_set/enable_repository.rb +1 -1
  42. data/app/lib/actions/pulp3/orchestration/repository/create.rb +2 -2
  43. data/app/lib/actions/pulp3/repository/create.rb +3 -4
  44. data/app/lib/actions/pulp3/repository/create_remote.rb +1 -6
  45. data/app/lib/actions/pulp3/repository/repair.rb +4 -0
  46. data/app/lib/katello/errors.rb +1 -0
  47. data/app/lib/katello/http_resource.rb +26 -73
  48. data/app/lib/katello/qpid/connection.rb +1 -3
  49. data/app/lib/katello/resources/candlepin/consumer.rb +1 -1
  50. data/app/lib/katello/resources/candlepin/environment.rb +2 -0
  51. data/app/lib/katello/resources/registry.rb +7 -20
  52. data/app/lib/katello/util/http_proxy.rb +0 -3
  53. data/app/lib/katello/validators/gpg_key_content_validator.rb +1 -1
  54. data/app/models/katello/authorization/{gpg_key.rb → content_credential.rb} +1 -1
  55. data/app/models/katello/authorization/product.rb +0 -4
  56. data/app/models/katello/concerns/host_managed_extensions.rb +2 -16
  57. data/app/models/katello/concerns/organization_extensions.rb +1 -1
  58. data/app/models/katello/concerns/pulp_database_unit.rb +13 -5
  59. data/app/models/katello/concerns/smart_proxy_extensions.rb +45 -41
  60. data/app/models/katello/{gpg_key.rb → content_credential.rb} +4 -4
  61. data/app/models/katello/content_view.rb +6 -1
  62. data/app/models/katello/generic_content_unit.rb +16 -0
  63. data/app/models/katello/glue/pulp/repos.rb +9 -25
  64. data/app/models/katello/kt_environment.rb +1 -1
  65. data/app/models/katello/product.rb +4 -4
  66. data/app/models/katello/repository.rb +13 -7
  67. data/app/models/katello/repository_generic_content_unit.rb +7 -0
  68. data/app/models/katello/root_repository.rb +38 -7
  69. data/app/models/setting/content.rb +5 -0
  70. data/app/services/cert/certs.rb +16 -8
  71. data/app/services/katello/applicability/applicable_content_helper.rb +1 -2
  72. data/app/services/katello/candlepin/consumer.rb +6 -0
  73. data/app/services/katello/component_view_presenter.rb +27 -0
  74. data/app/services/katello/pulp/repository.rb +1 -1
  75. data/app/services/katello/pulp/server.rb +2 -2
  76. data/app/services/katello/pulp3/api/core.rb +4 -0
  77. data/app/services/katello/pulp3/api/generic.rb +68 -0
  78. data/app/services/katello/pulp3/generic_content_unit.rb +29 -0
  79. data/app/services/katello/pulp3/pulp_content_unit.rb +5 -1
  80. data/app/services/katello/pulp3/repository/generic.rb +94 -0
  81. data/app/services/katello/pulp3/repository/yum.rb +4 -5
  82. data/app/services/katello/pulp3/repository.rb +27 -12
  83. data/app/services/katello/pulp3/repository_mirror.rb +2 -2
  84. data/app/services/katello/pulp3/smart_proxy_repository.rb +4 -4
  85. data/app/services/katello/registration_manager.rb +18 -7
  86. data/app/services/katello/repository_type.rb +59 -1
  87. data/app/services/katello/repository_type_manager.rb +116 -24
  88. data/app/views/katello/api/v2/content_views/base.json.rabl +4 -4
  89. data/app/views/katello/api/v2/repositories/show.json.rabl +1 -0
  90. data/app/views/smart_proxies/plugins/_pulpcore.html.erb +2 -5
  91. data/app/views/smart_proxies/pulp_status.html.erb +0 -7
  92. data/config/katello.yaml.example +0 -21
  93. data/config/routes/api/v2.rb +2 -1
  94. data/db/functions/deb_version_cmp_v01.sql +200 -0
  95. data/db/migrate/20171110082124_add_ssl_certs_to_products_and_repos.rb +5 -1
  96. data/db/migrate/20200402130013_add_repsoitory_docker_meta_tag_f_key.rb +3 -1
  97. data/db/migrate/20210624221630_katello_generic_content.rb +22 -0
  98. data/db/migrate/20210625095042_add_retain_package_versions_count.rb +9 -0
  99. data/db/migrate/20210628182553_add_generic_remote_options_to_root_repository.rb +5 -0
  100. data/db/migrate/20210714140440_remove_repo_export_permission.rb +5 -0
  101. data/db/migrate/20210721163730_change_gpg_keys_to_content_credentials.rb +8 -0
  102. data/db/migrate/20210728130748_create_function_deb_version_cmp.rb +12 -0
  103. data/db/seeds.d/111-upgrade_tasks.rb +1 -2
  104. data/engines/bastion/app/views/bastion/layouts/assets.html.erb +1 -1
  105. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/capsule-content/capsule-content.controller.js +7 -5
  106. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/content-hosts/bulk/views/content-hosts-bulk-errata-modal.html +4 -1
  107. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/content-views/details/views/content-view-details.html +1 -1
  108. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/errata/apply-errata.controller.js +1 -1
  109. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/errata/views/apply-errata-confirm.html +2 -1
  110. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/i18n/bastion_katello.pot +25 -33
  111. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/products/bulk/views/products-bulk-advanced-sync-modal.html +1 -1
  112. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/products/details/product-repositories.controller.js +1 -6
  113. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/products/details/repositories/details/repository-details-info.controller.js +10 -2
  114. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/products/details/repositories/details/repository-details-info.filter.js +9 -0
  115. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/products/details/repositories/details/repository-details.controller.js +0 -2
  116. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/products/details/repositories/details/views/repository-advanced-sync-options.html +1 -25
  117. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/products/details/repositories/details/views/repository-details.html +1 -1
  118. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/products/details/repositories/details/views/repository-info.html +31 -13
  119. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/products/details/repositories/new/views/new-repository.html +11 -3
  120. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/products/details/repositories/views/product-repositories.html +0 -6
  121. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/tasks/aggregate-task.factory.js +3 -3
  122. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/tasks/task.factory.js +1 -1
  123. data/lib/katello/engine.rb +2 -4
  124. data/lib/katello/permission_creator.rb +6 -12
  125. data/lib/katello/plugin.rb +76 -80
  126. data/lib/katello/repository_types/python.rb +37 -0
  127. data/lib/katello/tasks/reimport.rake +0 -9
  128. data/lib/katello/tasks/repository.rake +3 -4
  129. data/lib/katello/version.rb +1 -1
  130. data/locale/action_names.rb +28 -29
  131. data/locale/bn/katello.po +699 -221
  132. data/locale/cs/katello.po +167 -59
  133. data/locale/de/katello.po +585 -352
  134. data/locale/en/katello.po +167 -59
  135. data/locale/es/katello.po +1388 -1189
  136. data/locale/fr/katello.po +1740 -1494
  137. data/locale/gu/katello.po +896 -416
  138. data/locale/hi/katello.po +892 -415
  139. data/locale/it/katello.po +371 -170
  140. data/locale/ja/katello.po +1657 -1439
  141. data/locale/katello.pot +933 -736
  142. data/locale/kn/katello.po +894 -416
  143. data/locale/ko/katello.po +515 -317
  144. data/locale/mr/katello.po +857 -415
  145. data/locale/or/katello.po +894 -416
  146. data/locale/pa/katello.po +874 -411
  147. data/locale/pt/katello.po +347 -154
  148. data/locale/pt_BR/katello.po +1398 -1215
  149. data/locale/ru/katello.po +671 -463
  150. data/locale/ta/katello.po +697 -221
  151. data/locale/te/katello.po +891 -415
  152. data/locale/zh_CN/katello.po +2029 -1845
  153. data/locale/zh_TW/katello.po +735 -407
  154. data/package.json +3 -1
  155. data/webpack/components/EditableTextInput/EditableTextInput.js +3 -3
  156. data/webpack/components/RoutedTabs/RoutedTabs.js +7 -8
  157. data/webpack/components/Table/TableWrapper.js +19 -11
  158. data/webpack/components/Table/helpers.js +1 -1
  159. data/webpack/components/extensions/HostDetails/Tabs/ContentTab.js +42 -0
  160. data/webpack/components/extensions/HostDetails/Tabs/SubscriptionTab.js +12 -0
  161. data/webpack/components/extensions/RegistrationCommands/__tests__/__snapshots__/ActivationKeys.test.js.snap +4 -0
  162. data/webpack/components/extensions/RegistrationCommands/fields/ActivationKeys.js +1 -1
  163. data/webpack/components/extensions/RegistrationCommands/index.js +1 -2
  164. data/webpack/components/pf3Table/formatters/selectionHeaderCellFormatter.js +2 -1
  165. data/webpack/fills_index.js +4 -1
  166. data/webpack/redux/actions/RedHatRepositories/helpers.js +2 -4
  167. data/webpack/redux/reducers/RedHatRepositories/enabled.js +4 -1
  168. data/webpack/scenes/ContentViews/ContentViewsActions.js +16 -1
  169. data/webpack/scenes/ContentViews/ContentViewsConstants.js +15 -0
  170. data/webpack/scenes/ContentViews/ContentViewsPage.js +12 -22
  171. data/webpack/scenes/ContentViews/Copy/CopyContentViewForm.js +4 -3
  172. data/webpack/scenes/ContentViews/Create/CreateContentViewForm.js +25 -14
  173. data/webpack/scenes/ContentViews/Create/CreateContentViewModal.js +4 -2
  174. data/webpack/scenes/ContentViews/Details/ComponentContentViews/ComponentContentViewAddModal.js +153 -0
  175. data/webpack/scenes/ContentViews/Details/ComponentContentViews/ComponentVersion.js +21 -10
  176. data/webpack/scenes/ContentViews/Details/ComponentContentViews/ContentViewComponents.js +157 -19
  177. data/webpack/scenes/ContentViews/Details/ComponentContentViews/__tests__/contentViewComponents.fixtures.json +100 -108
  178. data/webpack/scenes/ContentViews/Details/ComponentContentViews/__tests__/contentViewComponents.test.js +140 -16
  179. data/webpack/scenes/ContentViews/Details/ComponentContentViews/__tests__/publishedContentViewDetails.fixtures.json +367 -0
  180. data/webpack/scenes/ContentViews/Details/ContentViewDetailActions.js +59 -6
  181. data/webpack/scenes/ContentViews/Details/ContentViewDetailSelectors.js +43 -0
  182. data/webpack/scenes/ContentViews/Details/ContentViewDetails.js +44 -13
  183. data/webpack/scenes/ContentViews/Details/Filters/Add/CVFilterAddModal.js +161 -0
  184. data/webpack/scenes/ContentViews/Details/Filters/Add/__tests__/cvFilterAdd.test.js +54 -0
  185. data/webpack/scenes/ContentViews/Details/Filters/Add/__tests__/cvFilterCreateResult.fixtures.json +124 -0
  186. data/webpack/scenes/ContentViews/Details/Filters/CVPackageGroupFilterContent.js +8 -6
  187. data/webpack/scenes/ContentViews/Details/Filters/CVRpmFilterContent.js +7 -6
  188. data/webpack/scenes/ContentViews/Details/Filters/ContentViewFilterDetails.js +4 -3
  189. data/webpack/scenes/ContentViews/Details/Filters/ContentViewFilters.js +71 -12
  190. data/webpack/scenes/ContentViews/Details/Filters/__tests__/contentViewFilters.test.js +77 -0
  191. data/webpack/scenes/ContentViews/Details/Histories/ContentViewHistories.js +13 -12
  192. data/webpack/scenes/ContentViews/Details/Histories/__tests__/contentViewHistory.test.js +2 -2
  193. data/webpack/scenes/ContentViews/Details/Repositories/ContentViewRepositories.js +17 -14
  194. data/webpack/scenes/ContentViews/Details/Repositories/LastSync.js +3 -3
  195. data/webpack/scenes/ContentViews/Details/Repositories/__tests__/contentViewAddRemove.test.js +2 -2
  196. data/webpack/scenes/ContentViews/Details/Repositories/__tests__/contentViewDetailRepos.test.js +6 -2
  197. data/webpack/scenes/ContentViews/Details/Versions/ContentViewVersions.js +61 -20
  198. data/webpack/scenes/ContentViews/Details/Versions/__tests__/contentViewTaskInProgressResponse.fixtures.json +71 -0
  199. data/webpack/scenes/ContentViews/Details/Versions/__tests__/contentViewTaskResponse.fixtures.json +75 -0
  200. data/webpack/scenes/ContentViews/Details/Versions/__tests__/contentViewVersions.test.js +86 -1
  201. data/webpack/scenes/ContentViews/Details/Versions/__tests__/contentViewVersionsWithTask.fixtures.json +713 -0
  202. data/webpack/scenes/ContentViews/Details/__tests__/contentViewDetail.test.js +3 -0
  203. data/webpack/scenes/ContentViews/Publish/CVPublishFinish.js +184 -0
  204. data/webpack/scenes/ContentViews/Publish/CVPublishForm.js +104 -0
  205. data/webpack/scenes/ContentViews/Publish/CVPublishReview.js +71 -0
  206. data/webpack/scenes/ContentViews/Publish/ContentViewPublishSelectors.js +17 -0
  207. data/webpack/scenes/ContentViews/Publish/PublishContentViewWizard.js +145 -0
  208. data/webpack/scenes/ContentViews/Publish/__tests__/environmentPaths.fixtures.json +352 -0
  209. data/webpack/scenes/ContentViews/Publish/__tests__/publishContentView.test.js +184 -0
  210. data/webpack/scenes/ContentViews/Publish/__tests__/publishResponse.fixture.json +69 -0
  211. data/webpack/scenes/ContentViews/Publish/cvPublishForm.scss +3 -0
  212. data/webpack/scenes/ContentViews/Table/ContentViewsTable.js +75 -48
  213. data/webpack/scenes/ContentViews/Table/tableDataGenerator.js +15 -2
  214. data/webpack/scenes/ContentViews/__tests__/contentViewPage.test.js +6 -10
  215. data/webpack/scenes/ContentViews/components/EnvironmentLabels.js +22 -10
  216. data/webpack/scenes/ContentViews/components/EnvironmentPaths/EnvironmentPathActions.js +12 -0
  217. data/webpack/scenes/ContentViews/components/EnvironmentPaths/EnvironmentPathConstants.js +2 -0
  218. data/webpack/scenes/ContentViews/components/EnvironmentPaths/EnvironmentPathSelectors.js +16 -0
  219. data/webpack/scenes/ContentViews/components/EnvironmentPaths/EnvironmentPaths.js +72 -0
  220. data/webpack/scenes/ContentViews/components/EnvironmentPaths/EnvironmentPaths.scss +8 -0
  221. data/webpack/scenes/ContentViews/components/TaskPresenter/TaskPresenter.js +85 -0
  222. data/webpack/scenes/SmartProxy/SmartProxyContentTable.js +9 -8
  223. data/webpack/scenes/Subscriptions/SubscriptionsPage.js +4 -25
  224. data/webpack/scenes/Subscriptions/__tests__/SubscriptionsPage.test.js +0 -3
  225. data/webpack/scenes/Subscriptions/__tests__/__snapshots__/SubscriptionsPage.test.js.snap +3 -3
  226. data/webpack/scenes/Subscriptions/components/SubscriptionsTable/SubscriptionsTableSchema.js +4 -2
  227. data/webpack/scenes/Subscriptions/components/SubscriptionsTable/__tests__/__snapshots__/SubscriptionsTable.test.js.snap +24 -0
  228. data/webpack/scenes/Subscriptions/components/SubscriptionsTable/components/Table.js +4 -1
  229. data/webpack/scenes/Subscriptions/index.js +1 -4
  230. metadata +74 -39
  231. data/app/lib/actions/candlepin/environment/create.rb +0 -21
  232. data/app/lib/actions/foreman/environment/destroy.rb +0 -23
  233. data/app/lib/actions/katello/content_view/environment_create.rb +0 -21
  234. data/app/lib/actions/katello/repository/export.rb +0 -85
  235. data/app/lib/actions/katello/repository/purge_empty_content.rb +0 -16
  236. data/app/lib/actions/katello/repository/upload_errata.rb +0 -38
  237. data/app/lib/katello/util/proxy_uri.rb +0 -64
  238. data/app/models/katello/rhsm_fact_importer.rb +0 -20
  239. data/app/models/katello/rhsm_fact_name.rb +0 -17
  240. data/app/models/katello/rhsm_fact_parser.rb +0 -120
@@ -1,5 +1,6 @@
1
- import React, { useState, useEffect } from 'react';
2
- import PropTypes from 'prop-types';
1
+ import React, { useState, useCallback } from 'react';
2
+ import useDeepCompareEffect from 'use-deep-compare-effect';
3
+ import { useSelector } from 'react-redux';
3
4
  import { translate as __ } from 'foremanReact/common/I18n';
4
5
  import { STATUS } from 'foremanReact/constants';
5
6
  import { Button } from '@patternfly/react-core';
@@ -9,30 +10,71 @@ import tableDataGenerator from './tableDataGenerator';
9
10
  import getContentViews from '../ContentViewsActions';
10
11
  import CreateContentViewModal from '../Create/CreateContentViewModal';
11
12
  import CopyContentViewModal from '../Copy/CopyContentViewModal';
13
+ import PublishContentViewWizard from '../Publish/PublishContentViewWizard';
14
+ import { selectContentViews, selectContentViewStatus, selectContentViewError } from '../ContentViewSelectors';
12
15
 
13
- const ContentViewTable = ({ response, status, error }) => {
16
+ const ContentViewTable = () => {
17
+ const response = useSelector(selectContentViews);
18
+ const status = useSelector(selectContentViewStatus);
19
+ const error = useSelector(selectContentViewError);
14
20
  const [table, setTable] = useState({ rows: [], columns: [] });
15
21
  const [rowMappingIds, setRowMappingIds] = useState([]);
16
22
  const [searchQuery, updateSearchQuery] = useState('');
17
- const { results, ...metadata } = response;
18
23
  const loadingResponse = status === STATUS.PENDING;
19
24
  const [isModalOpen, setIsModalOpen] = useState(false);
20
25
  const [copy, setCopy] = useState(false);
26
+ const [cvResults, setCvResults] = useState([]);
27
+ const [metadata, setMetadata] = useState({});
28
+ const [cvTableStatus, setCvTableStatus] = useState(STATUS.PENDING);
29
+ const [isPublishModalOpen, setIsPublishModalOpen] = useState(false);
30
+ const [actionableCvDetails, setActionableCvDetails] = useState({});
21
31
  const [actionableCvId, setActionableCvId] = useState('');
22
32
  const [actionableCvName, setActionableCvName] = useState('');
23
- function openForm() {
24
- setIsModalOpen(true);
25
- }
33
+ const [currentStep, setCurrentStep] = useState(1);
26
34
 
27
- useEffect(
35
+ const openForm = () => setIsModalOpen(true);
36
+
37
+ const openPublishModal = (rowInfo) => {
38
+ setActionableCvDetails({
39
+ id: rowInfo.cvId.toString(),
40
+ name: rowInfo.cvName,
41
+ composite: rowInfo.cvComposite,
42
+ version_count: rowInfo.cvVersionCount,
43
+ next_version: rowInfo.cvNextVersion,
44
+ });
45
+ setIsPublishModalOpen(true);
46
+ };
47
+
48
+ useDeepCompareEffect(
28
49
  () => {
50
+ // Prevents flash of "No Content" before rows are loaded
51
+ const tableStatus = () => {
52
+ if (typeof cvResults === 'undefined' || status === STATUS.ERROR) return status; // will handle errored state
53
+ const resultsIds = Array.from(cvResults.map(result => result.id));
54
+ // All results are accounted for in row mapping, the page is ready to load
55
+ if (resultsIds.length === rowMappingIds.length &&
56
+ resultsIds.every(id => rowMappingIds.includes(id))) {
57
+ return status;
58
+ }
59
+ return STATUS.PENDING; // Fallback to pending
60
+ };
61
+
62
+ const { results, ...meta } = response;
63
+ if (status === STATUS.ERROR) {
64
+ setCvTableStatus(tableStatus());
65
+ }
29
66
  if (!loadingResponse && results) {
67
+ setCvResults(results);
68
+ setMetadata(meta);
69
+ setCurrentStep(1);
30
70
  const { newRowMappingIds, ...tableData } = tableDataGenerator(results);
31
71
  setTable(tableData);
32
72
  setRowMappingIds(newRowMappingIds);
73
+ setCvTableStatus(tableStatus());
33
74
  }
34
75
  },
35
- [results, loadingResponse, setTable, setRowMappingIds],
76
+ [response, status, loadingResponse, setTable, setRowMappingIds,
77
+ setCvResults, setCvTableStatus, setCurrentStep, setMetadata, cvResults, rowMappingIds],
36
78
  );
37
79
 
38
80
  const onSelect = (_event, isSelected, rowId) => {
@@ -67,19 +109,18 @@ const ContentViewTable = ({ response, status, error }) => {
67
109
  /* eslint-disable no-console */
68
110
  return [
69
111
  {
70
- title: 'Publish and Promote',
71
- isDisabled: true,
112
+ title: __('Publish'),
72
113
  onClick: (_event, rowId, rowInfo) => {
73
- console.log(`clicked on row ${JSON.stringify(rowInfo)}`);
114
+ openPublishModal(rowInfo);
74
115
  },
75
116
  },
76
117
  {
77
- title: 'Promote',
118
+ title: __('Promote'),
78
119
  isDisabled: true,
79
120
  onClick: (_event, rowId, rowInfo) => console.log(`clicked on row ${rowInfo.cvName}`),
80
121
  },
81
122
  {
82
- title: 'Copy',
123
+ title: __('Copy'),
83
124
  onClick: (_event, rowId, rowInfo) => {
84
125
  setCopy(true);
85
126
  setActionableCvId(rowInfo.cvId.toString());
@@ -87,25 +128,15 @@ const ContentViewTable = ({ response, status, error }) => {
87
128
  },
88
129
  },
89
130
  {
90
- title: 'Delete',
131
+ title: __('Delete'),
91
132
  isDisabled: true,
92
133
  onClick: (_event, rowId, _rowInfo) => console.log(`clicked on row ${rowId}`),
93
134
  },
94
135
  ];
95
136
  /* eslint-enable no-console */
96
137
  };
97
- // Prevents flash of "No Content" before rows are loaded
98
- const tableStatus = () => {
99
- if (typeof results === 'undefined') return status; // will handle errored state
100
- const resultsIds = Array.from(results.map(result => result.id));
101
- // All results are accounted for in row mapping, the page is ready to load
102
- if (resultsIds.length === rowMappingIds.length &&
103
- resultsIds.every(id => rowMappingIds.includes(id))) {
104
- return status;
105
- }
106
- return STATUS.PENDING; // Fallback to pending
107
- };
108
138
 
139
+ const additionalListeners = new Array(isPublishModalOpen);
109
140
  const emptyContentTitle = __("You currently don't have any Content Views.");
110
141
  const emptyContentBody = __('A content view can be added by using the "New content view" button below.');
111
142
  const emptySearchTitle = __('No matching content views found');
@@ -126,42 +157,38 @@ const ContentViewTable = ({ response, status, error }) => {
126
157
  actionResolver,
127
158
  searchQuery,
128
159
  updateSearchQuery,
160
+ additionalListeners,
129
161
  }}
130
162
  variant={TableVariant.compact}
131
- status={tableStatus()}
132
- fetchItems={getContentViews}
163
+ status={cvTableStatus}
164
+ fetchItems={useCallback(getContentViews, [])}
133
165
  onCollapse={onCollapse}
134
166
  canSelectAll={false}
135
167
  cells={columns}
136
168
  autocompleteEndpoint="/content_views/auto_complete_search"
137
169
  >
138
- <React.Fragment>
170
+ <>
139
171
  <Button onClick={openForm} variant="primary" aria-label="create_content_view">
140
172
  Create content view
141
173
  </Button>
142
174
  <CreateContentViewModal show={isModalOpen} setIsOpen={setIsModalOpen} aria-label="create_content_view_modal" />
143
- </React.Fragment>
144
- <React.Fragment>
175
+ </>
176
+ <>
145
177
  <CopyContentViewModal cvId={actionableCvId} cvName={actionableCvName} show={copy} setIsOpen={setCopy} aria-label="copy_content_view_modal" />
146
- </React.Fragment>
178
+ </>
179
+ {isPublishModalOpen &&
180
+ <>
181
+ <PublishContentViewWizard
182
+ details={actionableCvDetails}
183
+ show={isPublishModalOpen}
184
+ setIsOpen={setIsPublishModalOpen}
185
+ currentStep={currentStep}
186
+ setCurrentStep={setCurrentStep}
187
+ aria-label="publish_content_view_modal"
188
+ />
189
+ </>}
147
190
  </TableWrapper>
148
191
  );
149
192
  };
150
193
 
151
- ContentViewTable.propTypes = {
152
- response: PropTypes.shape({
153
- results: PropTypes.arrayOf(PropTypes.shape({})),
154
- }),
155
- status: PropTypes.string.isRequired,
156
- error: PropTypes.oneOfType([
157
- PropTypes.shape({}),
158
- PropTypes.string,
159
- ]),
160
- };
161
-
162
- ContentViewTable.defaultProps = {
163
- error: null,
164
- response: { results: [] },
165
- };
166
-
167
194
  export default ContentViewTable;
@@ -44,11 +44,24 @@ const buildExpandableRows = (contentViews) => {
44
44
 
45
45
  contentViews.forEach((contentView) => {
46
46
  const {
47
- id, name, description, activation_keys: activationKeys, hosts,
47
+ id,
48
+ name,
49
+ composite,
50
+ next_version: nextVersion,
51
+ version_count: versionCount,
52
+ description,
53
+ activation_keys: activationKeys,
54
+ hosts,
48
55
  } = contentView;
49
56
  const cells = buildRow(contentView);
50
57
  const cellParent = {
51
- cvId: id, cvName: name, isOpen: false, cells,
58
+ cvId: id,
59
+ cvName: name,
60
+ cvVersionCount: versionCount,
61
+ cvComposite: composite,
62
+ cvNextVersion: nextVersion,
63
+ isOpen: false,
64
+ cells,
52
65
  };
53
66
  rows.push(cellParent);
54
67
  const cellChild = {
@@ -50,7 +50,7 @@ test('Can call API for CVs and show on screen on page load', async (done) => {
50
50
  // Assert that the CV is not showing yet by searching by name and the query returning null
51
51
  expect(queryByText(firstCV.name)).toBeNull();
52
52
  // Assert that the CV name is now showing on the screen, but wait for it to appear.
53
- await patientlyWaitFor(() => expect(queryByText(firstCV.name)).toBeTruthy());
53
+ await patientlyWaitFor(() => expect(queryByText(firstCV.name)).toBeInTheDocument());
54
54
  // Assert request was made and completed, see helper function
55
55
  assertNockRequest(autocompleteScope);
56
56
  assertNockRequest(scope, done); // Pass jest callback to confirm test is done
@@ -168,7 +168,7 @@ test('Can handle no Content Views being present', async (done) => {
168
168
  const { queryByText } = renderWithRedux(<ContentViewsPage />, renderOptions);
169
169
 
170
170
  expect(queryByText(firstCV.name)).toBeNull();
171
- await patientlyWaitFor(() => expect(queryByText(/don't have any Content Views/i)).toBeTruthy());
171
+ await patientlyWaitFor(() => expect(queryByText(/don't have any Content Views/i)).toBeInTheDocument());
172
172
  assertNockRequest(autocompleteScope);
173
173
  assertNockRequest(scope, done);
174
174
  });
@@ -183,7 +183,7 @@ test('Can handle errored response', async (done) => {
183
183
  const { queryByText } = renderWithRedux(<ContentViewsPage />, renderOptions);
184
184
 
185
185
  expect(queryByText(firstCV.name)).toBeNull();
186
- await patientlyWaitFor(() => expect(queryByText(/unable to connect/i)).toBeTruthy());
186
+ await patientlyWaitFor(() => expect(queryByText(/unable to connect/i)).toBeInTheDocument());
187
187
  assertNockRequest(autocompleteScope);
188
188
  assertNockRequest(scope, done);
189
189
  });
@@ -231,18 +231,16 @@ test('Can handle pagination', async (done) => {
231
231
  const firstPageScope = nockInstance
232
232
  .get(cvIndexPath)
233
233
  // Using a custom query params matcher because parameters can be strings
234
- .query(actualQueryObject => parseInt(actualQueryObject.page, 10) === 1)
234
+ .query(actualQueryObject => (parseInt(actualQueryObject.page, 10) === 1))
235
235
  .reply(200, cvIndexFirstPage);
236
236
 
237
237
  // Match second page API request
238
238
  const secondPageScope = nockInstance
239
239
  .get(cvIndexPath)
240
240
  // Using a custom query params matcher because parameters can be strings
241
- .query(actualQueryObject => parseInt(actualQueryObject.page, 10) === 2)
241
+ .query(actualQueryObject => (parseInt(actualQueryObject.page, 10) === 2))
242
242
  .reply(200, cvIndexSecondPage);
243
-
244
243
  const { queryByText, getByLabelText } = renderWithRedux(<ContentViewsPage />, renderOptions);
245
-
246
244
  // Wait for first paginated page to load and assert only the first page of results are present
247
245
  await patientlyWaitFor(() => {
248
246
  expect(queryByText(results[0].name)).toBeInTheDocument();
@@ -253,14 +251,12 @@ test('Can handle pagination', async (done) => {
253
251
  // Label comes from patternfly, if this test fails, check if patternfly updated the label.
254
252
  expect(getByLabelText('Go to next page')).toBeTruthy();
255
253
  getByLabelText('Go to next page').click();
256
-
257
254
  // Wait for second paginated page to load and assert only the second page of results are present
258
255
  await patientlyWaitFor(() => {
259
256
  expect(queryByText(results[20].name)).toBeInTheDocument();
260
257
  expect(queryByText(results[39].name)).toBeInTheDocument();
261
258
  expect(queryByText(results[41].name)).not.toBeInTheDocument();
262
259
  });
263
-
264
260
  assertNockRequest(autocompleteScope);
265
261
  assertNockRequest(firstPageScope);
266
262
  assertNockRequest(secondPageScope, done); // Only pass jest callback to the last API request
@@ -359,7 +355,7 @@ test('Displays Create Content View and opens modal with Form', async () => {
359
355
  const {
360
356
  getByText, queryByText, getByLabelText,
361
357
  } = renderWithRedux(<ContentViewsPage />, renderOptions);
362
- await patientlyWaitFor(() => expect(queryByText('Create content view')).toBeTruthy());
358
+ await patientlyWaitFor(() => expect(queryByText('Create content view')).toBeInTheDocument());
363
359
 
364
360
  expect(queryByText('Description')).not.toBeInTheDocument();
365
361
  expect(queryByText('Name')).not.toBeInTheDocument();
@@ -1,16 +1,28 @@
1
1
  import React from 'react';
2
- import PropTypes from 'prop-types';
3
2
  import { Label } from '@patternfly/react-core';
4
3
 
5
- const EnvironmentLabels = environments => environments.map(env =>
6
- <React.Fragment key={env.id}><Label color="purple" href={`/lifecycle_environments/${env.id}`}>{`${env.name}`}</Label></React.Fragment>);
7
-
8
- EnvironmentLabels.propTypes = {
9
- environments: PropTypes.instanceOf(Array),
10
- };
11
-
12
- EnvironmentLabels.defaultProps = {
13
- environments: [],
4
+ const EnvironmentLabels = (environments) => {
5
+ const { environments: singleEnvironment } = environments || {};
6
+ const { name } = singleEnvironment || {};
7
+ switch (environments) {
8
+ case Array:
9
+ return environments.map(env => (
10
+ <React.Fragment key={env.id} style={{ marginBottom: '5px' }}>
11
+ <Label
12
+ color="purple"
13
+ >{`${env.name}`}
14
+ </Label>
15
+ </React.Fragment>
16
+ ));
17
+ default:
18
+ return (
19
+ <React.Fragment>
20
+ <Label color="purple">
21
+ {`${name}`}
22
+ </Label>
23
+ </React.Fragment>
24
+ );
25
+ }
14
26
  };
15
27
 
16
28
  export default EnvironmentLabels;
@@ -0,0 +1,12 @@
1
+ import { API_OPERATIONS, get } from 'foremanReact/redux/API';
2
+ import api, { orgId } from '../../../../services/api';
3
+ import { ENVIRONMENT_PATHS_KEY } from './EnvironmentPathConstants';
4
+
5
+
6
+ const getEnvironmentPaths = () => get({
7
+ type: API_OPERATIONS.GET,
8
+ key: ENVIRONMENT_PATHS_KEY,
9
+ url: api.getApiUrl(`/organizations/${orgId()}/environments/paths?permission_type=promotable`),
10
+ });
11
+
12
+ export default getEnvironmentPaths;
@@ -0,0 +1,2 @@
1
+ export const ENVIRONMENT_PATHS_KEY = 'ENVIRONMENT_PATHS';
2
+ export default ENVIRONMENT_PATHS_KEY;
@@ -0,0 +1,16 @@
1
+ import {
2
+ selectAPIStatus,
3
+ selectAPIError,
4
+ selectAPIResponse,
5
+ } from 'foremanReact/redux/API/APISelectors';
6
+ import { STATUS } from 'foremanReact/constants';
7
+ import { ENVIRONMENT_PATHS_KEY } from './EnvironmentPathConstants';
8
+
9
+ export const selectEnvironmentPaths = state =>
10
+ selectAPIResponse(state, ENVIRONMENT_PATHS_KEY) || {};
11
+
12
+ export const selectEnvironmentPathsStatus = state =>
13
+ selectAPIStatus(state, ENVIRONMENT_PATHS_KEY) || STATUS.PENDING;
14
+
15
+ export const selectEnvironmentPathsError = state =>
16
+ selectAPIError(state, ENVIRONMENT_PATHS_KEY);
@@ -0,0 +1,72 @@
1
+ import React from 'react';
2
+ import { useSelector } from 'react-redux';
3
+ import { STATUS } from 'foremanReact/constants';
4
+ import PropTypes from 'prop-types';
5
+ import { translate as __ } from 'foremanReact/common/I18n';
6
+ import { Form, FormGroup, Checkbox } from '@patternfly/react-core';
7
+ import { selectEnvironmentPaths, selectEnvironmentPathsStatus } from './EnvironmentPathSelectors';
8
+ import EnvironmentLabels from '../EnvironmentLabels';
9
+ import './EnvironmentPaths.scss';
10
+ import Loading from '../../../../components/Loading';
11
+
12
+ const EnvironmentPaths = ({ userCheckedItems, setUserCheckedItems }) => {
13
+ const environmentPathResponse = useSelector(selectEnvironmentPaths);
14
+ const environmentPathStatus = useSelector(selectEnvironmentPathsStatus);
15
+ const environmentPathLoading = environmentPathStatus === STATUS.PENDING;
16
+
17
+ const oncheckedChange = (checked, env) => {
18
+ if (checked) {
19
+ setUserCheckedItems([...userCheckedItems, env]);
20
+ } else {
21
+ setUserCheckedItems(userCheckedItems.filter(item => item.id !== env.id));
22
+ }
23
+ };
24
+ if (environmentPathLoading) {
25
+ return <Loading />;
26
+ }
27
+ const { results } = environmentPathResponse || {};
28
+ /* eslint-disable react/no-array-index-key */
29
+ return (
30
+ <>
31
+ <p
32
+ style={{ marginBottom: '2px' }}
33
+ >{__('Select a lifecycle environment from the available promotion paths to promote new version.')}
34
+ </p>
35
+ <Form>{results.map((path, count) => {
36
+ const {
37
+ environments,
38
+ } = path || {};
39
+ return (
40
+ <React.Fragment key={count}>
41
+ <FormGroup key={`fg-${count}`} isInline fieldId="environment-checkbox-group">
42
+ {environments.map(env =>
43
+ (<Checkbox
44
+ isChecked={env.library ||
45
+ userCheckedItems.filter(item => item.id === env.id).length}
46
+ isDisabled={env.library}
47
+ style={{ marginRight: '3px', marginBottom: '1px' }}
48
+ className="env-labels-with-pointer"
49
+ key={`${env.id}${count}`}
50
+ id={`${env.id}${count}`}
51
+ label={<EnvironmentLabels environments={env} />}
52
+ aria-label={env.label}
53
+ onChange={checked => oncheckedChange(checked, env)}
54
+ />))}
55
+ </FormGroup>
56
+ <hr key={`hr${count}`} style={{ margin: '0em' }} />
57
+ </React.Fragment>
58
+ );
59
+ })}
60
+ </Form>
61
+ </>
62
+ );
63
+ /* eslint-enable react/no-array-index-key */
64
+ };
65
+
66
+ EnvironmentPaths.propTypes = {
67
+ userCheckedItems: PropTypes.arrayOf(PropTypes.shape({})).isRequired,
68
+ setUserCheckedItems: PropTypes.func.isRequired,
69
+ };
70
+
71
+
72
+ export default EnvironmentPaths;
@@ -0,0 +1,8 @@
1
+ .env-labels-with-pointer {
2
+ &:not(:last-child):after {
3
+ content: '>';
4
+ margin-top: 3px;
5
+ }
6
+ display: inline-flex;
7
+ margin-top: 3px;
8
+ }
@@ -0,0 +1,85 @@
1
+ import React, { useState, useEffect } from 'react';
2
+ import { useDispatch, useSelector } from 'react-redux';
3
+ import useDeepCompareEffect from 'use-deep-compare-effect';
4
+ import { InProgressIcon } from '@patternfly/react-icons';
5
+ import PropTypes from 'prop-types';
6
+ import { Progress,
7
+ ProgressSize,
8
+ ProgressMeasureLocation,
9
+ ProgressVariant } from '@patternfly/react-core';
10
+ import { STATUS } from 'foremanReact/constants';
11
+ import { stopPollingTask, toastTaskFinished } from '../../../Tasks/TaskActions';
12
+ import { selectTaskPoll, selectTaskPollStatus } from '../../Details/ContentViewDetailSelectors';
13
+
14
+ const TaskPresenter = ({ activeHistory, setPollingFinished }) => {
15
+ const { task } = activeHistory;
16
+ const dispatch = useDispatch();
17
+ const [polling, setPolling] = useState(true);
18
+ const [taskErrored, setTaskErrored] = useState(task.result === 'error');
19
+ const pollResponse = useSelector(state =>
20
+ selectTaskPoll(state, task.id));
21
+ const pollResponseStatus = useSelector(state =>
22
+ selectTaskPollStatus(state, task.id));
23
+ const loading = pollResponseStatus === STATUS.PENDING;
24
+
25
+ const progressCompleted = () => (
26
+ pollResponse.progress ?
27
+ pollResponse.progress * 100 :
28
+ task.progress * 100
29
+ );
30
+
31
+ useEffect(() => {
32
+ if (!polling) {
33
+ const { id } = task;
34
+ dispatch(stopPollingTask(id));
35
+ dispatch(toastTaskFinished(pollResponse));
36
+ setPollingFinished(true); // Use this boolean as activeListener in refering page table
37
+ }
38
+ }, [polling, dispatch, setPollingFinished, pollResponse, task]);
39
+
40
+ useDeepCompareEffect(() => {
41
+ if (!loading && polling) {
42
+ const { state, result } = pollResponse;
43
+ if ((state === 'paused' || result === 'error') && !taskErrored) {
44
+ setTaskErrored(true);
45
+ setPolling(false);
46
+ } else if (state === 'stopped' && result === 'success') {
47
+ setPolling(false);
48
+ }
49
+ }
50
+ }, [pollResponse, loading, taskErrored, setTaskErrored, polling, setPolling]);
51
+
52
+ if (pollResponse) {
53
+ return (
54
+ <a href={`/foreman_tasks/tasks/${task.id}`} target="_blank" rel="noreferrer">
55
+ <Progress
56
+ aria-label="task_presenter"
57
+ value={progressCompleted()}
58
+ measureLocation={ProgressMeasureLocation.inside}
59
+ variant={taskErrored ? ProgressVariant.danger : ProgressVariant.default}
60
+ size={ProgressSize.sm}
61
+ />
62
+ </a>
63
+ );
64
+ }
65
+ return (
66
+ <InProgressIcon />
67
+ );
68
+ };
69
+
70
+ TaskPresenter.propTypes = {
71
+ activeHistory: PropTypes.shape({
72
+ task: PropTypes.shape({
73
+ id: PropTypes.oneOfType([
74
+ PropTypes.number,
75
+ PropTypes.string,
76
+ ]).isRequired,
77
+ result: PropTypes.string.isRequired,
78
+ progress: PropTypes.number.isRequired,
79
+ }).isRequired,
80
+ }).isRequired,
81
+ setPollingFinished: PropTypes.func.isRequired,
82
+ };
83
+
84
+
85
+ export default TaskPresenter;
@@ -1,4 +1,5 @@
1
1
  import React, { useState, useEffect } from 'react';
2
+ import useDeepCompareEffect from 'use-deep-compare-effect';
2
3
  import PropTypes from 'prop-types';
3
4
  import { useSelector, useDispatch } from 'react-redux';
4
5
  import { wrappable } from '@patternfly/react-table';
@@ -49,10 +50,6 @@ const SmartProxyContentTable = ({ smartProxyId }) => {
49
50
  },
50
51
  ];
51
52
 
52
- const fetchWithParams = () => {
53
- dispatch(getSmartProxyContent({ smartProxyId }));
54
- };
55
-
56
53
  const buildrows = (results) => {
57
54
  const newRows = [];
58
55
  let envCount = 0;
@@ -125,15 +122,19 @@ const SmartProxyContentTable = ({ smartProxyId }) => {
125
122
  setRow(newRows);
126
123
  };
127
124
 
125
+ useEffect(
126
+ () => {
127
+ dispatch(getSmartProxyContent({ smartProxyId }));
128
+ }
129
+ , [dispatch, smartProxyId],
130
+ );
128
131
 
129
- useEffect(() => fetchWithParams(), []);
130
-
131
- useEffect(() => {
132
+ useDeepCompareEffect(() => {
132
133
  if (status !== STATUS.PENDING && response) {
133
134
  const { lifecycle_environments: env } = response;
134
135
  setRows(buildrows(env));
135
136
  }
136
- }, [JSON.stringify(response), status, error]);
137
+ }, [response, status, error]);
137
138
 
138
139
 
139
140
  return (