katello 3.7.0.rc1 → 3.7.0.rc2

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 (189) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/katello/api/v2/content_views_controller.rb +5 -2
  3. data/app/controllers/katello/api/v2/environments_controller.rb +8 -3
  4. data/app/controllers/katello/api/v2/host_tracer_controller.rb +1 -1
  5. data/app/controllers/katello/api/v2/upstream_subscriptions_controller.rb +4 -4
  6. data/app/helpers/katello/hosts_and_hostgroups_helper.rb +5 -22
  7. data/app/lib/actions/katello/host/update_content_overrides.rb +1 -0
  8. data/app/lib/actions/katello/product/content_create.rb +1 -0
  9. data/app/lib/actions/katello/product/repositories_certs_reset.rb +25 -0
  10. data/app/lib/actions/katello/product/update.rb +6 -0
  11. data/app/lib/katello/resources/candlepin/activation_key.rb +8 -4
  12. data/app/lib/katello/resources/candlepin/product.rb +2 -1
  13. data/app/lib/katello/util/cdn_var_substitutor.rb +5 -3
  14. data/app/lib/katello/util/package.rb +21 -13
  15. data/app/lib/katello/util/package_filter.rb +33 -31
  16. data/app/lib/katello/validators/prior_validator.rb +6 -10
  17. data/app/models/katello/concerns/host_managed_extensions.rb +2 -0
  18. data/app/models/katello/concerns/organization_extensions.rb +1 -0
  19. data/app/models/katello/concerns/subscription_facet_host_extensions.rb +10 -6
  20. data/app/models/katello/content.rb +23 -2
  21. data/app/models/katello/content_view_docker_filter.rb +1 -1
  22. data/app/models/katello/content_view_puppet_module.rb +3 -3
  23. data/app/models/katello/content_view_version.rb +4 -0
  24. data/app/models/katello/environment_prior.rb +7 -0
  25. data/app/models/katello/glue/candlepin/candlepin_object.rb +2 -2
  26. data/app/models/katello/glue/candlepin/pool.rb +10 -13
  27. data/app/models/katello/glue/candlepin/product.rb +19 -9
  28. data/app/models/katello/glue/candlepin/repository.rb +16 -0
  29. data/app/models/katello/glue/candlepin/subscription.rb +1 -1
  30. data/app/models/katello/glue/provider.rb +15 -81
  31. data/app/models/katello/host/subscription_facet.rb +1 -1
  32. data/app/models/katello/kt_environment.rb +39 -8
  33. data/app/models/katello/pool.rb +2 -1
  34. data/app/models/katello/rpm.rb +144 -2
  35. data/app/models/katello/upstream_pool.rb +7 -10
  36. data/app/services/katello/candlepin/pool_service.rb +18 -3
  37. data/app/services/katello/ui_notifications/pulp/proxy_disk_space.rb +13 -16
  38. data/app/views/dashboard/_content_views_widget.html.erb +3 -3
  39. data/app/views/dashboard/_errata_widget.html.erb +2 -2
  40. data/app/views/dashboard/_host_collection_widget.html.erb +3 -3
  41. data/app/views/dashboard/_subscription_status_widget.html.erb +2 -2
  42. data/app/views/dashboard/_subscription_widget.html.erb +1 -1
  43. data/app/views/dashboard/_sync_widget.html.erb +3 -3
  44. data/app/views/katello/api/v2/subscriptions/base.json.rabl +1 -1
  45. data/app/views/katello/api/v2/upstream_subscriptions/base.json.rabl +2 -6
  46. data/app/views/katello/layouts/react.html.erb +3 -3
  47. data/config/katello.yaml +89 -0
  48. data/config/routes.rb +3 -0
  49. data/db/migrate/20160302091113_change_environment_prior.rb +9 -0
  50. data/db/migrate/20180410140909_add_organization_id_to_pool.rb +2 -1
  51. data/db/migrate/20180612163403_add_foreign_key_to_hypervisor_id.rb +10 -0
  52. data/db/migrate/20180612164926_add_content_org_id.rb +39 -0
  53. data/db/migrate/20180612165011_remove_content_fields_from_host.rb +7 -0
  54. data/db/migrate/20180626160422_add_upstream_pool_id_to_katello_pool.rb +9 -0
  55. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/content-hosts/bulk/views/content-hosts-bulk-packages-modal.html +1 -1
  56. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/content-hosts/views/register.html +1 -1
  57. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/environments/details/environment-content.controller.js +2 -3
  58. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/environments/details/views/environment.html +4 -2
  59. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/environments/environments.controller.js +19 -14
  60. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/environments/new-environment.controller.js +18 -5
  61. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/environments/paths.service.js +51 -0
  62. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/environments/views/new-environment.html +16 -3
  63. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/products/bulk/products-bulk-advanced-sync-modal.controller.js +1 -1
  64. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/products/products.controller.js +2 -2
  65. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/subscriptions/subscriptions.routes.js +3 -3
  66. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/tasks/tasks.module.js +1 -6
  67. data/engines/bastion_katello/app/assets/stylesheets/bastion_katello/bastion_katello.scss +4 -0
  68. data/engines/bastion_katello/lib/bastion_katello/engine.rb +1 -1
  69. data/lib/katello/plugin.rb +2 -11
  70. data/lib/katello/scheduled_jobs.rb +2 -14
  71. data/lib/katello/tasks/clean_backend_objects.rake +2 -0
  72. data/lib/katello/tasks/repository.rake +11 -2
  73. data/lib/katello/tasks/upgrades/3.7/import_pools.rake +12 -0
  74. data/lib/katello/version.rb +1 -1
  75. data/package.json +4 -3
  76. data/webpack/components/PaginationRow/index.js +6 -2
  77. data/webpack/containers/Application/config.js +7 -2
  78. data/webpack/index.js +3 -5
  79. data/webpack/move_to_foreman/common/helpers.js +5 -24
  80. data/webpack/move_to_foreman/components/common/emptyState/index.js +12 -7
  81. data/webpack/move_to_foreman/components/common/table/components/CollapseSubscriptionGroupButton.js +31 -0
  82. data/webpack/move_to_foreman/components/common/table/components/CollapseSubscriptionGroupButton.test.js +16 -0
  83. data/webpack/move_to_foreman/components/common/table/components/Table.js +76 -0
  84. data/webpack/move_to_foreman/components/common/table/components/Table.test.js +31 -0
  85. data/webpack/move_to_foreman/components/common/table/components/TableBody.js +27 -0
  86. data/webpack/move_to_foreman/components/common/table/components/TableBody.test.js +18 -0
  87. data/webpack/move_to_foreman/components/common/table/components/TableBodyMessage.js +18 -0
  88. data/webpack/move_to_foreman/components/common/table/components/TableBodyMessage.test.js +12 -0
  89. data/webpack/move_to_foreman/components/common/table/components/TableFixtures.js +14 -0
  90. data/webpack/move_to_foreman/components/common/table/components/TableSelectionCell.js +39 -0
  91. data/webpack/move_to_foreman/components/common/table/components/TableSelectionCell.test.js +16 -0
  92. data/webpack/move_to_foreman/components/common/table/components/TableSelectionHeaderCell.js +34 -0
  93. data/webpack/move_to_foreman/components/common/table/components/TableSelectionHeaderCell.test.js +14 -0
  94. data/webpack/move_to_foreman/components/common/table/components/__snapshots__/CollapseSubscriptionGroupButton.test.js.snap +19 -0
  95. data/webpack/move_to_foreman/components/common/table/components/__snapshots__/Table.test.js.snap +167 -0
  96. data/webpack/move_to_foreman/components/common/table/components/__snapshots__/TableBody.test.js.snap +28 -0
  97. data/webpack/move_to_foreman/components/common/table/components/__snapshots__/TableBodyMessage.test.js.snap +13 -0
  98. data/webpack/move_to_foreman/components/common/table/components/__snapshots__/TableSelectionCell.test.js.snap +16 -0
  99. data/webpack/move_to_foreman/components/common/table/components/__snapshots__/TableSelectionHeaderCell.test.js.snap +15 -0
  100. data/webpack/move_to_foreman/components/common/table/components/index.js +6 -0
  101. data/webpack/move_to_foreman/components/common/table/formatters/cellFormatter.js +4 -0
  102. data/webpack/move_to_foreman/components/common/table/formatters/collapseableAndSelectionCellFormatter.js +18 -0
  103. data/webpack/move_to_foreman/components/common/table/formatters/ellipsisCellFormatter.js +5 -0
  104. data/webpack/move_to_foreman/components/common/table/formatters/headerFormatter.js +4 -0
  105. data/webpack/move_to_foreman/components/common/table/formatters/index.js +6 -0
  106. data/webpack/move_to_foreman/components/common/table/formatters/selectionCellFormatter.js +17 -0
  107. data/webpack/move_to_foreman/components/common/table/formatters/selectionHeaderCellFormatter.js +10 -0
  108. data/webpack/move_to_foreman/components/common/table/index.js +2 -88
  109. data/webpack/move_to_pf/LoadingState/LoadingState.js +35 -0
  110. data/webpack/move_to_pf/LoadingState/LoadingState.scss +12 -0
  111. data/webpack/move_to_pf/LoadingState/LoadingState.test.js +28 -0
  112. data/webpack/move_to_pf/LoadingState/__snapshots__/LoadingState.test.js.snap +20 -0
  113. data/webpack/move_to_pf/LoadingState/index.js +3 -0
  114. data/webpack/move_to_pf/test-utils/testHelpers.js +71 -0
  115. data/webpack/redux/actions/RedHatRepositories/enabled.js +1 -1
  116. data/webpack/redux/actions/RedHatRepositories/helpers.js +34 -9
  117. data/webpack/redux/actions/RedHatRepositories/sets.js +28 -6
  118. data/webpack/redux/consts.js +1 -0
  119. data/webpack/redux/reducers/RedHatRepositories/sets.fixtures.js +12 -2
  120. data/webpack/redux/reducers/RedHatRepositories/sets.js +34 -27
  121. data/webpack/redux/reducers/RedHatRepositories/sets.test.js +10 -2
  122. data/webpack/redux/reducers/index.js +2 -0
  123. data/webpack/scenes/Organizations/OrganizationActions.js +3 -3
  124. data/webpack/scenes/RedHatRepositories/components/RecommendedRepositorySetsToggler.js +44 -0
  125. data/webpack/scenes/RedHatRepositories/components/RecommendedRepositorySetsToggler.scss +16 -0
  126. data/webpack/scenes/RedHatRepositories/components/RepositorySet.js +8 -2
  127. data/webpack/scenes/RedHatRepositories/components/RepositorySetRepositories.js +5 -3
  128. data/webpack/scenes/RedHatRepositories/components/RepositorySetRepository.js +4 -2
  129. data/webpack/scenes/RedHatRepositories/components/Search.js +1 -1
  130. data/webpack/scenes/RedHatRepositories/components/SearchBar.js +1 -1
  131. data/webpack/scenes/RedHatRepositories/components/__tests__/RecommendedRepositorySetsToggler.test.js +17 -0
  132. data/webpack/scenes/RedHatRepositories/components/__tests__/__snapshots__/RecommendedRepositorySetsToggler.test.js.snap +37 -0
  133. data/webpack/scenes/RedHatRepositories/helpers.js +1 -1
  134. data/webpack/scenes/RedHatRepositories/index.js +17 -7
  135. data/webpack/scenes/RedHatRepositories/index.scss +16 -4
  136. data/webpack/scenes/Subscriptions/Details/SubscriptionAttributes.js +17 -0
  137. data/webpack/scenes/Subscriptions/Details/SubscriptionDetailActions.js +28 -0
  138. data/webpack/scenes/Subscriptions/Details/SubscriptionDetailAssociations.js +47 -0
  139. data/webpack/scenes/Subscriptions/Details/SubscriptionDetailConstants.js +3 -0
  140. data/webpack/scenes/Subscriptions/Details/SubscriptionDetailInfo.js +65 -0
  141. data/webpack/scenes/Subscriptions/Details/SubscriptionDetailProducts.js +20 -0
  142. data/webpack/scenes/Subscriptions/Details/SubscriptionDetailReducer.js +37 -0
  143. data/webpack/scenes/Subscriptions/Details/SubscriptionDetails.js +58 -0
  144. data/webpack/scenes/Subscriptions/Details/__tests__/SubscriptionDetailActions.test.js +47 -0
  145. data/webpack/scenes/Subscriptions/Details/__tests__/SubscriptionDetailAssociations.test.js +16 -0
  146. data/webpack/scenes/Subscriptions/Details/__tests__/SubscriptionDetailInfo.test.js +15 -0
  147. data/webpack/scenes/Subscriptions/Details/__tests__/SubscriptionDetailProducts.test.js +16 -0
  148. data/webpack/scenes/Subscriptions/Details/__tests__/SubscriptionDetailReducer.test.js +39 -0
  149. data/webpack/scenes/Subscriptions/Details/__tests__/SubscriptionDetails.test.js +28 -0
  150. data/webpack/scenes/Subscriptions/Details/__tests__/__snapshots__/SubscriptionDetailAssociations.test.js.snap +53 -0
  151. data/webpack/scenes/Subscriptions/Details/__tests__/__snapshots__/SubscriptionDetailInfo.test.js.snap +185 -0
  152. data/webpack/scenes/Subscriptions/Details/__tests__/__snapshots__/SubscriptionDetailProducts.test.js.snap +77 -0
  153. data/webpack/scenes/Subscriptions/Details/__tests__/__snapshots__/SubscriptionDetails.test.js.snap +432 -0
  154. data/webpack/scenes/Subscriptions/Details/__tests__/subscriptionDetails.fixtures.js +167 -0
  155. data/webpack/scenes/Subscriptions/Details/index.js +19 -0
  156. data/webpack/scenes/Subscriptions/Manifest/ManageManifestModal.js +58 -12
  157. data/webpack/scenes/Subscriptions/Manifest/Manifest.scss +6 -1
  158. data/webpack/scenes/Subscriptions/Manifest/ManifestActions.js +4 -4
  159. data/webpack/scenes/Subscriptions/Manifest/ManifestHistoryTableSchema.js +7 -7
  160. data/webpack/scenes/Subscriptions/Manifest/__tests__/__snapshots__/ManageManifestModal.test.js.snap +6 -9
  161. data/webpack/scenes/Subscriptions/Manifest/index.js +2 -2
  162. data/webpack/scenes/Subscriptions/SubscriptionActions.js +5 -6
  163. data/webpack/scenes/Subscriptions/SubscriptionReducer.js +2 -3
  164. data/webpack/scenes/Subscriptions/SubscriptionValidations.js +1 -1
  165. data/webpack/scenes/Subscriptions/SubscriptionsPage.js +46 -30
  166. data/webpack/scenes/Subscriptions/SubscriptionsPage.scss +38 -0
  167. data/webpack/scenes/Subscriptions/UpstreamSubscriptions/UpstreamSubscriptionsActions.js +3 -3
  168. data/webpack/scenes/Subscriptions/UpstreamSubscriptions/UpstreamSubscriptionsPage.js +7 -6
  169. data/webpack/scenes/Subscriptions/UpstreamSubscriptions/UpstreamSubscriptionsTableSchema.js +17 -14
  170. data/webpack/scenes/Subscriptions/UpstreamSubscriptions/__tests__/__snapshots__/UpstreamSubscriptionsPage.test.js.snap +12 -15
  171. data/webpack/scenes/Subscriptions/UpstreamSubscriptions/__tests__/upstreamSubscriptions.fixtures.js +4 -4
  172. data/webpack/scenes/Subscriptions/__tests__/SubscriptionValidations.test.js +5 -0
  173. data/webpack/scenes/Subscriptions/__tests__/subscriptions.fixtures.js +2 -2
  174. data/webpack/scenes/Subscriptions/{EntitlementsInlineEditFormatter.js → components/SubscriptionsTable/EntitlementsInlineEditFormatter.js} +7 -7
  175. data/webpack/scenes/Subscriptions/{SubscriptionsTable.js → components/SubscriptionsTable/SubscriptionsTable.js} +75 -47
  176. data/webpack/scenes/Subscriptions/components/SubscriptionsTable/SubscriptionsTableHelpers.js +60 -0
  177. data/webpack/scenes/Subscriptions/{SubscriptionsTableSchema.js → components/SubscriptionsTable/SubscriptionsTableSchema.js} +37 -26
  178. data/webpack/scenes/Subscriptions/components/SubscriptionsTable/__tests__/SubscriptionsTable.test.js +56 -0
  179. data/webpack/scenes/Subscriptions/{__tests__ → components/SubscriptionsTable/__tests__}/__snapshots__/SubscriptionsTable.test.js.snap +16 -5
  180. data/webpack/scenes/Subscriptions/components/SubscriptionsTable/index.js +2 -0
  181. data/webpack/scenes/Subscriptions/index.js +2 -2
  182. data/webpack/scenes/Tasks/TaskActions.js +18 -11
  183. data/webpack/scenes/Tasks/__tests__/TaskActions.test.js +92 -9
  184. data/webpack/scenes/Tasks/__tests__/task.fixtures.js +19 -9
  185. data/webpack/services/api/index.js +2 -2
  186. data/webpack/test_setup.js +1 -0
  187. metadata +79 -10
  188. data/webpack/scenes/Subscriptions/Subscriptions.scss +0 -14
  189. data/webpack/scenes/Subscriptions/__tests__/SubscriptionsTable.test.js +0 -47
