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
@@ -0,0 +1,153 @@
1
+ import React, { useState, useEffect } from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import { Modal, ModalVariant, FormSelect,
4
+ FormSelectOption, Checkbox, Form, FormGroup,
5
+ ActionGroup, Button } from '@patternfly/react-core';
6
+ import { useDispatch, useSelector, shallowEqual } from 'react-redux';
7
+ import { translate as __ } from 'foremanReact/common/I18n';
8
+ import { STATUS } from 'foremanReact/constants';
9
+ import { selectCVDetails,
10
+ selectCVDetailStatus,
11
+ selectCVDetailError } from '../../Details/ContentViewDetailSelectors';
12
+ import { addComponent } from '../ContentViewDetailActions';
13
+
14
+ const ComponentContentViewAddModal = ({
15
+ cvId, componentCvId, componentId, latest, show, setIsOpen,
16
+ }) => {
17
+ const dispatch = useDispatch();
18
+ const componentDetails = useSelector(
19
+ state => selectCVDetails(state, componentCvId),
20
+ shallowEqual,
21
+ );
22
+ const componentStatus = useSelector(
23
+ state => selectCVDetailStatus(state, componentCvId),
24
+ shallowEqual,
25
+ );
26
+ const componentError = useSelector(
27
+ state => selectCVDetailError(state, componentCvId),
28
+ shallowEqual,
29
+ );
30
+ const [cvName, setCvName] = useState('');
31
+ const [options, setOptions] = useState([]);
32
+ const [formLatest, setFormLatest] = useState(componentId ? latest : false);
33
+ const [selected, setSelected] = useState(null);
34
+ const versionsLoading = componentStatus === STATUS.PENDING;
35
+
36
+ useEffect(() => {
37
+ if (!versionsLoading && componentDetails) {
38
+ const { name, versions } = componentDetails;
39
+ const versionMutable = versions;
40
+ setCvName(name);
41
+ const opt = versionMutable.map(item => ({ value: item.id, label: __(`Version ${item.version}`) }));
42
+ setOptions([...opt].reverse());
43
+ setSelected(opt.slice(-1)[0].value);
44
+ }
45
+ }, [componentDetails, componentStatus, componentError, versionsLoading]);
46
+
47
+ const getAddParams = () => {
48
+ if (formLatest) {
49
+ return [{ latest: true, content_view_id: componentCvId }];
50
+ }
51
+ return [{ content_view_version_id: selected }];
52
+ };
53
+
54
+ const getUpdateParams = () => {
55
+ if (formLatest) {
56
+ return { id: componentId, latest: formLatest, compositeContentViewId: cvId };
57
+ }
58
+ return { id: componentId, compositeContentViewId: cvId, content_view_version_id: selected };
59
+ };
60
+
61
+ const updateLatest = (checked) => {
62
+ setFormLatest(checked);
63
+ if (checked) setSelected(options[0]);
64
+ };
65
+
66
+ const onSubmit = () => {
67
+ if (componentId) {
68
+ dispatch(addComponent({
69
+ compositeContentViewId: cvId,
70
+ components: getUpdateParams(),
71
+ }));
72
+ } else {
73
+ dispatch(addComponent({
74
+ compositeContentViewId: cvId,
75
+ components: getAddParams(),
76
+ }));
77
+ }
78
+ setIsOpen(false);
79
+ };
80
+
81
+ if (show && versionsLoading) return null;
82
+
83
+ return (
84
+ <Modal
85
+ title={componentId ? __('Update version') : __('Add component')}
86
+ variant={ModalVariant.small}
87
+ isOpen={show}
88
+ description={__(`Select available version of ${cvName} to use`)}
89
+ onClose={() => {
90
+ setIsOpen(false);
91
+ }}
92
+ appendTo={document.body}
93
+ >
94
+ <Form>
95
+ <FormGroup label={__('Version')} isRequired fieldId="version">
96
+ <FormSelect
97
+ value={selected}
98
+ isDisabled={formLatest || options.length === 1}
99
+ onChange={value => setSelected(value)}
100
+ id="horzontal-form-title"
101
+ name="horizontal-form-title"
102
+ aria-label="CvVersion"
103
+ >
104
+ {options.map((option, index) => (
105
+ // eslint-disable-next-line react/no-array-index-key
106
+ <FormSelectOption key={index} value={option.value} label={option.label} />
107
+ ))}
108
+ </FormSelect>
109
+ </FormGroup>
110
+ <FormGroup fieldId="latest">
111
+ <Checkbox
112
+ style={{ marginTop: 0 }}
113
+ id="latest"
114
+ name="latest"
115
+ label={__('Always update to latest version')}
116
+ isChecked={formLatest}
117
+ onChange={checked => updateLatest(checked)}
118
+ />
119
+ </FormGroup>
120
+ <ActionGroup>
121
+ <Button aria-label="add_component" variant="primary" onClick={() => onSubmit()}>{__('Submit')}</Button>
122
+ <Button variant="link" onClick={() => setIsOpen(false)}>{__('Cancel')}</Button>
123
+ </ActionGroup>
124
+ </Form>
125
+ </Modal>);
126
+ };
127
+
128
+ ComponentContentViewAddModal.propTypes = {
129
+ cvId: PropTypes.oneOfType([
130
+ PropTypes.number,
131
+ PropTypes.string,
132
+ ]).isRequired,
133
+ componentCvId: PropTypes.oneOfType([
134
+ PropTypes.number,
135
+ PropTypes.string,
136
+ ]).isRequired,
137
+ componentId: PropTypes.oneOfType([
138
+ PropTypes.number,
139
+ PropTypes.string,
140
+ ]),
141
+ latest: PropTypes.bool,
142
+ show: PropTypes.bool,
143
+ setIsOpen: PropTypes.func,
144
+ };
145
+
146
+ ComponentContentViewAddModal.defaultProps = {
147
+ componentId: null,
148
+ latest: false,
149
+ show: false,
150
+ setIsOpen: null,
151
+ };
152
+
153
+ export default ComponentContentViewAddModal;
@@ -10,33 +10,44 @@ import {
10
10
  } from '@patternfly/react-core';
