katello 3.17.3 → 3.18.0.rc1

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

Potentially problematic release.


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

Files changed (193) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/katello/api/registry/registry_proxies_controller.rb +38 -21
  3. data/app/controllers/katello/api/rhsm/candlepin_proxies_controller.rb +3 -1
  4. data/app/controllers/katello/api/v2/activation_keys_controller.rb +10 -15
  5. data/app/controllers/katello/api/v2/api_controller.rb +2 -1
  6. data/app/controllers/katello/api/v2/content_credentials_controller.rb +1 -8
  7. data/app/controllers/katello/api/v2/content_view_components_controller.rb +31 -14
  8. data/app/controllers/katello/api/v2/content_view_repositories_controller.rb +1 -0
  9. data/app/controllers/katello/api/v2/content_view_versions_controller.rb +65 -36
  10. data/app/controllers/katello/api/v2/content_views_controller.rb +27 -25
  11. data/app/controllers/katello/api/v2/environments_controller.rb +8 -8
  12. data/app/controllers/katello/api/v2/gpg_keys_controller.rb +5 -5
  13. data/app/controllers/katello/api/v2/host_collections_controller.rb +19 -16
  14. data/app/controllers/katello/api/v2/hosts_bulk_actions_controller.rb +17 -0
  15. data/app/controllers/katello/api/v2/repositories_bulk_actions_controller.rb +1 -1
  16. data/app/controllers/katello/api/v2/repositories_controller.rb +8 -5
  17. data/app/controllers/katello/api/v2/subscriptions_controller.rb +1 -1
  18. data/app/controllers/katello/api/v2/sync_plans_controller.rb +8 -9
  19. data/app/controllers/katello/api/v2/upstream_subscriptions_controller.rb +9 -2
  20. data/app/controllers/katello/concerns/api/v2/authorization.rb +9 -5
  21. data/app/controllers/katello/concerns/api/v2/registration_controller_extensions.rb +21 -0
  22. data/app/controllers/katello/concerns/api/v2/repository_content_controller.rb +1 -1
  23. data/app/controllers/katello/concerns/organizations_controller_extensions.rb +2 -1
  24. data/app/helpers/katello/katello_urls_helper.rb +5 -2
  25. data/app/lib/actions/candlepin/product/content_create.rb +2 -0
  26. data/app/lib/actions/candlepin/product/content_update.rb +2 -0
  27. data/app/lib/actions/katello/applicability/hosts/bulk_generate.rb +2 -6
  28. data/app/lib/actions/katello/capsule_content/sync_capsule.rb +2 -6
  29. data/app/lib/actions/katello/content_view/publish.rb +3 -4
  30. data/app/lib/actions/katello/content_view_version/import.rb +4 -3
  31. data/app/lib/actions/katello/content_view_version/incremental_update.rb +18 -3
  32. data/app/lib/actions/katello/host/update_system_purpose.rb +31 -0
  33. data/app/lib/actions/katello/organization/manifest_delete.rb +0 -1
  34. data/app/lib/actions/katello/organization/manifest_import.rb +0 -1
  35. data/app/lib/actions/katello/organization/manifest_refresh.rb +0 -1
  36. data/app/lib/actions/katello/product/content_create.rb +7 -6
  37. data/app/lib/actions/katello/repository/filtered_index_content.rb +10 -1
  38. data/app/lib/actions/katello/repository/import_upload.rb +2 -1
  39. data/app/lib/actions/katello/repository/update.rb +4 -1
  40. data/app/lib/actions/pulp3/abstract_async_task.rb +0 -1
  41. data/app/lib/actions/pulp3/content_view/delete_repository_references.rb +1 -1
  42. data/app/lib/actions/pulp3/content_view_version/create_importer.rb +7 -3
  43. data/app/lib/actions/pulp3/content_view_version/export.rb +2 -1
  44. data/app/lib/actions/pulp3/content_view_version/import.rb +7 -3
  45. data/app/lib/actions/pulp3/orchestration/content_view_version/export.rb +15 -10
  46. data/app/lib/actions/pulp3/orchestration/content_view_version/import.rb +16 -10
  47. data/app/lib/actions/pulp3/repository/commit_upload.rb +2 -1
  48. data/app/lib/actions/pulp3/repository/delete.rb +1 -1
  49. data/app/lib/actions/pulp3/repository/save_artifact.rb +1 -1
  50. data/app/lib/katello/resources/candlepin/consumer.rb +2 -2
  51. data/app/lib/katello/resources/candlepin/owner.rb +5 -0
  52. data/app/lib/katello/resources/candlepin/upstream_consumer.rb +6 -0
  53. data/app/lib/katello/resources/registry.rb +3 -3
  54. data/app/models/katello/authorization/activation_key.rb +4 -0
  55. data/app/models/katello/authorization/content_view.rb +13 -0
  56. data/app/models/katello/authorization/content_view_component.rb +15 -0
  57. data/app/models/katello/authorization/gpg_key.rb +12 -4
  58. data/app/models/katello/authorization/lifecycle_environment.rb +8 -0
  59. data/app/models/katello/authorization/sync_plan.rb +16 -0
  60. data/app/models/katello/concerns/organization_extensions.rb +4 -5
  61. data/app/models/katello/concerns/redhat_extensions.rb +2 -2
  62. data/app/models/katello/concerns/smart_proxy_extensions.rb +1 -3
  63. data/app/models/katello/content_view_component.rb +2 -0
  64. data/app/models/katello/content_view_version_export_history.rb +2 -0
  65. data/app/models/katello/glue/candlepin/pool.rb +9 -14
  66. data/app/models/katello/glue/pulp/repo.rb +8 -0
  67. data/app/models/katello/gpg_key.rb +1 -1
  68. data/app/models/katello/root_repository.rb +26 -1
  69. data/app/services/katello/applicability/applicable_content_helper.rb +1 -12
  70. data/app/services/katello/candlepin/event_handler.rb +2 -0
  71. data/app/services/katello/candlepin/message_handler.rb +34 -0
  72. data/app/services/katello/candlepin/upstream_consumer.rb +28 -0
  73. data/app/services/katello/host_status_manager.rb +9 -0
  74. data/app/services/katello/pulp3/api/apt.rb +57 -0
  75. data/app/services/katello/pulp3/api/core.rb +8 -0
  76. data/app/services/katello/pulp3/content_view_version/export.rb +4 -3
  77. data/app/services/katello/pulp3/content_view_version/import.rb +5 -15
  78. data/app/services/katello/pulp3/deb.rb +38 -0
  79. data/app/services/katello/pulp3/erratum.rb +1 -2
  80. data/app/services/katello/pulp3/pulp_content_unit.rb +5 -0
  81. data/app/services/katello/pulp3/repository/ansible_collection.rb +9 -0
  82. data/app/services/katello/pulp3/repository/apt.rb +63 -0
  83. data/app/services/katello/pulp3/repository/docker.rb +4 -0
  84. data/app/services/katello/pulp3/repository/yum.rb +2 -1
  85. data/app/services/katello/pulp3/repository.rb +11 -9
  86. data/app/services/katello/pulp3/repository_mirror.rb +9 -4
  87. data/app/services/katello/pulp3/task.rb +3 -3
  88. data/app/services/katello/pulp3/task_group.rb +0 -6
  89. data/app/views/dashboard/_subscription_widget.html.erb +0 -5
  90. data/app/views/katello/api/v2/content_view_version_export_histories/show.json.rabl +1 -1
  91. data/app/views/katello/api/v2/repositories/base.json.rabl +1 -1
  92. data/app/views/overrides/organizations/_index_row_override.html.erb +1 -1
  93. data/config/routes/api/v2.rb +2 -0
  94. data/config/routes/overrides.rb +1 -0
  95. data/db/migrate/20150930183738_migrate_content_hosts.rb +1 -1
  96. data/db/migrate/20200514092553_move_katello_fields_from_hostgroups.katello.rb +2 -5
  97. data/db/migrate/20201008204114_add_os_versions_to_katello_root_repositories.rb +5 -0
  98. data/db/migrate/20201012172713_remove_gpg_key_perms.rb +23 -0
  99. data/db/migrate/20201012192035_add_metadata_to_katello_content_view_version_export_history.rb +5 -0
  100. data/db/seeds.d/111-upgrade_tasks.rb +2 -1
  101. data/engines/bastion/app/assets/javascripts/bastion/components/notification.service.js +1 -1
  102. data/engines/bastion/app/assets/javascripts/bastion/components/nutupane.factory.js +8 -13
  103. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/common/views/katello-agent-notice.html +1 -1
  104. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/content-hosts/bulk/content-hosts-bulk-system-purpose-modal.controller.js +112 -0
  105. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/content-hosts/bulk/views/content-hosts-bulk-system-purpose-modal.html +78 -0
  106. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/content-hosts/content/content-host-debs-installed.controller.js +2 -42
  107. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/content-hosts/content-host-modal-helper.service.js +11 -0
  108. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/content-hosts/content-hosts.controller.js +5 -0
  109. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/content-hosts/views/content-hosts.html +4 -0
  110. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/content-hosts/views/register-client.html +1 -1
  111. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/host-collections/details/host-collection-details.controller.js +4 -0
  112. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/host-collections/details/views/host-collection-info.html +6 -0
  113. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/hosts/host-bulk-action.factory.js +2 -1
  114. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/i18n/bastion_katello.pot +16 -14
  115. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/products/bulk/products-bulk-advanced-sync-modal.controller.js +6 -7
  116. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/products/details/repositories/details/repository-details-info.controller.js +168 -155
  117. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/products/details/repositories/details/views/repository-info.html +17 -2
  118. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/products/details/repositories/new/new-repository.controller.js +125 -113
  119. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/products/details/repositories/new/views/new-repository.html +15 -3
  120. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/products/details/repositories/os-versions.service.js +46 -0
  121. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/subscriptions/views/content-access-mode-banner.html +1 -1
  122. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/sync-plans/details/views/sync-plan-details.html +1 -1
  123. data/lib/katello/engine.rb +1 -0
  124. data/lib/katello/permission_creator.rb +68 -59
  125. data/lib/katello/permissions/host_permissions.rb +1 -0
  126. data/lib/katello/plugin.rb +4 -1
  127. data/lib/katello/repository_types/deb.rb +9 -1
  128. data/lib/katello/tasks/pulp3_content_switchover.rake +1 -3
  129. data/lib/katello/tasks/pulp3_migration_stats.rake +21 -0
  130. data/lib/katello/tasks/reports.rake +1 -4
  131. data/lib/katello/tasks/reset.rake +2 -1
  132. data/lib/katello/tasks/upgrades/3.18/add_cvv_export_history_metadata.rb +18 -0
  133. data/lib/katello/version.rb +1 -1
  134. data/locale/action_names.rb +54 -47
  135. data/locale/bn/katello.po +165 -26
  136. data/locale/cs/katello.po +164 -24
  137. data/locale/de/katello.po +165 -26
  138. data/locale/en/katello.po +164 -23
  139. data/locale/es/katello.po +165 -25
  140. data/locale/fr/katello.po +165 -25
  141. data/locale/gu/katello.po +165 -26
  142. data/locale/hi/katello.po +165 -26
  143. data/locale/it/katello.po +165 -25
  144. data/locale/ja/katello.po +165 -26
  145. data/locale/katello.pot +1036 -802
  146. data/locale/kn/katello.po +165 -26
  147. data/locale/ko/katello.po +165 -25
  148. data/locale/mr/katello.po +165 -26
  149. data/locale/or/katello.po +165 -26
  150. data/locale/pa/katello.po +165 -26
  151. data/locale/pt/katello.po +164 -23
  152. data/locale/pt_BR/katello.po +165 -25
  153. data/locale/ru/katello.po +165 -25
  154. data/locale/ta/katello.po +165 -26
  155. data/locale/te/katello.po +165 -26
  156. data/locale/zh_CN/katello.po +165 -25
  157. data/locale/zh_TW/katello.po +165 -26
  158. data/webpack/components/ActionableDetail.js +2 -1
  159. data/webpack/components/Search/Search.js +1 -1
  160. data/webpack/components/Table/MainTable.js +6 -2
  161. data/webpack/components/Table/TableWrapper.js +46 -9
  162. data/webpack/scenes/ContentViews/ContentViewSelectors.js +7 -3
  163. data/webpack/scenes/ContentViews/ContentViewsConstants.js +8 -0
  164. data/webpack/scenes/ContentViews/ContentViewsPage.js +2 -9
  165. data/webpack/scenes/ContentViews/Details/ContentViewDetailActions.js +25 -3
  166. data/webpack/scenes/ContentViews/Details/ContentViewDetailSelectors.js +14 -4
  167. data/webpack/scenes/ContentViews/Details/ContentViewDetails.js +2 -1
  168. data/webpack/scenes/ContentViews/Details/Repositories/ContentCounts.js +56 -0
  169. data/webpack/scenes/ContentViews/Details/Repositories/ContentViewRepositories.js +169 -0
  170. data/webpack/scenes/ContentViews/Details/Repositories/LastSync.js +47 -0
  171. data/webpack/scenes/ContentViews/Details/Repositories/RepoAddedStatus.js +17 -0
  172. data/webpack/scenes/ContentViews/Details/Repositories/RepoIcon.js +23 -0
  173. data/webpack/scenes/ContentViews/Details/Repositories/SelectableDropdown.js +49 -0
  174. data/webpack/scenes/ContentViews/Details/Repositories/__tests__/contentViewDetailRepos.fixtures.json +154 -0
  175. data/webpack/scenes/ContentViews/Details/Repositories/__tests__/contentViewDetailRepos.test.js +131 -0
  176. data/webpack/scenes/ContentViews/Details/__tests__/contentViewDetail.test.js +3 -0
  177. data/webpack/scenes/ContentViews/Table/ContentViewsTable.js +4 -1
  178. data/webpack/scenes/ContentViews/__tests__/contentViewPage.test.js +2 -2
  179. data/webpack/scenes/Subscriptions/Manifest/ManageManifestModal.js +16 -8
  180. data/webpack/scenes/Subscriptions/Manifest/ManifestActions.js +17 -0
  181. data/webpack/scenes/Subscriptions/Manifest/ManifestConstants.js +4 -0
  182. data/webpack/scenes/Subscriptions/Manifest/SimpleContentAccess.js +19 -2
  183. data/webpack/scenes/Subscriptions/Manifest/__tests__/SimpleContentAccess.test.js +9 -1
  184. data/webpack/scenes/Subscriptions/Manifest/index.js +2 -1
  185. data/webpack/scenes/Subscriptions/SubscriptionConstants.js +1 -1
  186. data/webpack/scenes/Subscriptions/SubscriptionReducer.js +3 -0
  187. data/webpack/scenes/Subscriptions/SubscriptionsPage.js +8 -2
  188. data/webpack/scenes/Subscriptions/SubscriptionsSelectors.js +3 -0
  189. data/webpack/scenes/Subscriptions/__tests__/SubscriptionsPage.test.js +3 -0
  190. data/webpack/scenes/Subscriptions/__tests__/SubscriptionsSelectors.test.js +6 -0
  191. data/webpack/scenes/Subscriptions/__tests__/__snapshots__/SubscriptionsSelectors.test.js.snap +6 -0
  192. data/webpack/scenes/Subscriptions/components/SubscriptionsToolbar/SubscriptionsToolbar.js +1 -13
  193. metadata +69 -26