@@ -0,0 +1,38 @@
1
+ @import '~patternfly/dist/sass/patternfly/variables';
2
+ @import '~patternfly-react/dist/sass/inline-edit';
3
+
4
+ .editable {
5
+ &.changed {
6
+ font-weight: bold;
7
+ }
8
+ }
9
+
10
+ .pf-table-inline-edit {
11
+ tbody {
12
+ .form-group {
13
+ margin-bottom: 0;
14
+ }
15
+
16
+ tr.open-grouped-row {
17
+ background-color: $table-bg-hover;
18
+ td {
19
+ border-bottom-color: $table-border-hover;
20
+ }
21
+ }
22
+ }
23
+
24
+ .table-view-pf-select {
25
+ width: 50px;
26
+
27
+ .collapse-subscription-group-button {
28
+ margin-top: 4px;
29
+ cursor: pointer;
30
+ float: left;
31
+ font-weight: bold;
32
+ }
33
+
34
+ input[type="checkbox"] {
35
+ float: right;
36
+ }
37
+ }
38
+ }
@@ -14,12 +14,12 @@ export const loadUpstreamSubscriptions = (extendedParams = {}) => (dispatch) =>
14
14
  dispatch({ type: UPSTREAM_SUBSCRIPTIONS_REQUEST });
