katello 5.0.0.rc2 → 5.0.0

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.
Files changed (243) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/javascripts/katello/locale/bn/katello.js +41 -2
  3. data/app/assets/javascripts/katello/locale/bn_IN/katello.js +41 -2
  4. data/app/assets/javascripts/katello/locale/ca/katello.js +41 -2
  5. data/app/assets/javascripts/katello/locale/cs/katello.js +41 -2
  6. data/app/assets/javascripts/katello/locale/cs_CZ/katello.js +42 -3
  7. data/app/assets/javascripts/katello/locale/de/katello.js +42 -3
  8. data/app/assets/javascripts/katello/locale/de_AT/katello.js +41 -2
  9. data/app/assets/javascripts/katello/locale/de_DE/katello.js +41 -2
  10. data/app/assets/javascripts/katello/locale/el/katello.js +41 -2
  11. data/app/assets/javascripts/katello/locale/en/katello.js +41 -2
  12. data/app/assets/javascripts/katello/locale/en_GB/katello.js +41 -2
  13. data/app/assets/javascripts/katello/locale/en_US/katello.js +41 -2
  14. data/app/assets/javascripts/katello/locale/es/katello.js +42 -3
  15. data/app/assets/javascripts/katello/locale/et_EE/katello.js +41 -2
  16. data/app/assets/javascripts/katello/locale/fr/katello.js +42 -3
  17. data/app/assets/javascripts/katello/locale/gl/katello.js +41 -2
  18. data/app/assets/javascripts/katello/locale/gu/katello.js +41 -2
  19. data/app/assets/javascripts/katello/locale/he_IL/katello.js +41 -2
  20. data/app/assets/javascripts/katello/locale/hi/katello.js +41 -2
  21. data/app/assets/javascripts/katello/locale/id/katello.js +41 -2
  22. data/app/assets/javascripts/katello/locale/it/katello.js +42 -3
  23. data/app/assets/javascripts/katello/locale/ja/katello.js +42 -3
  24. data/app/assets/javascripts/katello/locale/ka/katello.js +42 -3
  25. data/app/assets/javascripts/katello/locale/kn/katello.js +41 -2
  26. data/app/assets/javascripts/katello/locale/ko/katello.js +42 -3
  27. data/app/assets/javascripts/katello/locale/ml_IN/katello.js +41 -2
  28. data/app/assets/javascripts/katello/locale/mr/katello.js +41 -2
  29. data/app/assets/javascripts/katello/locale/nl_NL/katello.js +41 -2
  30. data/app/assets/javascripts/katello/locale/or/katello.js +41 -2
  31. data/app/assets/javascripts/katello/locale/pa/katello.js +41 -2
  32. data/app/assets/javascripts/katello/locale/pl/katello.js +41 -2
  33. data/app/assets/javascripts/katello/locale/pl_PL/katello.js +41 -2
  34. data/app/assets/javascripts/katello/locale/pt/katello.js +41 -2
  35. data/app/assets/javascripts/katello/locale/pt_BR/katello.js +42 -3
  36. data/app/assets/javascripts/katello/locale/ro/katello.js +41 -2
  37. data/app/assets/javascripts/katello/locale/ro_RO/katello.js +41 -2
  38. data/app/assets/javascripts/katello/locale/ru/katello.js +42 -3
  39. data/app/assets/javascripts/katello/locale/sl/katello.js +41 -2
  40. data/app/assets/javascripts/katello/locale/sv_SE/katello.js +41 -2
  41. data/app/assets/javascripts/katello/locale/ta/katello.js +41 -2
  42. data/app/assets/javascripts/katello/locale/ta_IN/katello.js +41 -2
  43. data/app/assets/javascripts/katello/locale/te/katello.js +41 -2
  44. data/app/assets/javascripts/katello/locale/tr/katello.js +41 -2
  45. data/app/assets/javascripts/katello/locale/vi/katello.js +41 -2
  46. data/app/assets/javascripts/katello/locale/vi_VN/katello.js +41 -2
  47. data/app/assets/javascripts/katello/locale/zh/katello.js +41 -2
  48. data/app/assets/javascripts/katello/locale/zh_CN/katello.js +42 -3
  49. data/app/assets/javascripts/katello/locale/zh_TW/katello.js +42 -3
  50. data/app/controllers/katello/concerns/api/v2/hosts_controller_extensions.rb +10 -10
  51. data/app/controllers/katello/concerns/registration_commands_controller_extensions.rb +1 -1
  52. data/app/models/katello/concerns/smart_proxy_extensions.rb +1 -1
  53. data/app/views/katello/api/v2/activation_keys/base.json.rabl +18 -18
  54. data/app/views/katello/api/v2/content_facet/base.json.rabl +19 -19
  55. data/app/views/katello/api/v2/content_view_environments/show.json.rabl +10 -10
  56. data/app/views/katello/api/v2/content_view_versions/base.json.rabl +2 -2
  57. data/db/migrate/20220929204746_add_content_view_environment_content_facet.rb +5 -5
  58. data/db/migrate/20240730163043_add_content_view_environment_activation_key.rb +5 -5
  59. data/db/migrate/20260402151630_add_content_view_environment_to_hostgroup_content_facet.rb +4 -4
  60. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/activation-keys/details/views/activation-key-info.html +1 -1
  61. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/activation-keys/new/views/activation-key-new.html +1 -1
  62. data/lib/katello/tasks/upgrades/4.20/clean_cve_with_no_cvv.rake +4 -4
  63. data/lib/katello/version.rb +1 -1
  64. data/lib/rubocop/cop/katello/cve_abbreviation.rb +82 -0
  65. data/locale/bn/LC_MESSAGES/katello.mo +0 -0
  66. data/locale/bn/katello.po +41 -2
  67. data/locale/bn_IN/LC_MESSAGES/katello.mo +0 -0
  68. data/locale/bn_IN/katello.po +41 -2
  69. data/locale/ca/LC_MESSAGES/katello.mo +0 -0
  70. data/locale/ca/katello.po +41 -2
  71. data/locale/cs/LC_MESSAGES/katello.mo +0 -0
  72. data/locale/cs/katello.po +41 -2
  73. data/locale/cs_CZ/LC_MESSAGES/katello.mo +0 -0
  74. data/locale/cs_CZ/katello.po +42 -3
  75. data/locale/de/LC_MESSAGES/katello.mo +0 -0
  76. data/locale/de/katello.po +42 -3
  77. data/locale/de_AT/LC_MESSAGES/katello.mo +0 -0
  78. data/locale/de_AT/katello.po +41 -2
  79. data/locale/de_DE/LC_MESSAGES/katello.mo +0 -0
  80. data/locale/de_DE/katello.po +41 -2
  81. data/locale/el/LC_MESSAGES/katello.mo +0 -0
  82. data/locale/el/katello.po +41 -2
  83. data/locale/en/LC_MESSAGES/katello.mo +0 -0
  84. data/locale/en/katello.po +41 -2
  85. data/locale/en_GB/LC_MESSAGES/katello.mo +0 -0
  86. data/locale/en_GB/katello.po +41 -2
  87. data/locale/en_US/LC_MESSAGES/katello.mo +0 -0
  88. data/locale/en_US/katello.po +41 -2
  89. data/locale/es/LC_MESSAGES/katello.mo +0 -0
  90. data/locale/es/katello.po +42 -3
  91. data/locale/et_EE/LC_MESSAGES/katello.mo +0 -0
  92. data/locale/et_EE/katello.po +41 -2
  93. data/locale/fr/LC_MESSAGES/katello.mo +0 -0
  94. data/locale/fr/katello.po +42 -3
  95. data/locale/gl/LC_MESSAGES/katello.mo +0 -0
  96. data/locale/gl/katello.po +41 -2
  97. data/locale/gu/LC_MESSAGES/katello.mo +0 -0
  98. data/locale/gu/katello.po +41 -2
  99. data/locale/he_IL/LC_MESSAGES/katello.mo +0 -0
  100. data/locale/he_IL/katello.po +41 -2
  101. data/locale/hi/LC_MESSAGES/katello.mo +0 -0
  102. data/locale/hi/katello.po +41 -2
  103. data/locale/id/LC_MESSAGES/katello.mo +0 -0
  104. data/locale/id/katello.po +41 -2
  105. data/locale/it/LC_MESSAGES/katello.mo +0 -0
  106. data/locale/it/katello.po +42 -3
  107. data/locale/ja/LC_MESSAGES/katello.mo +0 -0
  108. data/locale/ja/katello.po +42 -3
  109. data/locale/ka/LC_MESSAGES/katello.mo +0 -0
  110. data/locale/ka/katello.po +42 -3
  111. data/locale/katello.pot +315 -257
  112. data/locale/kn/LC_MESSAGES/katello.mo +0 -0
  113. data/locale/kn/katello.po +41 -2
  114. data/locale/ko/LC_MESSAGES/katello.mo +0 -0
  115. data/locale/ko/katello.po +42 -3
  116. data/locale/ml_IN/LC_MESSAGES/katello.mo +0 -0
  117. data/locale/ml_IN/katello.po +41 -2
  118. data/locale/mr/LC_MESSAGES/katello.mo +0 -0
  119. data/locale/mr/katello.po +41 -2
  120. data/locale/nl_NL/LC_MESSAGES/katello.mo +0 -0
  121. data/locale/nl_NL/katello.po +41 -2
  122. data/locale/or/LC_MESSAGES/katello.mo +0 -0
  123. data/locale/or/katello.po +41 -2
  124. data/locale/pa/LC_MESSAGES/katello.mo +0 -0
  125. data/locale/pa/katello.po +41 -2
  126. data/locale/pl/LC_MESSAGES/katello.mo +0 -0
  127. data/locale/pl/katello.po +41 -2
  128. data/locale/pl_PL/LC_MESSAGES/katello.mo +0 -0
  129. data/locale/pl_PL/katello.po +41 -2
  130. data/locale/pt/LC_MESSAGES/katello.mo +0 -0
  131. data/locale/pt/katello.po +41 -2
  132. data/locale/pt_BR/LC_MESSAGES/katello.mo +0 -0
  133. data/locale/pt_BR/katello.po +42 -3
  134. data/locale/ro/LC_MESSAGES/katello.mo +0 -0
  135. data/locale/ro/katello.po +41 -2
  136. data/locale/ro_RO/LC_MESSAGES/katello.mo +0 -0
  137. data/locale/ro_RO/katello.po +41 -2
  138. data/locale/ru/LC_MESSAGES/katello.mo +0 -0
  139. data/locale/ru/katello.po +42 -3
  140. data/locale/sl/LC_MESSAGES/katello.mo +0 -0
  141. data/locale/sl/katello.po +41 -2
  142. data/locale/sv_SE/LC_MESSAGES/katello.mo +0 -0
  143. data/locale/sv_SE/katello.po +41 -2
  144. data/locale/ta/LC_MESSAGES/katello.mo +0 -0
  145. data/locale/ta/katello.po +41 -2
  146. data/locale/ta_IN/LC_MESSAGES/katello.mo +0 -0
  147. data/locale/ta_IN/katello.po +41 -2
  148. data/locale/te/LC_MESSAGES/katello.mo +0 -0
  149. data/locale/te/katello.po +41 -2
  150. data/locale/tr/LC_MESSAGES/katello.mo +0 -0
  151. data/locale/tr/katello.po +41 -2
  152. data/locale/vi/LC_MESSAGES/katello.mo +0 -0
  153. data/locale/vi/katello.po +41 -2
  154. data/locale/vi_VN/LC_MESSAGES/katello.mo +0 -0
  155. data/locale/vi_VN/katello.po +41 -2
  156. data/locale/zh/LC_MESSAGES/katello.mo +0 -0
  157. data/locale/zh/katello.po +41 -2
  158. data/locale/zh_CN/LC_MESSAGES/katello.mo +0 -0
  159. data/locale/zh_CN/katello.po +42 -3
  160. data/locale/zh_TW/LC_MESSAGES/katello.mo +0 -0
  161. data/locale/zh_TW/katello.po +42 -3
  162. data/package.json +2 -2
  163. data/webpack/ForemanColumnExtensions/index.js +1 -1
  164. data/webpack/components/extensions/HostDetails/Cards/ContentViewDetailsCard/AssignHostCVModal.js +3 -3
  165. data/webpack/components/extensions/HostDetails/Cards/ContentViewDetailsCard/ContentViewDetailsCard.js +5 -5
  166. data/webpack/components/extensions/HostDetails/Cards/ContentViewDetailsCard/OrderableAssignments.js +5 -5
  167. data/webpack/components/extensions/HostDetails/Cards/ContentViewDetailsCard/__tests__/AssignHostCVModal.test.js +17 -17
  168. data/webpack/components/extensions/Hosts/BulkActions/BulkAssignCVEnvsModal/BulkAssignCVEnvsModal.js +4 -8
  169. data/webpack/components/extensions/Hosts/BulkActions/BulkAssignCVEnvsModal/__tests__/BulkAssignCVEnvsModal.test.js +1 -1
  170. data/webpack/components/extensions/Hosts/BulkActions/BulkAssignCVEnvsModal/index.js +2 -1
  171. data/webpack/components/extensions/Hosts/BulkActions/BulkChangeHostCollectionsModal/BulkChangeHostCollectionsModal.js +5 -10
  172. data/webpack/components/extensions/Hosts/BulkActions/BulkChangeHostCollectionsModal/__tests__/BulkChangeHostCollectionsModal.test.js +6 -24
  173. data/webpack/components/extensions/Hosts/BulkActions/BulkChangeHostCollectionsModal/index.js +2 -1
  174. data/webpack/components/extensions/Hosts/BulkActions/BulkErrataWizard/04_ReviewFooter.js +2 -0
  175. data/webpack/components/extensions/Hosts/BulkActions/BulkErrataWizard/BulkErrataWizard.js +2 -1
  176. data/webpack/components/extensions/Hosts/BulkActions/BulkManageTracesModal/BulkManageTracesModal.js +4 -0
  177. data/webpack/components/extensions/Hosts/BulkActions/BulkManageTracesModal/index.js +2 -1
  178. data/webpack/components/extensions/Hosts/BulkActions/BulkPackagesWizard/04_ReviewFooter.js +2 -0
  179. data/webpack/components/extensions/Hosts/BulkActions/BulkPackagesWizard/BulkPackagesWizard.js +2 -1
  180. data/webpack/components/extensions/Hosts/BulkActions/BulkRepositorySetsWizard/03_ReviewFooter.js +2 -0
  181. data/webpack/components/extensions/Hosts/BulkActions/BulkRepositorySetsWizard/BulkRepositorySetsWizard.js +2 -1
  182. data/webpack/components/extensions/Hosts/BulkActions/BulkSystemPurposeModal/BulkSystemPurposeModal.js +5 -0
  183. data/webpack/components/extensions/Hosts/BulkActions/BulkSystemPurposeModal/index.js +2 -1
  184. data/webpack/components/extensions/Hosts/BulkActions/__tests__/bulkSystemPurposeModal.test.js +6 -1
  185. data/webpack/components/extensions/RegistrationCommands/__tests__/ActivationKeys.test.js +6 -6
  186. data/webpack/components/extensions/RegistrationCommands/fields/ActivationKeys.js +1 -1
  187. data/webpack/containers/Application/config.js +1 -1
  188. data/webpack/eslint-rules/no-cve-abbreviation.js +121 -0
  189. data/webpack/global_index.js +6 -6
  190. data/webpack/redux/reducers/index.js +0 -2
  191. data/webpack/scenes/ActivationKeys/Details/ActivationKeyDetails.js +2 -2
  192. data/webpack/scenes/ActivationKeys/Details/components/AssignAKCVModal.js +3 -3
  193. data/webpack/scenes/ActivationKeys/Details/components/CreateAKCVModal.js +1 -1
  194. data/webpack/scenes/ActivationKeys/Details/components/{CVEDetailsCard.js → CvEnvDetailsCard.js} +9 -9
  195. data/webpack/scenes/ActivationKeys/Details/components/{CVEDetailsCreateCard.js → CvEnvDetailsCreateCard.js} +10 -10
  196. data/webpack/scenes/ActivationKeys/Details/components/__tests__/{CVEDetailsCreateCard.test.js → CvEnvDetailsCreateCard.test.js} +8 -8
  197. data/webpack/scenes/ContainerImages/Booted/BootedContainerImagesPage.js +2 -2
  198. data/webpack/scenes/ContainerImages/ContainerImagesPage.js +59 -52
  199. data/webpack/scenes/ContainerImages/ContainerImagesPage.scss +27 -4
  200. data/webpack/scenes/ContainerImages/Synced/SyncedContainerImagesPage.js +1 -1
  201. data/webpack/scenes/ContentViews/Details/Versions/Delete/RemoveSteps/CVVersionRemoveReview.js +9 -9
  202. data/webpack/scenes/Hosts/ChangeContentSource/actions.js +2 -2
  203. data/webpack/scenes/Subscriptions/Manifest/CdnTabContent.js +44 -0
  204. data/webpack/scenes/Subscriptions/Manifest/ManageManifestModal.js +179 -307
  205. data/webpack/scenes/Subscriptions/Manifest/ManageManifestModalConstants.js +5 -0
  206. data/webpack/scenes/Subscriptions/Manifest/ManifestActions.js +64 -70
  207. data/webpack/scenes/Subscriptions/Manifest/ManifestConstants.js +5 -0
  208. data/webpack/scenes/Subscriptions/Manifest/ManifestHistoryContent.js +61 -0
  209. data/webpack/scenes/Subscriptions/Manifest/ManifestHistoryReducer.js +1 -1
  210. data/webpack/scenes/Subscriptions/Manifest/ManifestTabContent.js +357 -0
  211. data/webpack/scenes/Subscriptions/Manifest/__tests__/ManageManifestModal.test.js +129 -0
  212. data/webpack/scenes/Subscriptions/Manifest/__tests__/ManifestActions.test.js +69 -110
  213. data/webpack/scenes/Subscriptions/Manifest/__tests__/ManifestHistoryReducer.test.js +6 -2
  214. data/webpack/scenes/Subscriptions/Manifest/index.js +16 -1
  215. data/webpack/scenes/Subscriptions/SubscriptionActions.js +79 -145
  216. data/webpack/scenes/Subscriptions/SubscriptionConstants.js +4 -35
  217. data/webpack/scenes/Subscriptions/SubscriptionsColumns.js +90 -0
  218. data/webpack/scenes/Subscriptions/SubscriptionsPage.js +297 -217
  219. data/webpack/scenes/Subscriptions/SubscriptionsPage.scss +40 -30
  220. data/webpack/scenes/Subscriptions/UpstreamSubscriptions/UpstreamSubscriptionsActions.js +11 -8
  221. data/webpack/scenes/Subscriptions/UpstreamSubscriptions/UpstreamSubscriptionsConstants.js +0 -1
  222. data/webpack/scenes/Subscriptions/__tests__/SubscriptionsPage.test.js +104 -205
  223. data/webpack/scenes/Subscriptions/__tests__/subscriptions.fixtures.js +12 -8
  224. data/webpack/scenes/Subscriptions/components/SubscriptionsTable/SubscriptionsTable.js +245 -241
  225. data/webpack/scenes/Subscriptions/components/SubscriptionsTable/__tests__/SubscriptionsTable.test.js +61 -27
  226. data/webpack/scenes/Subscriptions/components/SubscriptionsTable/components/Table.js +487 -73
  227. data/webpack/scenes/Subscriptions/components/SubscriptionsToolbar/SubscriptionsToolbar.js +22 -4
  228. data/webpack/utils/contentViewEnvironmentLabel.js +3 -3
  229. data/webpack/utils/helpers.js +2 -2
  230. metadata +15 -20
  231. data/webpack/scenes/Subscriptions/Manifest/ManageManifestModal.scss +0 -39
  232. data/webpack/scenes/Subscriptions/Manifest/ManifestHistoryTableSchema.js +0 -47
  233. data/webpack/scenes/Subscriptions/SubscriptionReducer.js +0 -223
  234. data/webpack/scenes/Subscriptions/SubscriptionsSelectors.js +0 -31
  235. data/webpack/scenes/Subscriptions/__tests__/SubscriptionsActions.test.js +0 -297
  236. data/webpack/scenes/Subscriptions/__tests__/SubscriptionsReducer.test.js +0 -244
  237. data/webpack/scenes/Subscriptions/__tests__/SubscriptionsSelectors.test.js +0 -47
  238. data/webpack/scenes/Subscriptions/components/SubscriptionsTable/SubscriptionNameFormatter.js +0 -23
  239. data/webpack/scenes/Subscriptions/components/SubscriptionsTable/SubscriptionTypeFormatter.js +0 -32
  240. data/webpack/scenes/Subscriptions/components/SubscriptionsTable/SubscriptionsTableSchema.js +0 -169
  241. data/webpack/scenes/Subscriptions/components/SubscriptionsTable/__tests__/SubscriptionTypeFormatter.test.js +0 -41
  242. data/webpack/scenes/Subscriptions/components/SubscriptionsTable/index.js +0 -2
  243. data/webpack/scenes/Subscriptions/index.js +0 -57