@@ -0,0 +1,154 @@
1
+ {
2
+ "total": 14,
3
+ "subtotal": 14,
4
+ "page": 1,
5
+ "per_page": 20,
6
+ "error": null,
7
+ "search": null,
8
+ "sort": {
9
+ "by": null,
10
+ "order": null
11
+ },
12
+ "results": [
13
+ {
14
+ "backend_identifier": "dbe0d30c-e77b-47aa-8bb7-c0e1a2697eb5",
15
+ "relative_path": "Orion/Library/custom/Lagoon_Nebula/Butterfly",
16
+ "container_repository_name": null,
17
+ "full_path": "http://centos7-katello-devel-stable.example.com/pulp/repos/Orion/Library/custom/Lagoon_Nebula/Butterfly/",
18
+ "library_instance_id": null,
19
+ "version_href": "/pulp/api/v3/repositories/rpm/rpm/ce16107a-b912-4d78-a5d1-9f90066acf8e/versions/1/",
20
+ "remote_href": "/pulp/api/v3/remotes/rpm/rpm/b491c3fb-d9e6-4b7e-a95a-312a8856bf1c/",
21
+ "publication_href": "/pulp/api/v3/publications/rpm/rpm/65b57edc-2e21-4742-be99-28ae6eeb588e/",
22
+ "id": 107,
23
+ "name": "Butterfly",
24
+ "label": "Butterfly",
25
+ "description": null,
26
+ "last_sync": {
27
+ "id": "0ea3e395-4be0-4b75-96be-9b2abf4bba78",
28
+ "username": "admin",
29
+ "started_at": "2020-08-17 13:33:27 -0400",
30
+ "ended_at": "2020-08-17 13:33:33 -0400",
31
+ "state": "stopped",
32
+ "result": "success",
33
+ "progress": 1
34
+ },
35
+ "content_view": {
36
+ "id": 9,
37
+ "name": "Default Organization View"
38
+ },
39
+ "content_view_version": {
40
+ "id": 19,
41
+ "name": "Default Organization View 1.0",
42
+ "content_view_id": 9
43
+ },
44
+ "kt_environment": {
45
+ "id": 15,
46
+ "name": "Library"
47
+ },
48
+ "content_type": "yum",
49
+ "url": "https://jlsherrill.fedorapeople.org/fake-repos/needed-errata/",
50
+ "arch": "noarch",
51
+ "content_id": "1597685535659",
52
+ "auto_enabled": true,
53
+ "major": null,
54
+ "minor": null,
55
+ "product": {
56
+ "id": 3,
57
+ "cp_id": "370238709909",
58
+ "name": "Lagoon Nebula",
59
+ "orphaned": false,
60
+ "redhat": false,
61
+ "sync_plan": null
62
+ },
63
+ "content_label": "Orion_Lagoon_Nebula_Butterfly",
64
+ "content_counts": {
65
+ "ostree_branch": 0,
66
+ "docker_manifest": 0,
67
+ "docker_manifest_list": 0,
68
+ "docker_tag": 0,
69
+ "rpm": 32,
70
+ "srpm": 0,
71
+ "package": 32,
72
+ "package_group": 0,
73
+ "erratum": 4,
74
+ "puppet_module": 0,
75
+ "file": 0,
76
+ "deb": 0,
77
+ "module_stream": 0,
78
+ "ansible_collection": 0
79
+ },
80
+ "last_sync_words": "30 days",
81
+ "added_to_content_view": true
82
+ },
83
+ {
84
+ "backend_identifier": "e0aff6e3-f187-452a-9d7b-f624adf7684d",
85
+ "relative_path": "Orion/Library/custom/Lagoon_Nebula/Coma",
86
+ "container_repository_name": null,
87
+ "full_path": "http://centos7-katello-devel-stable.example.com/pulp/repos/Orion/Library/custom/Lagoon_Nebula/Coma/",
88
+ "library_instance_id": null,
89
+ "version_href": "/pulp/api/v3/repositories/rpm/rpm/a6b32606-0e72-43e5-8470-adc33a7b23d4/versions/1/",
90
+ "remote_href": "/pulp/api/v3/remotes/rpm/rpm/8f194340-7bfa-4af5-8a61-95f3e9a13fb3/",
91
+ "publication_href": "/pulp/api/v3/publications/rpm/rpm/1666923a-3269-46bd-a948-0f4f21474327/",
92
+ "id": 106,
93
+ "name": "Coma",
94
+ "label": "Coma",
95
+ "description": null,
96
+ "last_sync": {
97
+ "id": "8fd554ff-e795-4662-ab16-97466618fd2e",
98
+ "username": "admin",
99
+ "started_at": "2020-08-17 13:33:17 -0400",
100
+ "ended_at": "2020-08-17 13:33:23 -0400",
101
+ "state": "stopped",
102
+ "result": "success",
103
+ "progress": 1
104
+ },
105
+ "content_view": {
106
+ "id": 9,
107
+ "name": "Default Organization View"
108
+ },
109
+ "content_view_version": {
110
+ "id": 19,
111
+ "name": "Default Organization View 1.0",
112
+ "content_view_id": 9
113
+ },
114
+ "kt_environment": {
115
+ "id": 15,
116
+ "name": "Library"
117
+ },
118
+ "content_type": "yum",
119
+ "url": "https://inecas.fedorapeople.org/fakerepos/zoo3/",
120
+ "arch": "noarch",
121
+ "content_id": "1597685531082",
122
+ "auto_enabled": true,
123
+ "major": null,
124
+ "minor": null,
125
+ "product": {
126
+ "id": 3,
127
+ "cp_id": "370238709909",
128
+ "name": "Lagoon Nebula",
129
+ "orphaned": false,
130
+ "redhat": false,
131
+ "sync_plan": null
132
+ },
133
+ "content_label": "Orion_Lagoon_Nebula_Coma",
134
+ "content_counts": {
135
+ "ostree_branch": 1,
136
+ "docker_manifest": 2,
137
+ "docker_manifest_list": 2,
138
+ "docker_tag": 0,
139
+ "rpm": 32,
140
+ "srpm": 2,
141
+ "package": 32,
142
+ "package_group": 2,
143
+ "erratum": 4,
144
+ "puppet_module": 2,
145
+ "file": 2,
146
+ "deb": 2,
147
+ "module_stream": 2,
148
+ "ansible_collection": 2
149
+ },
150
+ "last_sync_words": "30 days",
151
+ "added_to_content_view": true
152
+ }
153
+ ]
154
+ }
@@ -0,0 +1,131 @@
1
+ import React from 'react';
2
+ import { renderWithRedux, patientlyWaitFor, fireEvent } from 'react-testing-lib-wrapper';
3
+
4
+ import nock, { nockInstance, assertNockRequest, mockAutocomplete, mockSetting } from '../../../../../test-utils/nockWrapper';
5
+ import api from '../../../../../services/api';
6
+ import CONTENT_VIEWS_KEY from '../../../ContentViewsConstants';
7
+ import ContentViewRepositories from '../ContentViewRepositories';
8
+
9
+ const repoData = require('./contentViewDetailRepos.fixtures.json');
10
+
11
+ const autocompleteUrl = '/repositories/auto_complete_search';
12
+ const renderOptions = { apiNamespace: `${CONTENT_VIEWS_KEY}_1` };
13
+ const cvAllRepos = api.getApiUrl('/content_views/1/repositories/show_all');
14
+ const cvRepos = api.getApiUrl('/content_views/1/repositories');
15
+
16
+ let firstRepo;
17
+ let searchDelayScope;
18
+ let autoSearchScope;
19
+ beforeEach(() => {
20
+ const { results } = repoData;
21
+ [firstRepo] = results;
22
+ searchDelayScope = mockSetting(nockInstance, 'autosearch_delay', 500);
23
+ autoSearchScope = mockSetting(nockInstance, 'autosearch_while_typing', true);
24
+ });
25
+
26
+ afterEach(() => {
27
+ nock.cleanAll();
28
+ assertNockRequest(searchDelayScope);
29
+ assertNockRequest(autoSearchScope);
30
+ });
31
+
32
+ test('Can call API and show repositories on page load', async (done) => {
33
+ const autocompleteScope = mockAutocomplete(nockInstance, autocompleteUrl);
34
+
35
+ const scope = nockInstance
36
+ .get(cvAllRepos)
37
+ .query(true)
38
+ .reply(200, repoData);
39
+
40
+ const { getByText, queryByText } = renderWithRedux(
41
+ <ContentViewRepositories cvId={1} />,
42
+ renderOptions,
43
+ );
44
+
45
+ // Nothing will show at first, page is loading
46
+ expect(queryByText(firstRepo.name)).toBeNull();
47
+ // Assert that the repo name is now showing on the screen, but wait for it to appear.
48
+ await patientlyWaitFor(() => expect(getByText(firstRepo.name)).toBeTruthy());
49
+
50
+
51
+ assertNockRequest(autocompleteScope);
52
+ assertNockRequest(scope, done);
53
+ });
54
+
55
+ test('Can filter by repository type', async (done) => {
56
+ const autocompleteScope = mockAutocomplete(nockInstance, autocompleteUrl);
57
+
58
+ const allTypesScope = nockInstance
59
+ .get(cvAllRepos)
60
+ .query(true)
61
+ .reply(200, repoData);
62
+
63
+ // With the yum checkbox unchecked, we can expect the query params to not include 'yum'
64
+ const noYumScope = nockInstance
65
+ .get(cvAllRepos)
66
+ .query(queryObj => queryObj.content_type === 'yum')
67
+ .reply(200, repoData);
68
+
69
+ const { getByLabelText } = renderWithRedux(<ContentViewRepositories cvId={1} />, renderOptions);
70
+
71
+ // Patternfly's Select component makes it hard to attach a label, the existing options aren't
72
+ // working as expected, so querying by container label and getting first button to open dropdown
73
+ const toggleContainer = getByLabelText('select Type container');
74
+ const toggleButton = toggleContainer.querySelector('button');
75
+ fireEvent.click(toggleButton); // Open type dropdown
76
+ fireEvent.click(getByLabelText('select Yum repositories')); // select yum repos
77
+
78
+ assertNockRequest(autocompleteScope);
79
+ assertNockRequest(allTypesScope);
80
+ assertNockRequest(noYumScope, done);
81
+ });
82
+
83
+ test('Can filter by Not added status', async (done) => {
84
+ const autocompleteScope = mockAutocomplete(nockInstance, autocompleteUrl);
85
+
86
+ const allStatusScope = nockInstance
87
+ .get(cvAllRepos)
88
+ .query(true)
89
+ .reply(200, repoData);
90
+
91
+ const notAddedScope = nockInstance
92
+ .get(cvRepos)
93
+ .query(params => params.available_for === 'content_view')
94
+ .reply(200, repoData);
95
+
96
+ const { getByLabelText } = renderWithRedux(<ContentViewRepositories cvId={1} />, renderOptions);
97
+
98
+ const toggleContainer = getByLabelText('select Status container');
99
+ const toggleButton = toggleContainer.querySelector('button');
100
+ fireEvent.click(toggleButton);
101
+ fireEvent.click(getByLabelText('select Not added'));
102
+
103
+ assertNockRequest(autocompleteScope);
104
+ assertNockRequest(allStatusScope);
105
+ assertNockRequest(notAddedScope, done);
106
+ });
107
+
108
+ test('Can filter by Added status', async (done) => {
109
+ const autocompleteScope = mockAutocomplete(nockInstance, autocompleteUrl);
110
+
111
+ const allStatusScope = nockInstance
112
+ .get(cvAllRepos)
113
+ .query(true)
114
+ .reply(200, repoData);
115
+
116
+ const addedScope = nockInstance
117
+ .get(cvRepos)
118
+ .query(true)
119
+ .reply(200, repoData);
120
+
121
+ const { getByLabelText } = renderWithRedux(<ContentViewRepositories cvId={1} />, renderOptions);
122
+
123
+ const toggleContainer = getByLabelText('select Status container');
124
+ const toggleButton = toggleContainer.querySelector('button');
125
+ fireEvent.click(toggleButton);
126
+ fireEvent.click(getByLabelText('select Added'));
127
+
128
+ assertNockRequest(autocompleteScope);
129
+ assertNockRequest(allStatusScope);
130
+ assertNockRequest(addedScope, done);
131
+ });
@@ -11,6 +11,9 @@ const cvDetailData = require('./contentViewDetails.fixtures.json');
11
11
  const renderOptions = { apiNamespace: `${CONTENT_VIEWS_KEY}_1` };