15
15
 
16
16
  const params = {
17
- ...{ organization_id: orgId, attachable: true },
17
+ ...{ organization_id: orgId(), attachable: true },
18
18
  ...propsToSnakeCase(extendedParams),
19
19
  };
20
20
 
21
21
  return api
22
- .get(`/organizations/${orgId}/upstream_subscriptions`, {}, params)
22
+ .get(`/organizations/${orgId()}/upstream_subscriptions`, {}, params)
23
23
  .then(({ data }) => {
24
24
  dispatch({
25
25
  type: UPSTREAM_SUBSCRIPTIONS_SUCCESS,
@@ -43,7 +43,7 @@ export const saveUpstreamSubscriptions = upstreamSubscriptions => (dispatch) =>
43
43
  };
44
44
 
45
45
  return api
46
- .post(`/organizations/${orgId}/upstream_subscriptions`, params)
46
+ .post(`/organizations/${orgId()}/upstream_subscriptions`, params)
47
47
  .then(({ data }) => {
48
48
  dispatch({
49
49
  type: SAVE_UPSTREAM_SUBSCRIPTIONS_SUCCESS,
@@ -4,7 +4,8 @@ import _ from 'lodash';
4
4
  import PropTypes from 'prop-types';
5
5
  import { LinkContainer } from 'react-router-bootstrap';
6
6
  import { Grid, Row, Col } from 'react-bootstrap';
7
- import { bindMethods, Button, Spinner } from 'patternfly-react';
7
+ import { bindMethods, Button } from 'patternfly-react';
8
+ import { LoadingState } from '../../../move_to_pf/LoadingState';
8
9
  import { notify } from '../../../move_to_foreman/foreman_toast_notifications';
9
10
  import helpers from '../../../move_to_foreman/common/helpers';
10
11
  import { Table } from '../../../move_to_foreman/components/common/table';
@@ -33,7 +34,7 @@ class UpstreamSubscriptionsPage extends Component {
33
34
  const newValue = parseInt(value, 10);
34
35
  const pool = {
35
36
  ...rowData,
36
- id: rowData.pool_id,
37
+ id: rowData.id,
37
38
  updatedQuantity: newValue,
38
39
  selected: true,
39
40
  };
@@ -86,7 +87,7 @@ class UpstreamSubscriptionsPage extends Component {
86
87
  );
87
88
 
88
89
  notify({ message: ReactDOMServer.renderToStaticMarkup(message), type: 'success' });
89
- this.props.history.push('/xui/subscriptions');
90
+ this.props.history.push('/subscriptions');
90
91
  } else {
91
92
  let errorMessages = [];
92
93
 
@@ -126,7 +127,7 @@ class UpstreamSubscriptionsPage extends Component {
126
127
  {__('Submit')}
127
128
  </Button>
128
129
 
129
- <LinkContainer to="/xui/subscriptions">
130
+ <LinkContainer to="/subscriptions">
130
131
  <Button>
131
132
  {__('Cancel')}
132
133
  </Button>
@@ -213,7 +214,7 @@ class UpstreamSubscriptionsPage extends Component {
213
214
  <Grid bsClass="container-fluid">
214
215
  <h1>{__('Add Subscriptions')}</h1>
215
216
 
216
- <Spinner loading={upstreamSubscriptions.loading} className="small-spacer">
217
+ <LoadingState loading={upstreamSubscriptions.loading} loadingText={__('Loading')}>
217
218
  <Row>
218
219
  <Col sm={12}>
219
220
  <Table
@@ -227,7 +228,7 @@ class UpstreamSubscriptionsPage extends Component {
227
228
  </Col>
228
229
  </Row>
229
230
  {getSubscriptionActions()}
230
- </Spinner>
231
+ </LoadingState>
231
232
  </Grid>
232
233
  );
233
234
  }
@@ -1,9 +1,11 @@
1
1
  import React from 'react';
2
2
  import { FormGroup, FormControl, ControlLabel } from 'react-bootstrap';
3
- import helpers, { selectionHeaderCellFormatter, selectionCellFormatter } from '../../../move_to_foreman/common/helpers';
3
+ import helpers from '../../../move_to_foreman/common/helpers';
4
4
  import {
5
- headerFormat,
6
- cellFormat,
5
+ headerFormatter,
6
+ cellFormatter,
7
+ selectionHeaderCellFormatter,
8
+ selectionCellFormatter,
7
9
  } from '../../../move_to_foreman/components/common/table';
8
10
 
9
11
  export const columns = (controller, selectionController) => [
@@ -14,15 +16,16 @@ export const columns = (controller, selectionController) => [
14
16
  formatters: [label => selectionHeaderCellFormatter(selectionController, label)],
15
17
  },
16
18
  cell: {
17
- formatters: [(value, additionalData) =>
18
- selectionCellFormatter(selectionController, value, additionalData)],
19
+ formatters: [
20
+ (value, additionalData) => selectionCellFormatter(selectionController, additionalData),
21
+ ],
19
22
  },
20
23
  },
21
24
  {
22
25
  property: 'id',
23
26
  header: {
24
27
  label: __('Subscription Name'),
25
- formatters: [headerFormat],
28
+ formatters: [headerFormatter],
26
29
  },
27
30
  cell: {
28
31
  formatters: [
@@ -40,10 +43,10 @@ export const columns = (controller, selectionController) => [
40
43
  property: 'contract_number',
41
44
  header: {
42
45
  label: __('Contract'),
43
- formatters: [headerFormat],
46
+ formatters: [headerFormatter],
44
47
  },
45
48
  cell: {
46
- formatters: [cellFormat],
49
+ formatters: [cellFormatter],
47
50
  },
48
51
  },
49
52
  // TODO: use date formatter from tomas' PR
@@ -51,27 +54,27 @@ export const columns = (controller, selectionController) => [
51
54
  property: 'start_date',
52
55
  header: {
53
56
  label: __('Start Date'),
54
- formatters: [headerFormat],
57
+ formatters: [headerFormatter],
55
58
  },
56
59
  cell: {
57
- formatters: [cellFormat],
60
+ formatters: [cellFormatter],
58
61
  },
59
62
  },
60
63
  {
61
64
  property: 'end_date',
62
65
  header: {
63
66
  label: __('End Date'),
64
- formatters: [headerFormat],
67
+ formatters: [headerFormatter],
65
68
  },
66
69
  cell: {
67
- formatters: [cellFormat],
70
+ formatters: [cellFormatter],
68
71
  },
69
72
  },
70
73
  {
71
74
  property: 'quantity',
72
75
  header: {
73
76
  label: __('Available Entitlements'),
74
- formatters: [headerFormat],
77
+ formatters: [headerFormatter],
75
78
  },
76
79
  cell: {
77
80
  formatters: [
@@ -87,7 +90,7 @@ export const columns = (controller, selectionController) => [
87
90
  property: 'consumed',
88
91
  header: {
89
92
  label: __('Quantity to Allocate'),
90
- formatters: [headerFormat],
93
+ formatters: [headerFormatter],
91
94
  },
92
95
  cell: {
93
96
  formatters: [
@@ -9,12 +9,9 @@ exports[`upstream subscriptions page should render 1`] = `
9
9
  <h1>
10
10
  Add Subscriptions
11
11
  </h1>
12
- <Spinner
13
- className="small-spacer"
14
- inline={false}
15
- inverse={false}
12
+ <LoadingState
16
13
  loading={false}
17
- size="md"
14
+ loadingText="Loading"
18
15
  >
19
16
  <Row
20
17
  bsClass="row"
@@ -157,7 +154,7 @@ exports[`upstream subscriptions page should render 1`] = `
157
154
  "consumed": 100,
158
155
  "contract_number": "11480900",
159
156
  "end_date": "2018-09-15T03:59:59+0000",
160
- "pool_id": "8a99f9815e718933015e85b19e1e11d7",
157
+ "id": "8a99f9815e718933015e85b19e1e11d7",
161
158
  "product_id": "RH00284",
162
159
  "product_name": "Red Hat Enterprise Linux for Power, LE, Premium (IFL, up to 4 LPARs)",
163
160
  "quantity": 200,
@@ -166,16 +163,16 @@ exports[`upstream subscriptions page should render 1`] = `
166
163
  "subscription_id": "4753270",
167
164
  },
168
165
  Object {
169
- "consumed": 100,
170
- "contract_number": "11480900",
166
+ "consumed": 125,
167
+ "contract_number": "11480898",
171
168
  "end_date": "2018-09-15T03:59:59+0000",
172
- "pool_id": "8a99f9815e718933015e85b19e1e11d7",
173
- "product_id": "RH00284",
174
- "product_name": "Red Hat Enterprise Linux for Power, LE, Premium (IFL, up to 4 LPARs)",
175
- "quantity": 200,
169
+ "id": "8a99f9815e718933015e85b1bfd211db",
170
+ "product_id": "RH00447",
171
+ "product_name": "Red Hat Enterprise Linux Server for ATOM with Smart Management, Hyperscale, Standard (5 Physical Nodes)",
172
+ "quantity": 250,
176
173
  "selected": false,
177
174
  "start_date": "2017-09-15T04:00:00+0000",
178
- "subscription_id": "4753270",
175
+ "subscription_id": "4753271",
179
176
  },
180
177
  ]
181
178
  }
@@ -207,7 +204,7 @@ exports[`upstream subscriptions page should render 1`] = `
207
204
  exact={false}
208
205
  replace={false}
209
206
  strict={false}
210
- to="/xui/subscriptions"
207
+ to="/subscriptions"
211
208
  >
212
209
  <Button
213
210
  active={false}
@@ -221,6 +218,6 @@ exports[`upstream subscriptions page should render 1`] = `
221
218
  </LinkContainer>
222
219
  </Col>
223
220
  </Row>
224
- </Spinner>
221
+ </LoadingState>
225
222
  </Grid>
226
223
  `;
@@ -36,7 +36,7 @@ export const requestSuccessResponse = Immutable({
36
36
  },
37
37
  results: [
38
38
  {
39
- pool_id: '8a99f9815e718933015e85b19e1e11d7',
39
+ id: '8a99f9815e718933015e85b19e1e11d7',
40
40
  quantity: 200,
41
41
  start_date: '2017-09-15T04:00:00+0000',
42
42
  end_date: '2018-09-15T03:59:59+0000',
@@ -47,7 +47,7 @@ export const requestSuccessResponse = Immutable({
47
47
  subscription_id: '4753270',
48
48
  },
49
49
  {
50
- pool_id: '8a99f9815e718933015e85b1bfd211db',
50
+ id: '8a99f9815e718933015e85b1bfd211db',
51
51
  quantity: 250,
52
52
  start_date: '2017-09-15T04:00:00+0000',
53
53
  end_date: '2018-09-15T03:59:59+0000',
@@ -65,7 +65,7 @@ export const successState = Immutable({
65
65
  loading: false,
66
66
  results: [
67
67
  {
68
- pool_id: '8a99f9815e718933015e85b19e1e11d7',
68
+ id: '8a99f9815e718933015e85b19e1e11d7',
69
69
  quantity: 200,
70
70
  start_date: '2017-09-15T04:00:00+0000',
71
71
  end_date: '2018-09-15T03:59:59+0000',
@@ -76,7 +76,7 @@ export const successState = Immutable({
76
76
  subscription_id: '4753270',
77
77
  },
78
78
  {
79
- pool_id: '8a99f9815e718933015e85b1bfd211db',
79
+ id: '8a99f9815e718933015e85b1bfd211db',
80
80
  quantity: 250,
81
81
  start_date: '2017-09-15T04:00:00+0000',
82
82
  end_date: '2018-09-15T03:59:59+0000',
@@ -30,6 +30,11 @@ describe('validateQuantity', () => {
30
30
  .toEqual(validationError('Has to be > 0'));
31
31
  });
32
32
 
33
+ it('detects zero', () => {
34
+ expect(validateQuantity('0', 500))
35
+ .toEqual(validationError('Has to be > 0'));
36
+ });
37
+
33
38
  it('detects too big quantity', () => {
34
39
  expect(validateQuantity('501', 500))
35
40
  .toEqual(validationError('Exceeds available quantity'));
@@ -135,7 +135,7 @@ export const requestSuccessResponseWithRHSubscriptions = Immutable({
135
135
  export const quantitiesRequestSuccessResponse = Immutable({
136
136
  results: [
137
137
  {
138
- pool_id: '9a95f981519abf020151ab082c5e0313',
138
+ id: '9a95f981519abf020151ab082c5e0313',
139
139
  quantity: 10000,
140
140
  available: 100,
141
141
  start_date: '2016-12-15T05:00:00+0000',
@@ -151,7 +151,7 @@ export const quantitiesRequestSuccessResponse = Immutable({
151
151
  ],
152
152
  },
153
153
  {
154
- pool_id: '6b123381519abf020151ab082c5e4678',
154
+ id: '6b123381519abf020151ab082c5e4678',
155
155
  quantity: 400,
156
156
  available: 40,
157
157
  start_date: '2016-12-15T05:00:00+0000',
@@ -1,7 +1,8 @@
1
1
  import React from 'react';
2
+ import { sprintf } from 'jed';
2
3
  import { Table, FormControl, FormGroup, HelpBlock, Spinner } from 'patternfly-react';
3
- import { validateQuantity } from './SubscriptionValidations';
4
- import { KEY_CODES } from '../../move_to_foreman/common/helpers';
4
+ import { validateQuantity } from '../../SubscriptionValidations';
5
+ import { KEY_CODES } from '../../../../move_to_foreman/common/helpers';
5
6
 
6
7
  export const entitlementsInlineEditFormatter =
7
8
  inlineEditController => Table.inlineEditFormatterFactory({
@@ -39,10 +40,9 @@ export const entitlementsInlineEditFormatter =
39
40
  ? 'editable editing changed'
40
41
  : 'editable editing';
41
42
 
42
- const maxMessage = [
43
- __('Max'),
44
- availableQuantity,
45
- ].join(' ');
43
+ const maxMessage = (availableQuantity < 1)
44
+ ? __('Unlimited')
45
+ : sprintf(__('Max %(availableQuantity)s'), { availableQuantity });
46
46
 
47
47
  const validation = validateQuantity(value, availableQuantity);
48
48
 
@@ -55,7 +55,7 @@ export const entitlementsInlineEditFormatter =
55
55
  // The same issue prevents from correct switching inputs on TAB.
56
56
  // See the reactabular code for details:
57
57
  // https://github.com/reactabular/reactabular/blob/master/packages/reactabular-table/src/body-row.js#L58
58
- <Spinner loading={!availableQuantity} size="xs">
58
+ <Spinner loading={availableQuantity === undefined} size="xs">
59
59
  <FormGroup
60
60
  validationState={validation.state}
61
61
  >
@@ -1,13 +1,16 @@
1
1
  import React, { Component } from 'react';
2
2
  import PropTypes from 'prop-types';
3
+ import classNames from 'classnames';
3
4
  import { sprintf } from 'jed';
4
- import { cloneDeep, findIndex } from 'lodash';
5
- import { Spinner, Table, Alert } from 'patternfly-react';
6
- import { Table as ForemanTable, TableBody as ForemanTableBody } from '../../move_to_foreman/components/common/table';
7
- import ConfirmDialog from '../../move_to_foreman/components/common/ConfirmDialog';
8
- import Dialog from '../../move_to_foreman/components/common/Dialog';
9
- import { columns } from './SubscriptionsTableSchema';
10
- import { recordsValid } from './SubscriptionValidations';
5
+ import { cloneDeep, findIndex, isEqual } from 'lodash';
6
+ import { Table, Alert } from 'patternfly-react';
7
+ import { LoadingState } from '../../../../move_to_pf/LoadingState';
8
+ import { Table as ForemanTable, TableBody as ForemanTableBody } from '../../../../move_to_foreman/components/common/table';
9
+ import ConfirmDialog from '../../../../move_to_foreman/components/common/ConfirmDialog';
10
+ import Dialog from '../../../../move_to_foreman/components/common/Dialog';
11
+ import { recordsValid } from '../../SubscriptionValidations';
12
+ import { createSubscriptionsTableSchema } from './SubscriptionsTableSchema';
13
+ import { buildTableRows, groupSubscriptionsByProductId, buildPools } from './SubscriptionsTableHelpers';
11
14
 
12
15
  const emptyStateData = {
13
16
  header: __('There are no Subscriptions to display'),
@@ -39,48 +42,16 @@ ErrorAlerts.propTypes = {
39
42
  errors: PropTypes.arrayOf(PropTypes.string).isRequired,
40
43
  };
41
44
 
42
- const buildTableRows = (subscriptions, updatedQuantity) =>
43
- subscriptions.results.map((subs) => {
44
- if (updatedQuantity[subs.id]) {
45
- return {
46
- ...subs,
47
- entitlementsChanged: true,
48
- quantity: updatedQuantity[subs.id],
49
- availableQuantity: subscriptions.availableQuantities[subs.id],
50
- };
51
- }
52
- return {
53
- ...subs,
54
- availableQuantity: subscriptions.availableQuantities[subs.id],
55
- };
56
- });
57
-
58
- const buildPools = updatedQuantity =>
59
- Object.entries(updatedQuantity)
60
- .map(([id, quantity]) => ({
61
- id,
62
- quantity,
63
- }));
64
-
65
45
  class SubscriptionsTable extends Component {
66
- static getDerivedStateFromProps(nextProps, prevState) {
67
- if (nextProps.subscriptions !== undefined) {
68
- return {
69
- rows: buildTableRows(
70
- nextProps.subscriptions,
71
- prevState.updatedQuantity,
72
- ),
73
- };
74
- }
75
- return null;
76
- }
77
-
78
46
  constructor(props) {
79
47
  super(props);
48
+
80
49
  this.state = {
50
+ rows: undefined,
51
+ subscriptions: undefined,
52
+ groupdSubscriptions: undefined,
81
53
  updatedQuantity: {},
82
54
  editing: false,
83
- rows: props.subscriptions.results,
84
55
  showUpdateConfirmDialog: false,
85
56
  showCancelConfirmDialog: false,
86
57
  showErrorDialog: false,
@@ -88,6 +59,41 @@ class SubscriptionsTable extends Component {
88
59
  };
89
60
  }
90
61
 
62
+ static getDerivedStateFromProps(nextProps, prevState) {
63
+ if (
64
+ nextProps.subscriptions !== undefined &&
65
+ !isEqual(nextProps.subscriptions, prevState.subscriptions)
66
+ ) {
67
+ const groupdSubscriptions = groupSubscriptionsByProductId(nextProps.subscriptions);
68
+ const rows = buildTableRows(
69
+ groupdSubscriptions,
70
+ nextProps.subscriptions.availableQuantities,
71
+ prevState.updatedQuantity,
72
+ );
73
+
74
+ return { rows, groupdSubscriptions, subscriptions: nextProps.subscriptions };
75
+ }
76
+
77
+ return null;
78
+ }
79
+
80
+ toggleSubscriptionGroup(groupId) {
81
+ const { subscriptions } = this.props;
82
+ const { groupdSubscriptions, updatedQuantity } = this.state;
83
+ const { open } = groupdSubscriptions[groupId];
84
+
85
+ groupdSubscriptions[groupId].open = !open;
86
+
87
+
88
+ const rows = buildTableRows(
89
+ groupdSubscriptions,
90
+ subscriptions.availableQuantities,
91
+ updatedQuantity,
92
+ );
93
+
94
+ this.setState({ rows, groupdSubscriptions });
95
+ }
96
+
91
97
  enableEditing(editingState) {
92
98
  this.setState({
93
99
  updatedQuantity: {},
@@ -96,8 +102,12 @@ class SubscriptionsTable extends Component {
96
102
  }
97
103
 
98
104
  updateRows(updatedQuantity) {
105
+ const { groupdSubscriptions } = this.state;
106
+ const { subscriptions } = this.props;
107
+
99
108
  const rows = buildTableRows(
100
- this.props.subscriptions,
109
+ groupdSubscriptions,
110
+ subscriptions.availableQuantities,
101
111
  updatedQuantity,
102
112
  );
103
113
  this.setState({ rows, updatedQuantity });
@@ -148,6 +158,17 @@ class SubscriptionsTable extends Component {
148
158
 
149
159
  render() {
150
160
  const { subscriptions } = this.props;
161
+ const { groupdSubscriptions } = this.state;
162
+
163
+ const groupingController = {
164
+ isCollapseable: ({ rowData }) =>
165
+ // it is the first subscription in the group
166
+ rowData.id === groupdSubscriptions[rowData.product_id].subscriptions[0].id &&
167
+ // the group contains more then one subscription
168
+ groupdSubscriptions[rowData.product_id].subscriptions.length > 1,
169
+ isCollapsed: ({ rowData }) => !groupdSubscriptions[rowData.product_id].open,
170
+ toggle: ({ rowData }) => this.toggleSubscriptionGroup(rowData.product_id),
171
+ };
151
172
 
152
173
  const inlineEditController = {
153
174
  isEditing: ({ rowData }) => (this.state.editing && rowData.available >= 0),
@@ -229,10 +250,14 @@ class SubscriptionsTable extends Component {
229
250
  bodyMessage = __('No subscriptions match your search criteria.');
230
251
  }
231
252
 
232
- const columnsDefinition = columns(inlineEditController, selectionController);
253
+ const columnsDefinition = createSubscriptionsTableSchema(
254
+ inlineEditController,
255
+ selectionController,
256
+ groupingController,
257
+ );
233
258
 
234
259
  return (
235
- <Spinner loading={subscriptions.loading} className="small-spacer">
260
+ <LoadingState loading={subscriptions.loading} loadingText={__('Loading')}>
236
261
  <ErrorAlerts
237
262
  errors={[
238
263
  subscriptions.error,
@@ -267,6 +292,9 @@ class SubscriptionsTable extends Component {
267
292
  rows={this.state.rows}
268
293
  rowKey="id"
269
294
  message={bodyMessage}
295
+ onRow={rowData => ({
296
+ className: classNames({ 'open-grouped-row': !groupingController.isCollapsed({ rowData }) }),
297
+ })}
270
298
  />
271
299
  </ForemanTable>
272
300
  <ConfirmDialog
@@ -316,7 +344,7 @@ class SubscriptionsTable extends Component {
316
344
  onConfirm={() => this.props.onDeleteSubscriptions(this.state.selectedRows)}
317
345
  onCancel={this.props.onSubscriptionDeleteModalClose}
318
346
  />
319
- </Spinner>
347
+ </LoadingState>
320
348
  );
321
349
  }
322
350
  }