11
11
 
12
12
  const ComponentVersion = ({ componentCV }) => {
13
- const { latest, content_view_version: cvVersion, content_view: cv } = componentCV;
13
+ const {
14
+ id: componentId, latest, content_view_version: cvVersion, content_view: cv,
15
+ } = componentCV;
14
16
  const {
15
17
  id,
16
18
  latest_version: latestVersion,
17
19
  } = cv;
18
- const { version } = cvVersion;
20
+ const { version } = cvVersion || {};
21
+ const noVersionText = __('Not yet published');
19
22
  const latestDescription = __('Latest (automatically updates)');
20
23
  const manualVersionText = (latestVersion === version) ? __('Latest version') : __(`New version is available: Version ${latestVersion}`);
24
+ if (componentId) {
25
+ return (
26
+ <>
27
+ <Link to={urlBuilder('labs/content_views', '', id)}>{version ? `Version ${version}` : noVersionText}</Link>
28
+ <TextContent>
29
+ <Text component={TextVariants.small}>
30
+ {latest ? latestDescription : manualVersionText}
31
+ </Text>
32
+ </TextContent>
33
+ </>
34
+ );
35
+ }
21
36
  return (
22
- <>
23
- <Link to={urlBuilder('labs/content_views', '', id)}> Version {version}</Link>
24
- <TextContent>
25
- <Text component={TextVariants.small}>{latest ? latestDescription : manualVersionText}</Text>
26
- </TextContent>
27
- </>
37
+ <Link to={urlBuilder('labs/content_views', '', id)}>{latestVersion ? `Version ${latestVersion}` : noVersionText}</Link>
28
38
  );
29
39
  };
30
40
 