@@ -1,63 +1,95 @@
1
- import React, { useState, useEffect, useCallback, useRef } from 'react';
1
+ import React, { useState, useEffect, useCallback, useMemo, useRef } from 'react';
2
+ import { useDispatch, useSelector, shallowEqual } from 'react-redux';
2
3
  import { FormattedMessage } from 'react-intl';
3
- import PropTypes from 'prop-types';
4
- import Immutable from 'seamless-immutable';
5
4
  import { translate as __ } from 'foremanReact/common/I18n';
6
5
  import { propsToCamelCase } from 'foremanReact/common/helpers';
7
6
  import { Popover, Title, Button } from '@patternfly/react-core';
8
7
  import { OutlinedQuestionCircleIcon } from '@patternfly/react-icons';
9
8
  import ModalProgressBar from 'foremanReact/components/common/ModalProgressBar';
10
9
  import PermissionDenied from 'foremanReact/components/PermissionDenied';
11
- import PageLayout from 'foremanReact/routes/common/PageLayout/PageLayout';
12
10
  import { useCurrentUserTablePreferences } from 'foremanReact/components/PF4/TableIndexPage/Table/TableIndexHooks';
11
+ import { selectAPIResponse } from 'foremanReact/redux/API/APISelectors';
13
12
  import ManageManifestModal from './Manifest/';