12
12
  const cvDetailsPath = api.getApiUrl('/content_views/1');
13
13
 
14
+ // The Repositories tab will load in the background, prevent this by mocking
15
+ jest.mock('../Repositories/ContentViewRepositories.js', () => () => 'mocked!');
16
+
14
17
  test('Can call API and show details on page load', async (done) => {
15
18
  const { label, name, description } = cvDetailData;
16
19
  const scope = nockInstance
@@ -11,6 +11,7 @@ import getContentViews from '../ContentViewsActions';
11
11
  const ContentViewTable = ({ response, status, error }) => {
12
12
  const [table, setTable] = useState({ rows: [], columns: [] });
13
13
  const [rowMapping, setRowMapping] = useState({});
14
+ const [searchQuery, updateSearchQuery] = useState('');
14
15
  const { results, ...metadata } = response;
15
16
  const loadingResponse = status === STATUS.PENDING;
16
17
 
@@ -28,7 +29,7 @@ const ContentViewTable = ({ response, status, error }) => {
28
29
  [results, JSON.stringify(rowMapping)], // use JSON to check obj values eq not reference eq
29
30
  );
30
31
 
31
- const onSelect = (event, isSelected, rowId) => {
32
+ const onSelect = (_event, isSelected, rowId) => {
32
33
  let rows;
33
34
  if (rowId === -1) {
34
35
  rows = table.rows.map(row => ({ ...row, selected: isSelected }));
@@ -90,6 +91,8 @@ const ContentViewTable = ({ response, status, error }) => {
90
91
  onSelect,
91
92
  onExpand,
92
93
  actionResolver,
94
+ searchQuery,
95
+ updateSearchQuery,
93
96
  }}
94
97
  status={tableStatus()}
95
98
  fetchItems={getContentViews}
@@ -3,7 +3,6 @@ import React from 'react';
3
3
  import { renderWithRedux, patientlyWaitFor, fireEvent } from 'react-testing-lib-wrapper';
4
4
 
5
5
  import CONTENT_VIEWS_KEY from '../ContentViewsConstants';
6
- import { createContentViewsParams } from '../ContentViewsActions';
7
6
  import ContentViewsPage from '../../ContentViews';
8
7
  import api from '../../../services/api';
9
8
  import nock, {
@@ -136,7 +135,8 @@ test('Can handle pagination', async (done) => {
136
135
  // Match first page API request
137
136
  const firstPageScope = nockInstance
138
137
  .get(cvIndexPath)
139
- .query(createContentViewsParams())
138
+ // Using a custom query params matcher because parameters can be strings
139
+ .query(actualQueryObject => parseInt(actualQueryObject.page, 10) === 1)
140
140
  .reply(200, cvIndexFirstPage);
141
141
 
142
142
  // Match second page API request
@@ -93,6 +93,7 @@ class ManageManifestModal extends Component {
93
93
  isManifestImported,
94
94
  canEditOrganizations,
95
95
  simpleContentAccess,
96
+ simpleContentAccessEligible,
96
97
  enableSimpleContentAccess,
97
98
  disableSimpleContentAccess,
98
99
  taskInProgress,
@@ -104,6 +105,8 @@ class ManageManifestModal extends Component {
104
105
  const showSubscriptionManifest = (canImportManifest || canDeleteManifest);
105
106
  const showManifestTab = (showRedHatProviderDetails || showSubscriptionManifest);
106
107
  const disableSCASwitch = (
108
+ // allow users to turn SCA off even if they are not eligible to turn it back on
109
+ (!simpleContentAccessEligible && !simpleContentAccess) ||
107
110
  disableManifestActions ||
108
111
  !isManifestImported ||
109
112
  actionInProgress ||
@@ -194,14 +197,17 @@ class ManageManifestModal extends Component {
194
197
  <Grid>
195
198
  <h3>{__('Subscription Manifest')}</h3>
196
199
  <hr />
197
- <Row>
198
- <SimpleContentAccess
199
- enableSimpleContentAccess={enableSimpleContentAccess}
200
- disableSimpleContentAccess={disableSimpleContentAccess}
201
- isSimpleContentAccessEnabled={simpleContentAccess}
202
- canToggleSimpleContentAccess={!disableSCASwitch}
203
- />
204
- </Row>
200
+ { isManifestImported &&
201
+ <Row>
202
+ <SimpleContentAccess
203
+ enableSimpleContentAccess={enableSimpleContentAccess}
204
+ disableSimpleContentAccess={disableSimpleContentAccess}
205
+ isSimpleContentAccessEnabled={simpleContentAccess}
206
+ canToggleSimpleContentAccess={!disableSCASwitch}
207
+ simpleContentAccessEligible={simpleContentAccessEligible}
208
+ />
209
+ </Row>
210
+ }
205
211
  <Row>
206
212
  <Col sm={5}>
207
213
  <strong>{__('Subscription Allocation')}</strong>
@@ -325,6 +331,7 @@ ManageManifestModal.propTypes = {
325
331
  saveOrganization: PropTypes.func.isRequired,
326
332
  taskInProgress: PropTypes.bool.isRequired,
327
333
  simpleContentAccess: PropTypes.bool,
334
+ simpleContentAccessEligible: PropTypes.bool,
328
335
  manifestHistory: PropTypes.shape({
329
336
  loading: PropTypes.bool,
330
337
  // Disabling rule as existing code failed due to an eslint-plugin-react update
@@ -344,6 +351,7 @@ ManageManifestModal.defaultProps = {
344
351
  isManifestImported: false,
345
352
  canEditOrganizations: false,
346
353
  simpleContentAccess: false,
354
+ simpleContentAccessEligible: undefined,
347
355
  manifestActionStarted: false,
348
356
  };
349
357
 
@@ -22,6 +22,9 @@ import {
22
22
  DISABLE_SIMPLE_CONTENT_ACCESS_REQUEST,
23
23
  DISABLE_SIMPLE_CONTENT_ACCESS_SUCCESS,
24
24
  DISABLE_SIMPLE_CONTENT_ACCESS_FAILURE,
25
+ SIMPLE_CONTENT_ACCESS_ELIGIBLE_REQUEST,
26
+ SIMPLE_CONTENT_ACCESS_ELIGIBLE_SUCCESS,
27
+ SIMPLE_CONTENT_ACCESS_ELIGIBLE_FAILURE,
25
28
  } from './ManifestConstants';
26
29
 
27
30
  export const uploadManifest = file => async (dispatch) => {
@@ -81,6 +84,20 @@ export const deleteManifest = (extendedParams = {}) => async (dispatch) => {
81
84
  }
82
85
  };
83
86
 
87
+ export const checkSimpleContentAccessEligible = () => async (dispatch) => {
88
+ dispatch({ type: SIMPLE_CONTENT_ACCESS_ELIGIBLE_REQUEST });
89
+
90
+ try {
91
+ const { data } = await api.get(`/organizations/${orgId()}/upstream_subscriptions/simple_content_access/eligible`, {});
92
+ return dispatch({
93
+ type: SIMPLE_CONTENT_ACCESS_ELIGIBLE_SUCCESS,
94
+ response: data,
95
+ });
96
+ } catch (error) {
97
+ return dispatch(apiError(SIMPLE_CONTENT_ACCESS_ELIGIBLE_FAILURE, error));
98
+ }
99
+ };
100
+
84
101
  export const enableSimpleContentAccess = (extendedParams = {}) => async (dispatch) => {
85
102
  dispatch({ type: ENABLE_SIMPLE_CONTENT_ACCESS_REQUEST });
86
103
 
@@ -22,6 +22,10 @@ export const DISABLE_SIMPLE_CONTENT_ACCESS_REQUEST = 'DISABLE_SIMPLE_CONTENT_ACC
22
22
  export const DISABLE_SIMPLE_CONTENT_ACCESS_SUCCESS = 'DISABLE_SIMPLE_CONTENT_ACCESS_SUCCESS';
23
23
  export const DISABLE_SIMPLE_CONTENT_ACCESS_FAILURE = 'DISABLE_SIMPLE_CONTENT_ACCESS_FAILURE';
24
24
 
25
+ export const SIMPLE_CONTENT_ACCESS_ELIGIBLE_REQUEST = 'SIMPLE_CONTENT_ACCESS_ELIGIBLE_REQUEST';
26
+ export const SIMPLE_CONTENT_ACCESS_ELIGIBLE_SUCCESS = 'SIMPLE_CONTENT_ACCESS_ELIGIBLE_SUCCESS';
27
+ export const SIMPLE_CONTENT_ACCESS_ELIGIBLE_FAILURE = 'SIMPLE_CONTENT_ACCESS_ELIGIBLE_FAILURE';
28
+
25
29
  // Modal IDs
26
30
  export const MANAGE_MANIFEST_MODAL_ID = 'manageManifestModal';
27
31
  export const DELETE_MANIFEST_MODAL_ID = 'deleteManifestModal';
@@ -11,6 +11,7 @@ const SimpleContentAccess = (props) => {
11
11
  isSimpleContentAccessEnabled,
12
12
  enableSimpleContentAccess,
13
13
  disableSimpleContentAccess,
14
+ simpleContentAccessEligible,
14
15
  } = props;
15
16
 
16
17
  const toggleSimpleContentAccess = () => {
@@ -21,6 +22,17 @@ const SimpleContentAccess = (props) => {
21
22
  }
22
23
  };
23
24
 
25
+ const simpleContentAccessText = () => {
26
+ // don't show this text unless explicitly told to
27
+ if (simpleContentAccessEligible !== undefined) {
28
+ if (!simpleContentAccessEligible && !isSimpleContentAccessEnabled) {
29
+ return __('Simple Content Access has been disabled by the upstream organization administrator.');
30
+ }
31
+ }
32
+
33
+ return __('Toggling Simple Content Access will refresh your manifest.');
34
+ };
35
+
24
36
  return (
25
37
  <div id="simple-content-access">
26
38
  <Col sm={5}>
@@ -36,7 +48,7 @@ const SimpleContentAccess = (props) => {
36
48
  <OverlayTrigger
37
49
  overlay={
38
50
  <Tooltip id="sca-refresh-tooltip">
39
- {__('When Simple Content Access is enabled, hosts can consume from all repositories in their Content View regardless of subscription status.')}
51
+ {__('When Simple Content Access is enabled, hosts are not required to have subscriptions attached to access repositories.')}
40
52
  </Tooltip>
41
53
  }
42
54
  placement="bottom"
@@ -61,7 +73,7 @@ const SimpleContentAccess = (props) => {
61
73
  />
62
74
  </div>
63
75
  <div>
64
- <i>{__('Toggling Simple Content Access will refresh your manifest.')}</i>
76
+ <i>{simpleContentAccessText()}</i>
65
77
  </div>
66
78
  </Col>
67
79
  </div>
@@ -73,6 +85,11 @@ SimpleContentAccess.propTypes = {
73
85
  disableSimpleContentAccess: PropTypes.func.isRequired,
74
86
  isSimpleContentAccessEnabled: PropTypes.bool.isRequired,
75
87
  canToggleSimpleContentAccess: PropTypes.bool.isRequired,
88
+ simpleContentAccessEligible: PropTypes.bool,
89
+ };
90
+
91
+ SimpleContentAccess.defaultProps = {
92
+ simpleContentAccessEligible: undefined,
76
93
  };
77
94
 
78
95
  export default SimpleContentAccess;
@@ -9,7 +9,15 @@ import api from '../../../../services/api';
9
9
  afterEach(cleanup);
10
10
 
11
11
  const noop = jest.fn();
12
- const organization = { id: 1, redhat_repository_url: 'https://redhat.com' };
12
+ const organization = {
13
+ id: 1,
14
+ redhat_repository_url: 'https://redhat.com',
15
+ owner_details: {
16
+ upstreamConsumer: {
17
+ webUrl: 'https://example.com/',
18
+ },
19
+ },
20
+ };
13
21
 
14
22
  const defaultProps = {
15
23
  disableManifestActions: false,
@@ -7,7 +7,7 @@ import * as organizationActions from '../../Organizations/OrganizationActions';
7
7
  import * as tasksActions from '../../Tasks/TaskActions';
8
8
  import history from './ManifestHistoryReducer';
9
9
  import { selectSimpleContentAccessEnabled, selectIsManifestImported } from '../../Organizations/OrganizationSelectors';
10
- import { selectManifestActionStarted } from '../SubscriptionsSelectors';
10
+ import { selectManifestActionStarted, selectSimpleContentAccessEligible } from '../SubscriptionsSelectors';
11
11
 
12
12
  import ManifestModal from './ManageManifestModal';
13
13
 
@@ -21,6 +21,7 @@ const mapStateToProps = state => ({
21
21
  isManifestImported: selectIsManifestImported(state),
22
22
  modalOpenState: state.foremanModals.ManageManifestModal,
23
23
  manifestActionStarted: selectManifestActionStarted(state),
24
+ simpleContentAccessEligible: selectSimpleContentAccessEligible(state),
24
25
  });
25
26
 
26
27
  // map action dispatchers to props
@@ -37,7 +37,7 @@ export const SUBSCRIPTIONS_CLOSE_DELETE_MODAL = 'SUBSCRIPTIONS_CLOSE_DELETE_MODA
37
37
  export const SUBSCRIPTIONS_DISABLE_DELETE_BUTTON = 'SUBSCRIPTIONS_DISABLE_DELETE_BUTTON';
38
38
  export const SUBSCRIPTIONS_ENABLE_DELETE_BUTTON = 'SUBSCRIPTIONS_ENABLE_DELETE_BUTTON';
39
39
 
40
- export const SUBSCRIPTION_WATCH_URL = 'https://cloud.redhat.com/subscriptions/rhel-sw/all';
40
+ export const SUBSCRIPTION_WATCH_URL = 'https://access.redhat.com/articles/subscription-watch';
41
41
 
42
42
  export const MANIFEST_DELETE_TASK_LABEL = 'Actions::Katello::Organization::ManifestDelete';
43
43
 
@@ -41,6 +41,7 @@ import {
41
41
  ENABLE_SIMPLE_CONTENT_ACCESS_REQUEST,
42
42
  ENABLE_SIMPLE_CONTENT_ACCESS_SUCCESS,
43
43
  DISABLE_SIMPLE_CONTENT_ACCESS_SUCCESS,
44
+ SIMPLE_CONTENT_ACCESS_ELIGIBLE_SUCCESS,
44
45
  UPLOAD_MANIFEST_FAILURE,
45
46
  UPLOAD_MANIFEST_REQUEST,
46
47
  DELETE_MANIFEST_FAILURE,
@@ -68,6 +69,8 @@ const initialState = Immutable({
68
69
 
69
70
  export default (state = initialState, action) => {
70
71
  switch (action.type) {
72
+ case SIMPLE_CONTENT_ACCESS_ELIGIBLE_SUCCESS:
73
+ return state.set('simpleContentAccessEligible', action.response.simple_content_access_eligible);
71
74
  case PING_UPSTREAM_SUBSCRIPTIONS_SUCCESS:
72
75
  return state.set('hasUpstreamConnection', true);
73
76
  case PING_UPSTREAM_SUBSCRIPTIONS_FAILURE:
@@ -51,6 +51,7 @@ class SubscriptionsPage extends Component {
51
51
  settings,
52
52
  subscriptions,
53
53
  task,
54
+ checkSimpleContentAccessEligible,
54
55
  } = this.props;
55
56
 
56
57
  const { disconnected } = settings;
@@ -79,6 +80,10 @@ class SubscriptionsPage extends Component {
79
80
  }
80
81
 
81
82
  if (hasUpstreamConnection) {
83
+ if (hasUpstreamConnection !== prevProps.hasUpstreamConnection) {
84
+ checkSimpleContentAccessEligible();
85
+ }
86
+
82
87
  const subscriptionsChanged = subscriptions.results !== prevProps.subscriptions.results;
83
88
  if (subscriptionsChanged || !this.state.availableQuantitiesLoaded) {
84
89
  const poolIds = filterRHSubscriptions(subscriptions.results).map(subs => subs.id);
@@ -267,8 +272,8 @@ class SubscriptionsPage extends Component {
267
272
  {simpleContentAccess && (
268
273
  <Alert type="info">
269
274
  This organization has Simple Content Access enabled.
270
- Hosts can consume from all repositories in their Content View regardless of
271
- subscription status. <br />
275
+ Hosts are not required to have subscriptions attached to access repositories.
276
+ <br />
272
277
  Learn more about your overall subscription usage at
273
278
  {' '}<a href={SUBSCRIPTION_WATCH_URL} target="_blank" rel="noreferrer">Subscription Watch</a>.
274
279
  </Alert>
@@ -305,6 +310,7 @@ class SubscriptionsPage extends Component {
305
310
 
306
311
  SubscriptionsPage.propTypes = {
307
312
  pingUpstreamSubscriptions: PropTypes.func.isRequired,
313
+ checkSimpleContentAccessEligible: PropTypes.func.isRequired,
308
314
  loadSubscriptions: PropTypes.func.isRequired,
309
315
  loadAvailableQuantities: PropTypes.func.isRequired,
310
316
  uploadManifest: PropTypes.func.isRequired,
@@ -29,3 +29,6 @@ export const selectManifestActionStarted = state =>
29
29
 
30
30
  export const selectHasUpstreamConnection = state =>
31
31
  selectSubscriptionsState(state).hasUpstreamConnection;
32
+
33
+ export const selectSimpleContentAccessEligible = state =>
34
+ selectSubscriptionsState(state).simpleContentAccessEligible;
@@ -6,6 +6,7 @@ import SubscriptionsPage from '../SubscriptionsPage';
6
6
  import { successState, settingsSuccessState, permissionDeniedState } from './subscriptions.fixtures';
7
7
  import { loadAvailableQuantities, loadSubscriptions, updateQuantity, loadTableColumns } from '../SubscriptionActions';
8
8
  import { pingUpstreamSubscriptions } from '../UpstreamSubscriptions/UpstreamSubscriptionsActions';
9
+ import { checkSimpleContentAccessEligible } from '../Manifest/ManifestActions';
9
10
  import { createColumns, updateColumns } from '../../../scenes/Settings/Tables/TableActions';
10
11
 
11
12
  jest.mock('foremanReact/components/PermissionDenied');
@@ -43,6 +44,7 @@ describe('subscriptions page', () => {
43
44
  loadSubscriptions={loadSubscriptions}
44
45
  loadAvailableQuantities={loadAvailableQuantities}
45
46
  pingUpstreamSubscriptions={pingUpstreamSubscriptions}
47
+ checkSimpleContentAccessEligible={checkSimpleContentAccessEligible}
46
48
  updateQuantity={updateQuantity}
47
49
  handleStartTask={handleStartTask}
48
50
  handleFinishedTask={handleFinishedTask}
@@ -78,6 +80,7 @@ describe('subscriptions page', () => {
78
80
  loadSubscriptions={loadSubscriptions}
79
81
  loadAvailableQuantities={loadAvailableQuantities}
80
82
  pingUpstreamSubscriptions={pingUpstreamSubscriptions}
83
+ checkSimpleContentAccessEligible={checkSimpleContentAccessEligible}
81
84
  updateQuantity={updateQuantity}
82
85
  handleStartTask={handleStartTask}
83
86
  handleFinishedTask={handleFinishedTask}
@@ -5,6 +5,8 @@ import {
5
5
  selectDeleteModalOpened,
6
6
  selectDeleteButtonDisabled,
7
7
  selectSubscriptionsTask,
8
+ selectHasUpstreamConnection,
9
+ selectSimpleContentAccessEligible,
8
10
  } from '../SubscriptionsSelectors';
9
11
 
10
12
  const state = {
@@ -14,6 +16,8 @@ const state = {
14
16
  deleteModalOpened: false,
15
17
  taskModalOpened: false,
16
18
  deleteButtonDisabled: true,
19
+ hasUpstreamConnection: false,
20
+ simpleContentAccessEligible: false,
17
21
  task: {},
18
22
  },
19
23
  },
@@ -25,6 +29,8 @@ const fixtures = {
25
29
  'should select delete-modal-opened': () => selectDeleteModalOpened(state),
26
30
  'should select delete-button-disabled': () => selectDeleteButtonDisabled(state),
27
31
  'should select subscriptions task': () => selectSubscriptionsTask(state),
32
+ 'should select whether we have an upstream connection': () => selectHasUpstreamConnection(state),
33
+ 'should select whether we are simple content access eligible': () => selectSimpleContentAccessEligible(state),
28
34
  };
29
35
 
30
36
  describe('Subscriptions selectors', () => testSelectorsSnapshotWithFixtures(fixtures));