31
41
  ComponentVersion.propTypes = {
32
42
  componentCV: PropTypes.shape({
43
+ id: PropTypes.number,
33
44
  latest: PropTypes.bool.isRequired,
34
45
  content_view_version: PropTypes.shape({
35
- version: PropTypes.string.isRequired,
46
+ version: PropTypes.string,
36
47
  }),
37
48
  content_view: PropTypes.shape({
38
49
  id: PropTypes.number.isRequired,
39
- latest_version: PropTypes.string.isRequired,
50
+ latest_version: PropTypes.string,
40
51
  }),
41
52
  }).isRequired,
42
53
  };
@@ -1,10 +1,10 @@
1
- import React, { useState, useEffect } from 'react';
2
- import { useSelector } from 'react-redux';
3
- import {
4
- Bullseye,
5
- } from '@patternfly/react-core';
1
+ import React, { useState, useCallback } from 'react';
2
+ import useDeepCompareEffect from 'use-deep-compare-effect';
3
+ import { useDispatch, useSelector } from 'react-redux';
4
+ import { Bullseye, Split, SplitItem, Button } from '@patternfly/react-core';
6
5
  import { Link } from 'react-router-dom';
7
6
  import { TableVariant, fitContent, TableText } from '@patternfly/react-table';
7
+ import { PencilAltIcon } from '@patternfly/react-icons';
8
8
  import { STATUS } from 'foremanReact/constants';
9
9
  import { translate as __ } from 'foremanReact/common/I18n';
10
10
  import { urlBuilder } from 'foremanReact/common/urlHelpers';
@@ -16,21 +16,39 @@ import {
16
16
  selectCVComponents,
17
17
  selectCVComponentsStatus,
18
18
  selectCVComponentsError,
19
+ selectCVComponentAddStatus,
20
+ selectCVComponentRemoveStatus,
19
21
  } from '../ContentViewDetailSelectors';
20
- import { getContentViewComponents } from '../ContentViewDetailActions';
22
+ import getContentViewDetails, {
23
+ addComponent, getContentViewComponents,
24
+ removeComponent,
25
+ } from '../ContentViewDetailActions';
21
26
  import AddedStatusLabel from '../../../../components/AddedStatusLabel';
22
27
  import ComponentVersion from './ComponentVersion';
23
28
  import ComponentEnvironments from './ComponentEnvironments';
24
29
  import ContentViewIcon from '../../components/ContentViewIcon';
30
+ import { ADDED, ALL_STATUSES, NOT_ADDED } from '../../ContentViewsConstants';
31
+ import SelectableDropdown from '../../../../components/SelectableDropdown/SelectableDropdown';
32
+ import '../../../../components/EditableTextInput/editableTextInput.scss';
33
+ import ComponentContentViewAddModal from './ComponentContentViewAddModal';
25
34
 