14
- import { SubscriptionsTable } from './components/SubscriptionsTable';
13
+ import SubscriptionsTable from './components/SubscriptionsTable/SubscriptionsTable';
15
14
  import SubscriptionsToolbar from './components/SubscriptionsToolbar';
16
- import { filterRHSubscriptions } from './SubscriptionHelpers';
15
+ import { filterRHSubscriptions, selectSubscriptionsQuantitiesFromResponse } from './SubscriptionHelpers';
17
16
  import api, { orgId } from '../../services/api';
18
-
19
- import { createSubscriptionParams } from './SubscriptionActions.js';
20
- import { SUBSCRIPTION_TABLE_DEFAULT_COLUMNS, SUBSCRIPTIONS_SERVICE_DOC_URL } from './SubscriptionConstants';
21
- import './SubscriptionsPage.scss';
22
-
23
- const SubscriptionsPage = ({
24
- resetTasks,
17
+ import {
18
+ createSubscriptionParams,
19
+ pollTasks,
20
+ cancelPollTasks,
25
21
  handleStartTask,
26
22
  handleFinishedTask,
27
- isTaskPending,
28
- isPollingTask,
29
- hasUpstreamConnection,
30
23
  loadAvailableQuantities,
31
- organization,
32
- isManifestImported,
33
- pingUpstreamSubscriptions,
34
- subscriptions,
35
- task,
36
- cancelPollTasks,
37
- loadSubscriptions,
38
- loadTableColumns,
39
- pollTasks,
40
- deleteModalOpened,
41
- openDeleteModal,
42
- closeDeleteModal,
43
- deleteButtonDisabled,
44
- disableDeleteButton,
45
- enableDeleteButton,
46
- searchQuery,
47
- updateSearchQuery,
48
- activePermissions,
24
+ updateQuantity,
25
+ deleteSubscriptions,
26
+ } from './SubscriptionActions';
27
+ import { stopPollingTask } from '../Tasks/TaskActions';
28
+ import {
29
+ SUBSCRIPTIONS,
30
+ SUBSCRIPTION_TABLE_COLUMNS,
31
+ SUBSCRIPTION_TABLE_DEFAULT_COLUMNS,
32
+ SUBSCRIPTIONS_SERVICE_DOC_URL,
33
+ MANIFEST_DELETE_TASK_LABEL,
34
+ } from './SubscriptionConstants';
35
+ import { createSubscriptionsColumns } from './SubscriptionsColumns';
36
+ import { selectOrganizationState, selectIsManifestImported } from '../Organizations/OrganizationSelectors';
37
+ import { selectIsPollingTask, selectIsPollingTasks } from '../Tasks/TaskSelectors';
38
+ import { bulkSearchKey, pollTaskKey } from '../Tasks/helpers';
39
+ import pingUpstreamSubscriptions from './UpstreamSubscriptions/UpstreamSubscriptionsActions';
40
+ import {
41
+ PING_UPSTREAM_SUBSCRIPTIONS_SUCCESS,
42
+ } from './UpstreamSubscriptions/UpstreamSubscriptionsConstants';
43
+ import {
49
44
  uploadManifest,
50
45
  deleteManifest,
51
46
  refreshManifest,
52
- updateQuantity,
53
- deleteSubscriptions,
54
- }) => {
47
+ } from './Manifest/ManifestActions';
48
+ import './SubscriptionsPage.scss';
49
+
50
+ const buildTableColumns = enabledColumns =>
51
+ SUBSCRIPTION_TABLE_COLUMNS.map(option => ({
52
+ ...option,
53
+ value: enabledColumns.includes(option.key),
54
+ }));
55
+
56
+ const SubscriptionsPage = () => {
57
+ const dispatch = useDispatch();
58
+ const organization = useSelector(selectOrganizationState);
59
+ const isManifestImported = useSelector(selectIsManifestImported);
60
+ const isPollingTask = useSelector(state => selectIsPollingTask(state, SUBSCRIPTIONS));
61
+ const isPollingTasks = useSelector(state => selectIsPollingTasks(state, SUBSCRIPTIONS));
62
+ const taskSearchResponse = useSelector(
63
+ state => selectAPIResponse(state, bulkSearchKey(SUBSCRIPTIONS)),
64
+ shallowEqual,
65
+ );
66
+ const pollTaskResponse = useSelector(
67
+ state => selectAPIResponse(state, pollTaskKey(SUBSCRIPTIONS)),
68
+ shallowEqual,
69
+ );
70
+
55
71
  const [isManageManifestModalOpen, setIsManageManifestModalOpen] = useState(false);
56
72
  const [selectedRows, setSelectedRows] = useState([]);
57
- const [availableQuantitiesLoaded, setAvailableQuantitiesLoaded] = useState(false);
73
+ const [searchQuery, setSearchQuery] = useState('');
74
+ const [deleteModalOpened, setDeleteModalOpened] = useState(false);
75
+ const [deleteButtonDisabled, setDeleteButtonDisabled] = useState(true);
76
+ const [task, setTask] = useState(null);
77
+ const [hasUpstreamConnection, setHasUpstreamConnection] = useState(false);
78
+ const [availableQuantities, setAvailableQuantities] = useState(null);
79
+ const [loadedQuantityPoolIds, setLoadedQuantityPoolIds] = useState('');
80
+ const [activePermissions, setActivePermissions] = useState({});
81
+ const [missingPermissions, setMissingPermissions] = useState(undefined);
82
+ const [subscriptionResults, setSubscriptionResults] = useState([]);
83
+ const [selectedColumnKeys, setSelectedColumnKeys] = useState(SUBSCRIPTION_TABLE_DEFAULT_COLUMNS);
84
+ const [tableColumns, setTableColumns] =
85
+ useState(buildTableColumns(SUBSCRIPTION_TABLE_DEFAULT_COLUMNS));
86
+ const refreshSubscriptionsRef = useRef(() => {});
58
87
  const prevPropsRef = useRef({});
88
+ const finishedTaskIdsRef = useRef(new Set());
89
+ const startedTaskIdRef = useRef(null);
90
+ const quantitiesRequestTokenRef = useRef(0);
91
+ const quantitiesInFlightPoolIdsRef = useRef('');
59
92
 
60
- // Load column preferences from Foreman's table_preferences API
61
93
  const {
62
94
  columns: userColumns,
63
95
  hasPreference,
@@ -66,29 +98,61 @@ const SubscriptionsPage = ({
66
98
  tableName: 'subscriptions',
67
99
  });
68
100
 
69
- // Apply user column preferences when they load, or use defaults if none saved
70
101
  useEffect(() => {
71
102
  try {
72
- // Use saved preferences if they exist, otherwise use default columns
73
103
  const columnsToLoad = userColumns && userColumns.length > 0
74
104
  ? userColumns
75
105
  : SUBSCRIPTION_TABLE_DEFAULT_COLUMNS;
76
-
77
- loadTableColumns({ columns: columnsToLoad });
106
+ setSelectedColumnKeys((prev) => {
107
+ if (prev.length === columnsToLoad.length &&
108
+ prev.every((key, idx) => key === columnsToLoad[idx])) {
109
+ return prev;
110
+ }
111
+ return columnsToLoad;
112
+ });
113
+ setTableColumns((prev) => {
114
+ const next = buildTableColumns(columnsToLoad);
115
+ if (prev.length === next.length &&
116
+ prev.every((col, idx) => col.key === next[idx].key && col.value === next[idx].value)) {
117
+ return prev;
118
+ }
119
+ return next;
120
+ });
78
121
  } catch (error) {
79
- // If loading preferences fails, fall back to default columns
80
122
  // eslint-disable-next-line no-console
81
123
  console.error('Failed to load table column preferences:', error);
82
- loadTableColumns({ columns: SUBSCRIPTION_TABLE_DEFAULT_COLUMNS });
124
+ setSelectedColumnKeys(SUBSCRIPTION_TABLE_DEFAULT_COLUMNS);
125
+ setTableColumns(buildTableColumns(SUBSCRIPTION_TABLE_DEFAULT_COLUMNS));
83
126
  }
84
- // loadTableColumns is from bindActionCreators and has a stable reference
85
- // eslint-disable-next-line react-hooks/exhaustive-deps
86
127
  }, [userColumns]);
87
128
 
88
- const loadData = useCallback(async () => {
89
- pollTasks();
90
- loadSubscriptions();
91
- }, [pollTasks, loadSubscriptions]);
129
+ const isTaskPending = !!(
130
+ task &&
131
+ (task.pending ||
132
+ task.result === 'pending' ||
133
+ task.state === 'pending' ||
134
+ task.state === 'running' ||
135
+ task.state === 'planned')
136
+ );
137
+
138
+ const isTaskStopped = !!(task && task.state === 'stopped');
139
+
140
+ const onTaskStarted = useCallback((response) => {
141
+ setTask(response.data);
142
+ }, []);
143
+
144
+ const refreshSubscriptions = useCallback(() => {
145
+ refreshSubscriptionsRef.current();
146
+ }, []);
147
+
148
+ const onRefreshReady = useCallback((refreshFn) => {
149
+ refreshSubscriptionsRef.current = refreshFn;
150
+ }, []);
151
+
152
+ const doPingUpstream = useCallback(async () => {
153
+ const action = await dispatch(pingUpstreamSubscriptions());
154
+ setHasUpstreamConnection(action?.type === PING_UPSTREAM_SUBSCRIPTIONS_SUCCESS);
155
+ }, [dispatch]);
92
156
 
93
157
  const getDisabledReason = useCallback((deleteButton) => {
94
158
  let disabledReason = null;
@@ -110,81 +174,186 @@ const SubscriptionsPage = ({
110
174
  setSelectedRows(rows);
111
175
  }, []);
112
176
 
177
+ const handleApiResponse = useCallback((apiData) => {
178
+ setSubscriptionResults(apiData.results || []);
179
+ setActivePermissions(apiData.activePermissions || {});
180
+ setMissingPermissions(apiData.missingPermissions);
181
+ }, []);
182
+
183
+ // Start watching for pending subscription tasks on mount (e.g. bind entitlements
184
+ // after redirect from Add Subscriptions). Stop on unmount.
185
+ useEffect(() => {
186
+ dispatch(pollTasks());
187
+ return () => {
188
+ dispatch(cancelPollTasks());
189
+ dispatch(stopPollingTask(SUBSCRIPTIONS));
190
+ };
191
+ }, [dispatch]);
192
+
193
+ // Watch bulk-search results: adopt a pending task, or stop when idle.
113
194
  useEffect(() => {
114
- resetTasks();
115
- }, [resetTasks]);
195
+ if (!isPollingTasks) {
196
+ return;
197
+ }
198
+ // Wait until a real search payload arrives (avoid acting on {} / REQUEST null)
199
+ if (!taskSearchResponse || !Object.prototype.hasOwnProperty.call(taskSearchResponse, 'results')) {
200
+ return;
201
+ }
202
+ if (taskSearchResponse.results.length === 0) {
203
+ dispatch(cancelPollTasks());
204
+ return;
205
+ }
206
+ if (task) {
207
+ return;
208
+ }
209
+ const nextTask = taskSearchResponse.results.find(candidate =>
210
+ candidate?.id && !finishedTaskIdsRef.current.has(String(candidate.id)));
211
+ if (nextTask) {
212
+ setTask(nextTask);
213
+ } else {
214
+ // Only finished/stale tasks remain in the payload
215
+ dispatch(cancelPollTasks());
216
+ }
217
+ }, [taskSearchResponse, task, isPollingTasks, dispatch]);
116
218
 
117
- // Cleanup on unmount
118
- useEffect(() => () => {
119
- cancelPollTasks();
120
- }, [cancelPollTasks]);
219
+ // Keep local task in sync while polling a single task
220
+ useEffect(() => {
221
+ if (!isPollingTask || !pollTaskResponse?.id) {
222
+ return;
223
+ }
224
+ setTask((prev) => {
225
+ if (!prev || String(prev.id) !== String(pollTaskResponse.id)) {
226
+ return pollTaskResponse;
227
+ }
228
+ if (
229
+ prev.pending === pollTaskResponse.pending &&
230
+ prev.result === pollTaskResponse.result &&
231
+ prev.progress === pollTaskResponse.progress &&
232
+ prev.state === pollTaskResponse.state
233
+ ) {
234
+ return prev;
235
+ }
236
+ return pollTaskResponse;
237
+ });
238
+ }, [isPollingTask, pollTaskResponse]);
121
239
 
122
- // Handle task lifecycle
240
+ // Handle task lifecycle: start single-task poll, finish when stopped
123
241
  useEffect(() => {
124
242
  const prevProps = prevPropsRef.current;
125
243
 
126
- if (task) {
244
+ if (task?.id) {
127
245
  if (isPollingTask) {
128
- if (prevProps.isTaskPending && !isTaskPending) {
129
- handleFinishedTask(task);
246
+ const justFinished = (prevProps.isTaskPending && !isTaskPending) ||
247
+ (prevProps.isTaskPending && isTaskStopped);
248
+ if (justFinished) {
249
+ const finishedTask = task;
250
+ finishedTaskIdsRef.current.add(String(finishedTask.id));
251
+ startedTaskIdRef.current = null;
252
+ dispatch(handleFinishedTask(finishedTask, refreshSubscriptions));
253
+ if (finishedTask.label === MANIFEST_DELETE_TASK_LABEL) {
254
+ setHasUpstreamConnection(false);
255
+ } else {
256
+ doPingUpstream();
257
+ }
258
+ setTask(null);
259
+ setLoadedQuantityPoolIds('');
130
260
  }
131
- } else {
132
- handleStartTask(task);
261
+ } else if (startedTaskIdRef.current !== String(task.id)) {
262
+ startedTaskIdRef.current = String(task.id);
263
+ dispatch(handleStartTask(task));
133
264
  }
134
265
  }
135
266
 
136
- prevPropsRef.current = { ...prevPropsRef.current, isTaskPending };
137
- }, [task, isPollingTask, isTaskPending, handleStartTask, handleFinishedTask]);
267
+ prevPropsRef.current = {
268
+ ...prevPropsRef.current,
269
+ isTaskPending: isTaskPending && !isTaskStopped,
270
+ };
271
+ }, [task, isPollingTask, isTaskPending, isTaskStopped, dispatch, refreshSubscriptions,
272
+ doPingUpstream]);
138
273
 
139
274
  // Handle organization changes
275
+ const organizationId = organization?.id;
140
276
  useEffect(() => {
141
277
  const prevProps = prevPropsRef.current;
142
278
 
143
- if (organization && (!prevProps.organization ||
144
- prevProps.organization.id !== organization.id)) {
145
- loadData();
279
+ if (organizationId && prevProps.organizationId !== organizationId) {
280
+ finishedTaskIdsRef.current = new Set();
281
+ startedTaskIdRef.current = null;
282
+ setTask(null);
283
+ dispatch(cancelPollTasks());
284
+ dispatch(pollTasks());
285
+ refreshSubscriptions();
286
+ setHasUpstreamConnection(false);
287
+ setAvailableQuantities(null);
288
+ setLoadedQuantityPoolIds('');
289
+ quantitiesRequestTokenRef.current += 1;
290
+ quantitiesInFlightPoolIdsRef.current = '';
146
291
  if (isManifestImported) {
147
- pingUpstreamSubscriptions();
148
- setAvailableQuantitiesLoaded(false);
292
+ doPingUpstream();
149
293
  }
150
294
  }
151
295
 
152
- prevPropsRef.current = { ...prevPropsRef.current, organization };
153
- }, [organization, isManifestImported, loadData, pingUpstreamSubscriptions]);
296
+ prevPropsRef.current = { ...prevPropsRef.current, organizationId };
297
+ }, [organizationId, isManifestImported, dispatch, refreshSubscriptions, doPingUpstream]);
154
298
 
155
299
  // Handle available quantities loading
156
300
  useEffect(() => {
157
- if (hasUpstreamConnection && subscriptions.results) {
158
- const poolIds = filterRHSubscriptions(subscriptions.results).map(subs => subs.id);
159
- if (poolIds.length > 0 && !availableQuantitiesLoaded) {
160
- loadAvailableQuantities({ poolIds });
161
- setAvailableQuantitiesLoaded(true);
162
- }
301
+ if (!hasUpstreamConnection || subscriptionResults.length === 0) {
302
+ quantitiesRequestTokenRef.current += 1;
303
+ quantitiesInFlightPoolIdsRef.current = '';
304
+ return;
163
305
  }
164
- }, [hasUpstreamConnection, subscriptions.results, availableQuantitiesLoaded,
165
- loadAvailableQuantities]);
306
+
307
+ const poolIds = filterRHSubscriptions(subscriptionResults).map(subs => subs.id);
308
+ const poolIdsKey = [...poolIds].sort().join(',');
309
+ if (
310
+ poolIds.length === 0 ||
311
+ poolIdsKey === loadedQuantityPoolIds ||
312
+ poolIdsKey === quantitiesInFlightPoolIdsRef.current
313
+ ) {
314
+ return;
315
+ }
316
+
317
+ quantitiesRequestTokenRef.current += 1;
318
+ const requestToken = quantitiesRequestTokenRef.current;
319
+ quantitiesInFlightPoolIdsRef.current = poolIdsKey;
320
+ dispatch(loadAvailableQuantities({ poolIds }, (response) => {
321
+ if (requestToken !== quantitiesRequestTokenRef.current) {
322
+ return;
323
+ }
324
+ quantitiesInFlightPoolIdsRef.current = '';
325
+ setAvailableQuantities(selectSubscriptionsQuantitiesFromResponse(response.data));
326
+ setLoadedQuantityPoolIds(poolIdsKey);
327
+ }, () => {
328
+ if (requestToken !== quantitiesRequestTokenRef.current) {
329
+ return;
330
+ }
331
+ quantitiesInFlightPoolIdsRef.current = '';
332
+ }));
333
+ }, [
334
+ hasUpstreamConnection,
335
+ subscriptionResults,
336
+ loadedQuantityPoolIds,
337
+ dispatch,
338
+ ]);
166
339
 
167
340
  const currentOrg = orgId();
341
+ const columns = useMemo(() => createSubscriptionsColumns(), []);
168
342
 
169
- // If organization failed to load (404/403), the user doesn't have
170
- // permission to view this organization. Show permission denied
171
- // regardless of whether subscriptions returned results
172
343
  if (organization?.error && !organization.loading) {
173
344
  const statusCode = organization.error.response?.status;
174
345
 
175
346
  if (statusCode === 404 || statusCode === 403) {
176
- const errorMessage = 'You do not have permission to view this organization.';
347
+ const errorMessage = __('You do not have permission to view this organization.');
177
348
  return <PermissionDenied missingPermissions={[errorMessage]} />;
178
349
  }
179
350
  }
180
351
 
181
- // Basic permissions - should we even show this page?
182
- if (subscriptions.missingPermissions && subscriptions.missingPermissions.length > 0) {
183
- return <PermissionDenied missingPermissions={subscriptions.missingPermissions} />;
352
+ if (missingPermissions && missingPermissions.length > 0) {
353
+ return <PermissionDenied missingPermissions={missingPermissions} />;
184
354
  }
185
355
 
186
- // Granular permissions
187
- const permissions = propsToCamelCase(activePermissions);
356
+ const permissions = propsToCamelCase(activePermissions || {});
188
357
  const {
189
358
  canDeleteManifest,
190
359
  canManageSubscriptionAllocations,
@@ -193,22 +362,21 @@ const SubscriptionsPage = ({
193
362
  } = permissions;
194
363
  const disableManifestActions = !!task || !hasUpstreamConnection;
195
364
 
196
- const tableColumns = Immutable.asMutable(subscriptions.tableColumns, { deep: true });
197
365
  const onSearch = (search) => {
198
- loadSubscriptions({ search });
366
+ setSearchQuery(search);
199
367
  };
200
368
 
201
369
  const onDeleteSubscriptions = (rows) => {
202
- deleteSubscriptions(rows);
370
+ dispatch(deleteSubscriptions(rows, onTaskStarted));
203
371
  handleSelectedRowsChange([]);
204
- closeDeleteModal();
372
+ setDeleteModalOpened(false);
373
+ setDeleteButtonDisabled(true);
205
374
  };
206
375
 
207
376
  const toggleDeleteButton = rowsSelected =>
208
- (rowsSelected ? enableDeleteButton() : disableDeleteButton());
377
+ setDeleteButtonDisabled(!rowsSelected);
209
378
 
210
379
  const csvParams = createSubscriptionParams({ search: searchQuery });
211
- const columns = subscriptions.selectedTableColumns;
212
380
  const emptyStateData = isManifestImported
213
381
  ? {
214
382
  header: __('There are no Subscriptions to display'),
@@ -267,8 +435,9 @@ const SubscriptionsPage = ({
267
435
  disableDeleteReason={getDisabledReason(true)}
268
436
  disableAddButton={disableManifestActions}
269
437
  autocompleteQueryParams={{ organization_id: currentOrg }}
270
- updateSearchQuery={updateSearchQuery}
271
- onDeleteButtonClick={openDeleteModal}
438
+ updateSearchQuery={setSearchQuery}
439
+ searchQuery={searchQuery}
440
+ onDeleteButtonClick={() => setDeleteModalOpened(true)}
272
441
  onSearch={onSearch}
273
442
  onManageManifestButtonClick={() => setIsManageManifestModalOpen(true)}
274
443
  onExportCsvButtonClick={() => { api.open('/subscriptions.csv', csvParams); }}
@@ -287,132 +456,43 @@ const SubscriptionsPage = ({
287
456
  taskInProgress={!!task}
288
457
  disableManifestActions={disableManifestActions}
289
458
  disabledReason={getDisabledReason()}
290
- upload={uploadManifest}
291
- delete={deleteManifest}
292
- refresh={refreshManifest}
459
+ upload={file => dispatch(uploadManifest(file, onTaskStarted))}
460
+ delete={() => dispatch(deleteManifest({}, onTaskStarted))}
461
+ refresh={() => dispatch(refreshManifest({}, onTaskStarted))}
293
462
  isOpen={isManageManifestModalOpen}
294
463
  closeModal={() => setIsManageManifestModalOpen(false)}
295
464
  />
296
465
 
297
- <PageLayout
298
- searchable={false}
299
- header={__('Subscriptions')}
300
- customHeader={customHeader}
301
- customToolbar={customToolbar}
302
- >
303
- <div id="subscriptions-table" className="modal-container">
304
- <SubscriptionsTable
305
- canManageSubscriptionAllocations={canManageSubscriptionAllocations}
306
- loadSubscriptions={loadSubscriptions}
307
- tableColumns={columns}
308
- updateQuantity={updateQuantity}
309
- emptyState={emptyStateData}
310
- subscriptions={subscriptions}
311
- subscriptionDeleteModalOpen={deleteModalOpened}
312
- onSubscriptionDeleteModalClose={closeDeleteModal}
313
- onDeleteSubscriptions={onDeleteSubscriptions}
314
- toggleDeleteButton={toggleDeleteButton}
315
- task={task}
316
- selectedRows={selectedRows}
317
- onSelectedRowsChange={handleSelectedRowsChange}
318
- selectionEnabled={!disableManifestActions}
319
- />
320
- <ModalProgressBar
321
- show={!!task}
322
- container={document.getElementById('subscriptions-table')}
323
- title={task ? task.humanized.action : null}
324
- progress={task ? Math.round(task.progress * 100) : 0}
325
- />
326
- </div>
327
- </PageLayout>
466
+ <div id="subscriptions-table">
467
+ <SubscriptionsTable
468
+ canManageSubscriptionAllocations={canManageSubscriptionAllocations}
469
+ tableColumns={selectedColumnKeys}
470
+ columns={columns}
471
+ updateQuantity={quantities => dispatch(updateQuantity(quantities, onTaskStarted))}
472
+ emptyState={emptyStateData}
473
+ searchQuery={searchQuery}
474
+ organizationId={organization?.id || currentOrg}
475
+ availableQuantities={availableQuantities}
476
+ subscriptionDeleteModalOpen={deleteModalOpened}
477
+ onSubscriptionDeleteModalClose={() => setDeleteModalOpened(false)}
478
+ onDeleteSubscriptions={onDeleteSubscriptions}
479
+ toggleDeleteButton={toggleDeleteButton}
480
+ selectedRows={selectedRows}
481
+ onSelectedRowsChange={handleSelectedRowsChange}
482
+ selectionEnabled={!disableManifestActions}
483
+ customHeader={customHeader}
484
+ customToolbar={customToolbar}
485
+ onApiResponse={handleApiResponse}
486
+ onRefreshReady={onRefreshReady}
487
+ />
488
+ <ModalProgressBar
489
+ show={!!task}
490
+ title={task ? (task.humanized?.action ?? null) : null}
491
+ progress={task?.progress != null ? Math.round(task.progress * 100) : 0}
492
+ />
493
+ </div>
328
494
  </>
329
495
  );
330
496
  };
331
497
 
332
- SubscriptionsPage.propTypes = {
333
- pingUpstreamSubscriptions: PropTypes.func.isRequired,
334
- loadSubscriptions: PropTypes.func.isRequired,
335
- loadAvailableQuantities: PropTypes.func.isRequired,
336
- uploadManifest: PropTypes.func.isRequired,
337
- deleteManifest: PropTypes.func.isRequired,
338
- resetTasks: PropTypes.func.isRequired,
339
- updateQuantity: PropTypes.func.isRequired,
340
- loadTableColumns: PropTypes.func.isRequired,
341
- isManifestImported: PropTypes.bool,
342
- subscriptions: PropTypes.shape({
343
- tableColumns: PropTypes.arrayOf(PropTypes.shape({
344
- key: PropTypes.string,
345
- label: PropTypes.string,
346
- value: PropTypes.bool,
347
- })),
348
- selectedTableColumns: PropTypes.arrayOf(PropTypes.string),
349
- missingPermissions: PropTypes.arrayOf(PropTypes.string),
350
- results: PropTypes.arrayOf(PropTypes.shape({
351
- id: PropTypes.number,
352
- name: PropTypes.string,
353
- })),
354
- }).isRequired,
355
- activePermissions: PropTypes.shape({
356
- can_delete_manifest: PropTypes.bool,
357
- can_manage_subscription_allocations: PropTypes.bool,
358
- }),
359
- organization: PropTypes.shape({
360
- id: PropTypes.number,
361
- loading: PropTypes.bool,
362
- owner_details: PropTypes.shape({
363
- upstreamConsumer: PropTypes.shape({
364
- name: PropTypes.string,
365
- webUrl: PropTypes.string,
366
- uuid: PropTypes.string,
367
- }),
368
- }),
369
- error: PropTypes.shape({
370
- response: PropTypes.shape({
371
- status: PropTypes.number,
372
- }),
373
- }),
374
- }),
375
- task: PropTypes.shape({
376
- id: PropTypes.string,
377
- progress: PropTypes.number,
378
- humanized: PropTypes.shape({
379
- action: PropTypes.string,
380
- }),
381
- pending: PropTypes.bool,
382
- }),
383
- isTaskPending: PropTypes.bool,
384
- isPollingTask: PropTypes.bool,
385
- pollTasks: PropTypes.func.isRequired,
386
- cancelPollTasks: PropTypes.func.isRequired,
387
- handleStartTask: PropTypes.func.isRequired,
388
- handleFinishedTask: PropTypes.func.isRequired,
389
- hasUpstreamConnection: PropTypes.bool,
390
- deleteSubscriptions: PropTypes.func.isRequired,
391
- refreshManifest: PropTypes.func.isRequired,
392
- searchQuery: PropTypes.string,
393
- updateSearchQuery: PropTypes.func.isRequired,
394
- deleteButtonDisabled: PropTypes.bool,
395
- disableDeleteButton: PropTypes.func.isRequired,
396
- enableDeleteButton: PropTypes.func.isRequired,
397
- deleteModalOpened: PropTypes.bool,
398
- openDeleteModal: PropTypes.func.isRequired,
399
- closeDeleteModal: PropTypes.func.isRequired,
400
- };
401
-
402
- SubscriptionsPage.defaultProps = {
403
- task: undefined,
404
- isTaskPending: undefined,
405
- isPollingTask: undefined,
406
- organization: undefined,
407
- searchQuery: '',
408
- deleteModalOpened: false,
409
- deleteButtonDisabled: true,
410
- isManifestImported: false,
411
- hasUpstreamConnection: false,
412
- activePermissions: {
413
- can_import_manifest: false,
414
- can_manage_subscription_allocations: false,
415
- },
416
- };
417
-
418
498
  export default SubscriptionsPage;