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,161 @@
1
+ import React, { useState } from 'react';
2
+ import useDeepCompareEffect from 'use-deep-compare-effect';
3
+ import { Redirect } from 'react-router-dom';
4
+ import { useDispatch, useSelector } from 'react-redux';
5
+ import { STATUS } from 'foremanReact/constants';
6
+ import PropTypes from 'prop-types';
7
+ import { translate as __ } from 'foremanReact/common/I18n';
8
+ import { Modal, ModalVariant, Form, FormGroup, TextInput, ActionGroup, Button, Radio, TextArea,
9
+ Split, SplitItem, Select, SelectOption } from '@patternfly/react-core';
10
+ import { createContentViewFilter } from '../../ContentViewDetailActions';
11
+ import {
12
+ selectCreateContentViewFilter, selectCreateContentViewFilterStatus,
13
+ selectCreateContentViewFilterError,
14
+ } from '../../../Details/ContentViewDetailSelectors';
15
+ import { FILTER_TYPES } from '../../../ContentViewsConstants';
16
+ import ContentType from '../ContentType';
17
+
18
+ const CVFilterAddModal = ({ cvId, show, setIsOpen }) => {
19
+ const [inclusion, setInclusion] = useState(true);
20
+ const [name, setName] = useState('');
21
+ const [description, setDescription] = useState('');
22
+ const [type, setType] = useState('rpm');
23
+ const [saving, setSaving] = useState(false);
24
+ const [typeSelectOpen, setTypeSelectOpen] = useState(false);
25
+ const dispatch = useDispatch();
26
+ const response = useSelector(state => selectCreateContentViewFilter(state));
27
+ const status = useSelector(state => selectCreateContentViewFilterStatus(state));
28
+ const error = useSelector(state => selectCreateContentViewFilterError(state));
29
+ const [redirect, setRedirect] = useState(false);
30
+
31
+ const onSave = () => {
32
+ setSaving(true);
33
+ dispatch(createContentViewFilter(
34
+ cvId,
35
+ {
36
+ name, description, inclusion, type,
37
+ },
38
+ ));
39
+ };
40
+
41
+ const onSelect = (event, selection) => {
42
+ setType(selection);
43
+ setTypeSelectOpen(false);
44
+ };
45
+
46
+ useDeepCompareEffect(() => {
47
+ const { id } = response || {};
48
+ if (id && status === STATUS.RESOLVED && saving) {
49
+ setSaving(false);
50
+ setRedirect(true);
51
+ } else if (status === STATUS.ERROR) {
52
+ setSaving(false);
53
+ }
54
+ }, [response, status, error, saving]);
55
+
56
+ if (redirect) {
57
+ const { id } = response;
58
+ return (<Redirect to={`/labs/content_views/${cvId}#filters?subContentId=${id}`} />);
59
+ }
60
+
61
+ return (
62
+ <Modal
63
+ title={__('Create filter')}
64
+ variant={ModalVariant.small}
65
+ isOpen={show}
66
+ onClose={() => {
67
+ setIsOpen(false);
68
+ }}
69
+ appendTo={document.body}
70
+ >
71
+ <Form>
72
+ <FormGroup label={__('Name')} isRequired fieldId="name">
73
+ <TextInput
74
+ isRequired
75
+ type="text"
76
+ id="name"
77
+ aria-label="input_name"
78
+ name="name"
79
+ value={name}
80
+ onChange={value => setName(value)}
81
+ />
82
+ </FormGroup>
83
+ <FormGroup>
84
+ <Split hasGutter>
85
+ <SplitItem>
86
+ <Radio
87
+ isChecked={inclusion}
88
+ name="radio-1"
89
+ onChange={checked => setInclusion(checked)}
90
+ label="Include Filter"
91
+ id="include_filter"
92
+ value="includeFilter"
93
+ />
94
+ </SplitItem>
95
+ <SplitItem>
96
+ <Radio
97
+ isChecked={!inclusion}
98
+ name="radio-1"
99
+ onChange={checked => setInclusion(!checked)}
100
+ label="Exclude Filter"
101
+ id="exclude_filter"
102
+ value="excludeFilter"
103
+ />
104
+ </SplitItem>
105
+ </Split>
106
+ </FormGroup>
107
+ <FormGroup label={__('Content type')} isRequired fieldId="content_type">
108
+ <Select
109
+ selections={type}
110
+ onSelect={onSelect}
111
+ isOpen={typeSelectOpen}
112
+ onToggle={isExpanded => setTypeSelectOpen(isExpanded)}
113
+ id="content_type"
114
+ name="content_type"
115
+ aria-label="ContentType"
116
+ >
117
+ {
118
+ FILTER_TYPES.map(item =>
119
+ <SelectOption key={item} value={item}><ContentType type={item} /></SelectOption>)
120
+ }
121
+ </Select>
122
+ </FormGroup>
123
+ <FormGroup label={__('Description')} fieldId="description">
124
+ <TextArea
125
+ type="text"
126
+ id="description"
127
+ name="description"
128
+ aria-label="input_description"
129
+ value={description}
130
+ onChange={value => setDescription(value)}
131
+ />
132
+ </FormGroup>
133
+ <ActionGroup>
134
+ <Button
135
+ aria-label="create_filter"
136
+ variant="primary"
137
+ isDisabled={saving}
138
+ onClick={() => onSave()}
139
+ >
140
+ {__('Create filter')}
141
+ </Button>
142
+ <Button variant="link" onClick={() => setIsOpen(false)}>
143
+ {__('Cancel')}
144
+ </Button>
145
+ </ActionGroup>
146
+ </Form>
147
+ </Modal>
148
+ );
149
+ };
150
+
151
+ CVFilterAddModal.propTypes = {
152
+ cvId: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
153
+ show: PropTypes.bool.isRequired,
154
+ setIsOpen: PropTypes.func,
155
+ };
156
+
157
+ CVFilterAddModal.defaultProps = {
158
+ setIsOpen: null,
159
+ };
160
+
161
+ export default CVFilterAddModal;
@@ -0,0 +1,54 @@
1
+ import React from 'react';
2
+ import { renderWithRedux, patientlyWaitFor, fireEvent } from 'react-testing-lib-wrapper';
3
+
4
+
5
+ import api from '../../../../../../services/api';
6
+ import CVFilterAddModal from '../CVFilterAddModal';
7
+ import { nockInstance, assertNockRequest } from '../../../../../../test-utils/nockWrapper';
8
+
9
+ const cvCreateData = require('./cvFilterCreateResult.fixtures.json');
10
+
11
+ const cvCreateFilterPath = api.getApiUrl('/content_view_filters?content_view_id=5');
12
+
13
+ const setIsOpen = jest.fn();
14
+
15
+ const createDetails = {
16
+ name: 'test',
17
+ description: 'Creating filter',
18
+ inclusion: true,
19
+ type: 'rpm',
20
+ };
21
+
22
+ const createdCVDetails = { ...cvCreateData };
23
+
24
+ const form = <CVFilterAddModal cvId={5} show setIsOpen={setIsOpen} />;
25
+
26
+ test('Can save content view filter from form', (done) => {
27
+ const createFilterscope = nockInstance
28
+ .post(cvCreateFilterPath, createDetails)
29
+ .reply(201, createdCVDetails);
30
+ const { queryByText, getByLabelText } = renderWithRedux(form);
31
+ expect(queryByText('Description')).toBeInTheDocument();
32
+
33
+ fireEvent.change(getByLabelText('input_name'), { target: { value: 'test' } });
34
+ fireEvent.change(getByLabelText('input_description'), { target: { value: 'Creating filter' } });
35
+
36
+ getByLabelText('create_filter').click();
37
+ assertNockRequest(createFilterscope, done);
38
+ });
39
+
40
+ test('Closes content view filter form upon save', async (done) => {
41
+ const createFilterscope = nockInstance
42
+ .post(cvCreateFilterPath, createDetails)
43
+ .reply(201, createdCVDetails);
44
+ const { queryByText, getByLabelText } = renderWithRedux(form);
45
+ fireEvent.change(getByLabelText('input_name'), { target: { value: 'test' } });
46
+ fireEvent.change(getByLabelText('input_description'), { target: { value: 'Creating filter' } });
47
+
48
+ getByLabelText('create_filter').click();
49
+ await patientlyWaitFor(() => {
50
+ expect(queryByText('Description')).not.toBeInTheDocument();
51
+ });
52
+
53
+ assertNockRequest(createFilterscope, done);
54
+ });
@@ -0,0 +1,124 @@
1
+ {
2
+ "inclusion": true,
3
+ "id": 9,
4
+ "name": "test",
5
+ "description": "Creating filter",
6
+ "created_at": "2021-07-16 12:57:17 -0400",
7
+ "updated_at": "2021-07-16 12:57:17 -0400",
8
+ "content_view": {
9
+ "composite": false,
10
+ "component_ids": [],
11
+ "default": false,
12
+ "version_count": 2,
13
+ "latest_version": "2.0",
14
+ "latest_version_id": 7,
15
+ "auto_publish": false,
16
+ "solve_dependencies": false,
17
+ "import_only": false,
18
+ "repository_ids": [
19
+ 1
20
+ ],
21
+ "id": 5,
22
+ "name": "test_capsule_syncing",
23
+ "label": "test_capsule_syncing",
24
+ "description": "",
25
+ "organization_id": 1,
26
+ "organization": {
27
+ "name": "Default Organization",
28
+ "label": "Default_Organization",
29
+ "id": 1
30
+ },
31
+ "created_at": "2021-07-16 10:22:02 -0400",
32
+ "updated_at": "2021-07-16 12:36:29 -0400",
33
+ "last_task": {
34
+ "id": "8130bdae-3b9f-425a-b25e-55ee9f2fff74",
35
+ "result": "success",
36
+ "last_sync_words": "20 minutes"
37
+ },
38
+ "latest_version_environments": [
39
+ {
40
+ "id": 1,
41
+ "name": "Library",
42
+ "label": "Library"
43
+ },
44
+ {
45
+ "id": 2,
46
+ "name": "dev",
47
+ "label": "dev"
48
+ }
49
+ ],
50
+ "environments": [
51
+ {
52
+ "id": 1,
53
+ "name": "Library",
54
+ "label": "Library",
55
+ "permissions": {
56
+ "readable": true
57
+ }
58
+ },
59
+ {
60
+ "id": 2,
61
+ "name": "dev",
62
+ "label": "dev",
63
+ "permissions": {
64
+ "readable": true
65
+ }
66
+ }
67
+ ],
68
+ "repositories": [
69
+ {
70
+ "id": 1,
71
+ "name": "repo1",
72
+ "label": "repo1",
73
+ "content_type": "yum",
74
+ "product": {
75
+ "id": 1,
76
+ "name": "test1"
77
+ },
78
+ "content_counts": {
79
+ "ostree_branch": 0,
80
+ "docker_manifest": 0,
81
+ "docker_tag": 0,
82
+ "rpm": 8,
83
+ "package": 8,
84
+ "package_group": 0,
85
+ "erratum": 2,
86
+ "module_stream": 0
87
+ }
88
+ }
89
+ ],
90
+ "versions": [
91
+ {
92
+ "id": 6,
93
+ "version": "1.0",
94
+ "published": "2021-07-16 10:22:26 -0400",
95
+ "environment_ids": []
96
+ },
97
+ {
98
+ "id": 7,
99
+ "version": "2.0",
100
+ "published": "2021-07-16 12:36:29 -0400",
101
+ "environment_ids": [
102
+ 1,
103
+ 2
104
+ ]
105
+ }
106
+ ],
107
+ "components": [],
108
+ "content_view_components": [],
109
+ "activation_keys": [],
110
+ "hosts": [],
111
+ "next_version": "3.0",
112
+ "last_published": "2021-07-16 12:36:29 -0400",
113
+ "permissions": {
114
+ "view_content_views": true,
115
+ "edit_content_views": true,
116
+ "destroy_content_views": true,
117
+ "publish_content_views": true,
118
+ "promote_or_remove_content_views": true
119
+ }
120
+ },
121
+ "repositories": [],
122
+ "type": "package_group",
123
+ "rules": []
124
+ }
@@ -1,4 +1,5 @@
1
- import React, { useEffect, useState } from 'react';
1
+ import React, { useState, useCallback } from 'react';
2
+ import useDeepCompareEffect from 'use-deep-compare-effect';
2
3
  import PropTypes from 'prop-types';