26
35
  const ContentViewComponents = ({ cvId, details }) => {
27
36
  const response = useSelector(state => selectCVComponents(state, cvId));
28
37
  const status = useSelector(state => selectCVComponentsStatus(state, cvId));
29
38
  const error = useSelector(state => selectCVComponentsError(state, cvId));
30
-
39
+ const componentAddedStatus = useSelector(state => selectCVComponentAddStatus(state, cvId));
40
+ const componentRemovedStatus = useSelector(state => selectCVComponentRemoveStatus(state, cvId));
31
41
  const [rows, setRows] = useState([]);
32
42
  const [metadata, setMetadata] = useState({});
33
43
  const [searchQuery, updateSearchQuery] = useState('');
44
+ const [statusSelected, setStatusSelected] = useState(ALL_STATUSES);
45
+ const [versionEditing, setVersionEditing] = useState(false);
46
+ const [compositeCvEditing, setCompositeCvEditing] = useState(null);
47
+ const [componentCvEditing, setComponentCvEditing] = useState(null);
48
+ const [componentLatest, setComponentLatest] = useState(false);
49
+ const [componentId, setComponentId] = useState(null);
50
+ const dispatch = useDispatch();
51
+
34
52
  const columnHeaders = [
35
53
  { title: __('Type'), transforms: [fitContent] },
36
54
  { title: __('Name') },
@@ -41,12 +59,53 @@ const ContentViewComponents = ({ cvId, details }) => {
41
59
  { title: __('Description') },
42
60
  ];
43
61
  const loading = status === STATUS.PENDING;
62
+ const addComponentsResolved = componentAddedStatus === STATUS.RESOLVED;
63
+ const removeComponentsResolved = componentRemovedStatus === STATUS.RESOLVED;
64
+
44
65
  const { label } = details || {};
45
66
 
46
- const buildRows = (results) => {
67
+ const bulkRemoveEnabled = () => rows.some(row => row.selected && row.added);
68
+
69
+ const onAdd = useCallback(({
70
+ componentCvId, published, added, latest,
71
+ }) => {
72
+ if (published) {
73
+ dispatch(getContentViewDetails(componentCvId));
74
+ setVersionEditing(true);
75
+ setCompositeCvEditing(cvId);
76
+ setComponentCvEditing(componentCvId);
77
+ setComponentLatest(latest);
78
+ setComponentId(added);
79
+ } else {
80
+ dispatch(addComponent({
81
+ compositeContentViewId: cvId,
82
+ components: [{ latest: true, content_view_id: componentCvId }],
83
+ }));
84
+ }
85
+ }, [cvId, dispatch]);
86
+
87
+ const removeBulk = () => {
88
+ const componentIds = [];
89
+ rows.forEach(row => row.selected && componentIds.push(row.added));
90
+ dispatch(removeComponent({
91
+ compositeContentViewId: cvId,
92
+ component_ids: componentIds,
93
+ }));
94
+ };
95
+
96
+ const onRemove = (componentIdToRemove) => {
97
+ dispatch(removeComponent({
98
+ compositeContentViewId: cvId,
99
+ component_ids: [componentIdToRemove],
100
+ }));
101
+ };
102
+
103
+ const buildRows = useCallback((results) => {
47
104
  const newRows = [];
48
105
  results.forEach((componentCV) => {
49
- const { content_view: cv, content_view_version: cvVersion } = componentCV;
106
+ const {
107
+ id: componentCvId, content_view: cv, content_view_version: cvVersion, latest,
108
+ } = componentCV;
50
109
  const { environments, repositories } = cvVersion || {};
51
110
  const {
52
111
  id,
@@ -57,25 +116,72 @@ const ContentViewComponents = ({ cvId, details }) => {
57
116
  const cells = [
58
117
  { title: <Bullseye><ContentViewIcon composite={false} /></Bullseye> },
59
118
  { title: <Link to={urlBuilder('labs/content_views', '', id)}>{name}</Link> },
60
- { title: cvVersion ? <ComponentVersion {...{ componentCV }} /> : 'Not yet published' },
61
- { title: environments ? <ComponentEnvironments {...{ environments }} /> : 'Not yet published' },
119
+ {
120
+ title:
121
+ <Split>
122
+ <SplitItem>
123
+ <ComponentVersion {...{ componentCV }} />
124
+ </SplitItem>
125
+ {componentCvId && cvVersion &&
126
+ <SplitItem>
127
+ <Button
128
+ className="foreman-edit-icon"
129
+ aria-label="edit_version"
130
+ variant="plain"
131
+ onClick={() => {
132
+ onAdd({
133
+ componentCvId: id, published: cvVersion, added: componentCvId, latest,
134
+ });
135
+ }}
136
+ >
137
+ <PencilAltIcon />
138
+ </Button>
139
+ </SplitItem>}
140
+ </Split>,
141
+ },
142
+ { title: environments ? <ComponentEnvironments {...{ environments }} /> : __('Not yet published') },
62
143
  { title: <Link to={urlBuilder(`labs/content_views/${id}#repositories`, '')}>{ repositories ? repositories.length : 0 }</Link> },
63
144
  {
64
- title: <AddedStatusLabel added />,
145
+ title: <AddedStatusLabel added={componentCvId} />,
65
146
  },
66
- { title: <TableText wrapModifier="truncate">{description || 'No description'}</TableText> },
147
+ { title: <TableText wrapModifier="truncate">{description || __('No description')}</TableText> },
67
148
  ];
68
- newRows.push({ cells });
149
+ newRows.push({
150
+ componentCvId: id, added: componentCvId, published: cvVersion, latest, cells,
151
+ });
69
152
  });
70
153
  return newRows;
71
- };
154
+ }, [onAdd]);
155
+
156
+ const actionResolver = (rowData, { _rowIndex }) => [
157
+ {
158
+ title: __('Add'),
159
+ isDisabled: rowData.added,
160
+ onClick: (_event, rowId, rowInfo) => {
161
+ onAdd({
162
+ componentCvId: rowInfo.componentCvId,
163
+ published: rowInfo.published,
164
+ added: rowInfo.added,
165
+ latest: rowInfo.latest,
166
+ });
167
+ },
168
+ },
169
+ {
170
+ title: __('Remove'),
171
+ isDisabled: !rowData.added,
172
+ onClick: (_event, rowId, rowInfo) => {
173
+ onRemove(rowInfo.added);
174
+ },
175
+ },
176
+ ];
72
177
 
73
178
  const emptyContentTitle = __(`No content views belong to ${label}`);
74
179
  const emptyContentBody = __('Please add some content views.');
75
180
  const emptySearchTitle = __('No matching content views found');
76
181
  const emptySearchBody = __('Try changing your search settings.');
182
+ const activeFilters = statusSelected && statusSelected !== ALL_STATUSES;
77
183
 
78
- useEffect(() => {
184
+ useDeepCompareEffect(() => {
79
185
  const { results, ...meta } = response;
80
186
  setMetadata(meta);
81
187
 
@@ -83,7 +189,7 @@ const ContentViewComponents = ({ cvId, details }) => {
83
189
  const newRows = buildRows(results);
84
190
  setRows(newRows);
85
191
  }
86
- }, [JSON.stringify(response)]);
192
+ }, [response, buildRows, loading]);
87
193
 
88
194
  return (
89
195
  <TableWrapper
@@ -98,13 +204,45 @@ const ContentViewComponents = ({ cvId, details }) => {
98
204
  updateSearchQuery,
99
205
  error,
100
206
  status,
207
+ activeFilters,
208
+ actionResolver,
101
209
  }}
102
210
  onSelect={onSelect(rows, setRows)}
103
211
  cells={columnHeaders}
104
212
  variant={TableVariant.compact}
105
213
  autocompleteEndpoint="/content_views/auto_complete_search"
106
- fetchItems={params => getContentViewComponents(cvId, params)}
107
- />
214
+ fetchItems={useCallback(params =>
215
+ getContentViewComponents(cvId, params, statusSelected), [cvId, statusSelected])}
216
+ additionalListeners={[statusSelected, addComponentsResolved, removeComponentsResolved]}
217
+ >
218
+ <Split hasGutter>
219
+ <SplitItem>
220
+ <SelectableDropdown
221
+ items={[ADDED, NOT_ADDED, ALL_STATUSES]}
222
+ title={__('Status')}
223
+ selected={statusSelected}
224
+ setSelected={setStatusSelected}
225
+ placeholderText={__('Status')}
226
+ />
227
+ </SplitItem>
228
+ <SplitItem>
229
+ <Button onClick={removeBulk} isDisabled={!(bulkRemoveEnabled())} variant="secondary" aria-label="remove_components">
230
+ {__('Remove content views')}
231
+ </Button>
232
+ </SplitItem>
233
+ </Split>
234
+ {versionEditing &&
235
+ <ComponentContentViewAddModal
236
+ cvId={compositeCvEditing}
237
+ componentCvId={componentCvEditing}
238
+ componentId={componentId}
239
+ latest={componentLatest}
240
+ show={versionEditing}
241
+ setIsOpen={setVersionEditing}
242
+ aria-label="copy_content_view_modal"
243
+ />
244
+ }
245
+ </TableWrapper>
108
246
  );
109
247
  };
110
248