3
4
  import { shallowEqual, useSelector } from 'react-redux';
4
5
  import { TableVariant } from '@patternfly/react-table';
@@ -38,7 +39,7 @@ const CVPackageGroupFilterContent = ({ cvId, filterId }) => {
38
39
  __('Status'),
39
40
  ];
40
41
 
41
- const buildRows = (results) => {
42
+ const buildRows = useCallback((results) => {
42
43
  const newRows = [];
43
44
  results.forEach((packageGroups) => {
44
45
  const {
@@ -62,9 +63,9 @@ const CVPackageGroupFilterContent = ({ cvId, filterId }) => {
62
63
  });
63
64
 
64
65
  return newRows;
65
- };
66
+ }, [filterId]);
66
67
 
67
- useEffect(() => {
68
+ useDeepCompareEffect(() => {
68
69
  const { results, ...meta } = response;
69
70
  setMetadata(meta);
70
71
 
@@ -72,7 +73,7 @@ const CVPackageGroupFilterContent = ({ cvId, filterId }) => {
72
73
  const newRows = buildRows(results);
73
74
  setRows(newRows);
74
75
  }
75
- }, [JSON.stringify(response)]);
76
+ }, [response, loading, buildRows]);
76
77
 
77
78
  const emptyContentTitle = __('No package groups have been added to this filter.');
78
79
  const emptyContentBody = __("Add to this filter using the 'Add package group' button.");
@@ -101,7 +102,8 @@ const CVPackageGroupFilterContent = ({ cvId, filterId }) => {
101
102
  cells={columnHeaders}
102
103
  variant={TableVariant.compact}
103
104
  autocompleteEndpoint={`/package_groups/auto_complete_search?filterid=${filterId}`}
104
- fetchItems={params => getCVFilterPackageGroups(cvId, filterId, params)}
105
+ fetchItems={useCallback(params =>
106
+ getCVFilterPackageGroups(cvId, filterId, params), [cvId, filterId])}
105
107
  />
106
108
  </div>
107
109
  </Tab>
@@ -1,4 +1,5 @@
1
- import React, { useEffect, useState } from 'react';
1
+ import React, { useState, useCallback } from 'react';
2
+ import useDeepCompareEffect from 'use-deep-compare-effect';
2
3
  import PropTypes from 'prop-types';
3
4
  import { shallowEqual, useSelector } from 'react-redux';
4
5
  import { TableVariant } from '@patternfly/react-table';
@@ -42,7 +43,7 @@ const CVRpmFilterContent = ({ filterId, inclusion }) => {
42
43
  return 'All versions';
43
44
  };
44
45
 
45
- const buildRows = (results) => {
46
+ const buildRows = useCallback((results) => {
46
47
  const newRows = [];
47
48
  results.forEach((rule) => {
48
49
  const { name, architecture } = rule;
@@ -56,9 +57,9 @@ const CVRpmFilterContent = ({ filterId, inclusion }) => {
56
57
  });
57
58
 
58
59
  return newRows;
59
- };
60
+ }, []);
60
61
 
61
- useEffect(() => {
62
+ useDeepCompareEffect(() => {
62
63
  const { results, ...meta } = response;
63
64
  setMetadata(meta);
64
65
 
@@ -66,7 +67,7 @@ const CVRpmFilterContent = ({ filterId, inclusion }) => {
66
67
  const newRows = buildRows(results);
67
68
  setRows(newRows);
68
69
  }
69
- }, [JSON.stringify(response)]);
70
+ }, [response, loading, buildRows]);
70
71
 
71
72
  const emptyContentTitle = __('No rules have been added to this filter.');
72
73
  const emptyContentBody = __("Add to this filter using the 'Add RPM' button.");
@@ -95,7 +96,7 @@ const CVRpmFilterContent = ({ filterId, inclusion }) => {
95
96
  cells={columnHeaders}
96
97
  variant={TableVariant.compact}
97
98
  autocompleteEndpoint={`/content_view_filters/${filterId}/rules/auto_complete_search`}
98
- fetchItems={params => getCVFilterRules(filterId, params)}
99
+ fetchItems={useCallback(params => getCVFilterRules(filterId, params), [filterId])}
99
100
  />
100
101
  </div>
101
102
  </Tab>
@@ -1,4 +1,5 @@
1
1
  import React, { useEffect, useState } from 'react';
2
+ import useDeepCompareEffect from 'use-deep-compare-effect';
2
3
  import { useParams } from 'react-router-dom';
3
4
  import { shallowEqual, useSelector, useDispatch } from 'react-redux';
4
5
  import { STATUS } from 'foremanReact/constants';
@@ -25,11 +26,11 @@ const ContentViewFilterDetails = () => {
25
26
 
26
27
  useEffect(() => {
27
28
  dispatch(getCVFilterDetails(cvId, filterId));
28
- }, []);
29
+ }, [dispatch, cvId, filterId]);
29
30
 
30
- useEffect(() => {
31
+ useDeepCompareEffect(() => {
31
32
  if (loaded) setDetails(response);
32
- }, [JSON.stringify(response), loaded]);
33
+ }, [response, loaded]);
33
34
 
34
35
  const { type, inclusion } = details;
35
36
 
@@ -1,6 +1,7 @@
1
- import React, { useState, useEffect } from 'react';
2
- import { shallowEqual, useSelector } from 'react-redux';
3
- import { Label } from '@patternfly/react-core';
1
+ import React, { useState, useEffect, useCallback } from 'react';
2
+ import useDeepCompareEffect from 'use-deep-compare-effect';
3
+ import { shallowEqual, useSelector, useDispatch } from 'react-redux';
4
+ import { Label, Split, SplitItem, Button, Dropdown, DropdownItem, KebabToggle } from '@patternfly/react-core';
4
5
  import { TableVariant } from '@patternfly/react-table';
5
6
  import { STATUS } from 'foremanReact/constants';
6
7
  import LongDateTime from 'foremanReact/components/common/dates/LongDateTime';
@@ -10,7 +11,7 @@ import PropTypes from 'prop-types';
10
11
 
11
12
  import TableWrapper from '../../../../components/Table/TableWrapper';
12
13
  import onSelect from '../../../../components/Table/helpers';
13
- import { getContentViewFilters } from '../ContentViewDetailActions';
14
+ import { deleteContentViewFilter, deleteContentViewFilters, getContentViewFilters } from '../ContentViewDetailActions';
14
15
  import {
15
16
  selectCVFilters,
16
17
  selectCVFiltersStatus,
@@ -18,10 +19,12 @@ import {
18
19
  } from '../ContentViewDetailSelectors';
19
20
  import { truncate } from '../../../../utils/helpers';
20
21
  import ContentType from './ContentType';
22
+ import CVFilterAddModal from './Add/CVFilterAddModal';
21
23
 
22
24
  const cvFilterUrl = (cvId, filterId) => `/labs/content_views/${cvId}#filters?subContentId=${filterId}`;
23
25
 
24
26
  const ContentViewFilters = ({ cvId }) => {
27
+ const dispatch = useDispatch();
25
28
  const response = useSelector(state => selectCVFilters(state, cvId), shallowEqual);
26
29
  const status = useSelector(state => selectCVFiltersStatus(state, cvId), shallowEqual);
27
30
  const error = useSelector(state => selectCVFiltersError(state, cvId), shallowEqual);
@@ -29,6 +32,12 @@ const ContentViewFilters = ({ cvId }) => {
29
32
  const [metadata, setMetadata] = useState({});
30
33
  const [searchQuery, updateSearchQuery] = useState('');
31
34
  const loading = status === STATUS.PENDING;
35
+ const [addModalOpen, setAddModalOpen] = useState(false);
36
+ const [bulkActionOpen, setBulkActionOpen] = useState(false);
37
+ const [bulkActionEnabled, setBulkActionEnabled] = useState(false);
38
+ const toggleBulkAction = () => setBulkActionOpen(prevState => !prevState);
39
+
40
+ const openAddModal = () => setAddModalOpen(true);
32
41
 
33
42
  const columnHeaders = [
34
43
  __('Name'),
@@ -38,14 +47,14 @@ const ContentViewFilters = ({ cvId }) => {
38
47
  __('Inclusion type'),
39
48
  ];
40
49
 
41
- const buildRows = (results) => {
50
+ const buildRows = useCallback((results) => {
42
51
  const newRows = [];
43
52
  results.forEach((filter) => {
44
53
  let errataByDate = false;
45
54
  const {
46
- id, name, type, description, updated_at: updatedAt, inclusion,
55
+ id, name, type, description, updated_at: updatedAt, inclusion, rules,
47
56
  } = filter;
48
- if (filter.type === 'erratum' && filter.rules[0].types) errataByDate = true;
57
+ if (type === 'erratum' && rules[0]?.types) errataByDate = true;
49
58
 
50
59
  const cells = [
51
60
  { title: (type === 'package_group' || type === 'rpm') ? <Link to={cvFilterUrl(cvId, id)}>{name}</Link> : name },
@@ -60,12 +69,24 @@ const ContentViewFilters = ({ cvId }) => {
60
69
  },
61
70
  ];
62
71
 
63
- newRows.push({ cells });
72
+ newRows.push({ cells, id });
64
73
  });
65
74
  return newRows;
75
+ }, [cvId]);
76
+
77
+ const bulkRemove = () => {
78
+ setBulkActionOpen(false);
79
+ const filterIds = rows.filter(({ selected }) => selected).map(({ id }) => id);
80
+ dispatch(deleteContentViewFilters(cvId, filterIds, () =>
81
+ dispatch(getContentViewFilters(cvId, {}))));
66
82
  };
67
83
 
68
84
  useEffect(() => {
85
+ const rowsAreSelected = rows.some(row => row.selected);
86
+ setBulkActionEnabled(rowsAreSelected);
87
+ }, [rows]);
88
+
89
+ useDeepCompareEffect(() => {
69
90
  const { results, ...meta } = response;
70
91
  setMetadata(meta);
71
92
 
@@ -73,13 +94,22 @@ const ContentViewFilters = ({ cvId }) => {
73
94
  const newRows = buildRows(results);
74
95
  setRows(newRows);
75
96
  }
76
- }, [JSON.stringify(response)]);
97
+ }, [response, loading, buildRows]);
98
+
99
+ const actionResolver = () => [
100
+ {
101
+ title: __('Remove'),
102
+ onClick: (_event, _rowId, { id }) => {
103
+ dispatch(deleteContentViewFilter(id, () =>
104
+ dispatch(getContentViewFilters(cvId, {}))));
105
+ },
106
+ },
107
+ ];
77
108
 
78
109
  const emptyContentTitle = __("You currently don't have any filters for this content view.");
79
110
  const emptyContentBody = __("Add filters using the 'Add filter' button above."); // needs link
80
111
  const emptySearchTitle = __('No matching filters found');
81
112
  const emptySearchBody = __('Try changing your search settings.');
82
-
83
113
  return (
84
114
  <TableWrapper
85
115
  {...{
@@ -93,13 +123,42 @@ const ContentViewFilters = ({ cvId }) => {
93
123
  updateSearchQuery,
94
124
  error,
95
125
  status,
126
+ actionResolver,
96
127
  }}
97
128
  onSelect={onSelect(rows, setRows)}
98
129
  cells={columnHeaders}
99
130
  variant={TableVariant.compact}
100
131
  autocompleteEndpoint="/content_view_filters/auto_complete_search"
101
- fetchItems={params => getContentViewFilters(cvId, params)}
102
- />);
132
+ fetchItems={useCallback(params => getContentViewFilters(cvId, params), [cvId])}
133
+ >
134
+ <Split hasGutter>
135
+ <SplitItem>
136
+ <Button onClick={openAddModal} variant="secondary" aria-label="create_filter">
137
+ {__('Create filter')}
138
+ </Button>
139
+ </SplitItem>
140
+ <SplitItem>
141
+ <Dropdown
142
+ toggle={<KebabToggle aria-label="bulk_actions" onToggle={toggleBulkAction} />}
143
+ isOpen={bulkActionOpen}
144
+ isPlain
145
+ dropdownItems={[
146
+ <DropdownItem aria-label="bulk_remove" key="bulk_remove" isDisabled={!bulkActionEnabled} component="button" onClick={bulkRemove}>
147
+ {__('Remove')}
148
+ </DropdownItem>]
149
+ }
150
+ />
151
+ </SplitItem>
152
+ </Split>
153
+ {addModalOpen &&
154
+ <CVFilterAddModal
155
+ cvId={cvId}
156
+ show={addModalOpen}
157
+ setIsOpen={setAddModalOpen}
158
+ aria-label="add_filter_modal"
159
+ />
160
+ }
161
+ </TableWrapper>);
103
162
  };
104
163
 
105
164
 
@@ -90,3 +90,80 @@ test('Can search for filter', async (done) => {
90
90
  assertNockRequest(initialScope);
91
91
  assertNockRequest(searchResultScope, done);
92
92
  });
93
+
94
+ test('Can remove a filter', async (done) => {
95
+ const { id } = firstFilter;
96
+ const autocompleteScope = mockAutocomplete(nockInstance, autocompleteUrl);
97
+
98
+ const getContentViewScope = nockInstance
99
+ .get(cvFilters)
100
+ .query(true)
101
+ .reply(200, cvFilterFixtures);
102
+
103
+ const removeFilterScope = nockInstance
104
+ .delete(api.getApiUrl(`/content_view_filters/${id}`))
105
+ .query(true)
106
+ .reply(200, {});
107
+
108
+ const callbackGetContentViewScope = nockInstance
109
+ .get(cvFilters)
110
+ .query(true)
111
+ .reply(200, {});
112
+
113
+ const { getAllByLabelText, getByText } = renderWithRedux(
114
+ <ContentViewFilters cvId={1} />,
115
+ renderOptions,
116
+ );
117
+
118
+ await patientlyWaitFor(() => {
119
+ expect(getAllByLabelText('Actions')[0]).toHaveAttribute('aria-expanded', 'false');
120
+ });
121
+ fireEvent.click(getAllByLabelText('Actions')[0]);
122
+ expect(getAllByLabelText('Actions')[0]).toHaveAttribute('aria-expanded', 'true');
123
+ await patientlyWaitFor(() => expect(getByText('Remove')).toBeInTheDocument());
124
+ fireEvent.click(getByText('Remove'));
125
+
126
+ assertNockRequest(autocompleteScope);
127
+ assertNockRequest(getContentViewScope);
128
+ assertNockRequest(removeFilterScope);
129
+ assertNockRequest(callbackGetContentViewScope, done);
130
+ });
131
+
132
+ test('Can remove multiple filters', async (done) => {
133
+ const autocompleteScope = mockAutocomplete(nockInstance, autocompleteUrl);
134
+ const getContentViewScope = nockInstance
135
+ .get(cvFilters)
136
+ .query(true)
137
+ .reply(200, cvFilterFixtures);
138
+
139
+ const removeFilterScope = nockInstance
140
+ .put(
141
+ api.getApiUrl('/content_views/1/remove_filters'),
142
+ { filter_ids: [4, 5, 6, 7, 8, 9] },
143
+ )
144
+ .reply(200, {});
145
+
146
+ const callbackGetContentViewScope = nockInstance
147
+ .get(cvFilters)
148
+ .query(true)
149
+ .reply(200, {});
150
+
151
+ const { getAllByLabelText, getByLabelText, getByText } = renderWithRedux(
152
+ <ContentViewFilters cvId={1} />,
153
+ renderOptions,
154
+ );
155
+
156
+ await patientlyWaitFor(() => {
157
+ fireEvent.click(getByLabelText('Select all rows'));
158
+ expect(getAllByLabelText('bulk_actions')[0]).toHaveAttribute('aria-expanded', 'false');
159
+ });
160
+ fireEvent.click(getAllByLabelText('bulk_actions')[0]);
161
+ expect(getAllByLabelText('bulk_actions')[0]).toHaveAttribute('aria-expanded', 'true');
162
+ await patientlyWaitFor(() => expect(getByText('Remove')).toBeInTheDocument());
163
+ fireEvent.click(getByText('Remove'));
164
+
165
+ assertNockRequest(autocompleteScope);
166
+ assertNockRequest(getContentViewScope);
167
+ assertNockRequest(removeFilterScope);
168
+ assertNockRequest(callbackGetContentViewScope, done);
169
+ });