katello 3.7.0 → 3.7.1

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 (113) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/javascripts/katello/common/index.js +1 -0
  3. data/app/assets/javascripts/katello/sync_management/index.js +1 -0
  4. data/app/controllers/katello/api/v2/host_packages_controller.rb +1 -5
  5. data/app/controllers/katello/remote_execution_controller.rb +6 -6
  6. data/app/helpers/katello/hosts_and_hostgroups_helper.rb +37 -9
  7. data/app/lib/actions/katello/host/hypervisors_update.rb +82 -22
  8. data/app/lib/actions/pulp/consumer/abstract_content_action.rb +12 -0
  9. data/app/lib/actions/pulp/consumer/content_install.rb +1 -1
  10. data/app/lib/actions/pulp/consumer/content_uninstall.rb +1 -1
  11. data/app/lib/actions/pulp/consumer/content_update.rb +1 -1
  12. data/app/models/katello/concerns/subscription_facet_host_extensions.rb +1 -1
  13. data/app/models/katello/content_view.rb +12 -4
  14. data/app/models/katello/glue/candlepin/pool.rb +11 -11
  15. data/app/models/katello/host/content_facet.rb +2 -1
  16. data/app/models/katello/rpm.rb +14 -6
  17. data/app/models/katello/subscription_status.rb +1 -1
  18. data/app/services/katello/candlepin/consumer.rb +8 -0
  19. data/app/views/overrides/activation_keys/_host_environment_select.html.erb +2 -3
  20. data/config/routes.rb +1 -0
  21. data/db/migrate/20180612163403_add_foreign_key_to_hypervisor_id.rb +3 -0
  22. data/db/seeds.d/75-job_templates.rb +5 -2
  23. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/content-hosts/bulk/content-hosts-bulk-repository-sets-modal.controller.js +4 -3
  24. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/content-hosts/bulk/content-hosts-bulk-subscriptions-modal.controller.js +4 -1
  25. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/content-hosts/content/content-host-packages-installed.controller.js +1 -1
  26. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/products/discovery/views/discovery-create.html +1 -1
  27. data/engines/bastion_katello/app/assets/stylesheets/bastion_katello/bastion_katello.scss +5 -0
  28. data/lib/katello/tasks/clean_backend_objects.rake +12 -3
  29. data/lib/katello/version.rb +1 -1
  30. data/package.json +10 -7
  31. data/webpack/__mocks__/foremanReact/redux.js +3 -0
  32. data/webpack/__mocks__/foremanReact/redux/actions/toasts.js +2 -0
  33. data/webpack/components/Search/Search.test.js +3 -1
  34. data/webpack/components/SelectOrg/SelectOrg.scss +3 -0
  35. data/webpack/components/SelectOrg/SelectOrgAction.js +41 -0
  36. data/webpack/components/SelectOrg/SelectOrgReducer.js +33 -0
  37. data/webpack/components/SelectOrg/SetOrganization.js +116 -0
  38. data/webpack/components/WithOrganization/withOrganization.js +28 -0
  39. data/webpack/containers/Application/config.js +9 -2
  40. data/webpack/containers/Application/index.js +4 -2
  41. data/webpack/global_test_setup.js +6 -0
  42. data/webpack/helpers/caret.js +6 -0
  43. data/webpack/move_to_foreman/components/common/{emptyState → EmptyState}/index.js +16 -3
  44. data/webpack/move_to_foreman/components/common/table/components/Table.js +1 -1
  45. data/webpack/move_to_foreman/components/common/table/components/__snapshots__/CollapseSubscriptionGroupButton.test.js.snap +2 -2
  46. data/webpack/move_to_foreman/components/common/table/components/__snapshots__/TableSelectionCell.test.js.snap +1 -1
  47. data/webpack/move_to_foreman/components/common/table/components/__snapshots__/TableSelectionHeaderCell.test.js.snap +1 -1
  48. data/webpack/move_to_pf/LoadingState/LoadingState.js +27 -14
  49. data/webpack/move_to_pf/LoadingState/LoadingState.test.js +8 -4
  50. data/webpack/move_to_pf/Select/Select.js +40 -0
  51. data/webpack/move_to_pf/react-bootstrap-select/index.js +12 -1
  52. data/webpack/redux/actions/RedHatRepositories/enabled.js +0 -1
  53. data/webpack/redux/actions/RedHatRepositories/helpers.js +5 -5
  54. data/webpack/redux/consts.js +6 -0
  55. data/webpack/redux/reducers/index.js +2 -0
  56. data/webpack/scenes/Products/ProductActions.js +24 -0
  57. data/webpack/scenes/Products/ProductConstants.js +3 -0
  58. data/webpack/scenes/Products/__tests__/ProductActions.test.js +40 -0
  59. data/webpack/scenes/Products/__tests__/products.fixtures.js +90 -0
  60. data/webpack/scenes/RedHatRepositories/components/EnabledRepository.js +14 -23
  61. data/webpack/scenes/RedHatRepositories/components/EnabledRepositoryContent.js +34 -0
  62. data/webpack/scenes/RedHatRepositories/components/RepositorySetRepository.js +1 -1
  63. data/webpack/scenes/RedHatRepositories/components/SearchBar.js +1 -0
  64. data/webpack/scenes/RedHatRepositories/components/__tests__/EnabledRepository.test.js +36 -0
  65. data/webpack/scenes/RedHatRepositories/components/__tests__/EnabledRepositoryContent.test.js +27 -0
  66. data/webpack/scenes/RedHatRepositories/components/__tests__/__snapshots__/EnabledRepository.test.js.snap +25 -0
  67. data/webpack/scenes/RedHatRepositories/components/__tests__/__snapshots__/EnabledRepositoryContent.test.js.snap +47 -0
  68. data/webpack/scenes/RedHatRepositories/components/__tests__/__snapshots__/RecommendedRepositorySetsToggler.test.js.snap +3 -1
  69. data/webpack/scenes/RedHatRepositories/index.js +7 -3
  70. data/webpack/scenes/RedHatRepositories/index.scss +1 -0
  71. data/webpack/scenes/Subscriptions/Details/SubscriptionDetailActions.js +1 -1
  72. data/webpack/scenes/Subscriptions/Details/SubscriptionDetailEnabledProducts.js +54 -0
  73. data/webpack/scenes/Subscriptions/Details/SubscriptionDetailProduct.js +29 -0
  74. data/webpack/scenes/Subscriptions/Details/SubscriptionDetailReducer.js +29 -0
  75. data/webpack/scenes/Subscriptions/Details/SubscriptionDetails.js +67 -22
  76. data/webpack/scenes/Subscriptions/Details/SubscriptionDetails.scss +9 -0
  77. data/webpack/scenes/Subscriptions/Details/__tests__/SubscriptionDetailEnabledProducts.test.js +18 -0
  78. data/webpack/scenes/Subscriptions/Details/__tests__/SubscriptionDetailProduct.test.js +13 -0
  79. data/webpack/scenes/Subscriptions/Details/__tests__/SubscriptionDetails.test.js +6 -0
  80. data/webpack/scenes/Subscriptions/Details/__tests__/__snapshots__/SubscriptionDetailEnabledProducts.test.js.snap +45 -0
  81. data/webpack/scenes/Subscriptions/Details/__tests__/__snapshots__/SubscriptionDetailProduct.test.js.snap +67 -0
  82. data/webpack/scenes/Subscriptions/Details/__tests__/__snapshots__/SubscriptionDetails.test.js.snap +497 -410
  83. data/webpack/scenes/Subscriptions/Details/__tests__/subscriptionDetails.fixtures.js +4 -0
  84. data/webpack/scenes/Subscriptions/Details/index.js +3 -1
  85. data/webpack/scenes/Subscriptions/Manifest/ManageManifestModal.js +78 -34
  86. data/webpack/scenes/Subscriptions/Manifest/ManifestHistoryReducer.js +8 -0
  87. data/webpack/scenes/Subscriptions/Manifest/__tests__/ManageManifestModal.test.js +3 -0
  88. data/webpack/scenes/Subscriptions/Manifest/__tests__/__snapshots__/ManageManifestModal.test.js.snap +34 -7
  89. data/webpack/scenes/Subscriptions/Manifest/index.js +1 -0
  90. data/webpack/scenes/Subscriptions/SubscriptionConstants.js +1 -0
  91. data/webpack/scenes/Subscriptions/SubscriptionHelpers.js +3 -0
  92. data/webpack/scenes/Subscriptions/SubscriptionReducer.js +6 -2
  93. data/webpack/scenes/Subscriptions/SubscriptionsPage.js +31 -36
  94. data/webpack/scenes/Subscriptions/UpstreamSubscriptions/UpstreamSubscriptionsPage.js +2 -7
  95. data/webpack/scenes/Subscriptions/UpstreamSubscriptions/__tests__/UpstreamSubscriptionsPage.test.js +1 -1
  96. data/webpack/scenes/Subscriptions/UpstreamSubscriptions/__tests__/__snapshots__/UpstreamSubscriptionsPage.test.js.snap +3 -6
  97. data/webpack/scenes/Subscriptions/__tests__/SubscriptionsPage.test.js +2 -0
  98. data/webpack/scenes/Subscriptions/__tests__/__snapshots__/SubscriptionsPage.test.js.snap +14 -2
  99. data/webpack/scenes/Subscriptions/__tests__/subscriptions.fixtures.js +4 -3
  100. data/webpack/scenes/Subscriptions/components/SubscriptionsTable/EntitlementsInlineEditFormatter.js +8 -5
  101. data/webpack/scenes/Subscriptions/components/SubscriptionsTable/SubscriptionsTable.js +29 -19
  102. data/webpack/scenes/Subscriptions/components/SubscriptionsTable/SubscriptionsTableHelpers.js +9 -2
  103. data/webpack/scenes/Subscriptions/components/SubscriptionsTable/SubscriptionsTableSchema.js +2 -2
  104. data/webpack/scenes/Subscriptions/components/SubscriptionsTable/__tests__/EntitlementsInlineEditFormatter.test.js +110 -0
  105. data/webpack/scenes/Subscriptions/components/SubscriptionsTable/__tests__/SubscriptionsTable.test.js +15 -3
  106. data/webpack/scenes/Subscriptions/components/SubscriptionsTable/__tests__/__snapshots__/EntitlementsInlineEditFormatter.test.js.snap +228 -0
  107. data/webpack/scenes/Subscriptions/components/SubscriptionsTable/__tests__/__snapshots__/SubscriptionsTable.test.js.snap +54 -21
  108. data/webpack/scenes/Subscriptions/index.js +1 -0
  109. data/webpack/scenes/Tasks/helpers.js +52 -0
  110. data/webpack/services/api/index.js +17 -1
  111. data/webpack/test_setup.js +2 -0
  112. metadata +31 -4
  113. data/config/katello.yaml +0 -89
@@ -193,13 +193,9 @@ class UpstreamSubscriptionsPage extends Component {
193
193
  description: __('Subscription Allocations allow you to export subscriptions from the Red Hat Customer Portal to ' +
194
194
  'an on-premise subscription management application such as Red Hat Satellite.'),
195
195
  docUrl: 'http://redhat.com',
196
- documentation: {
197
- title: __('Learn more about Subscription Allocations'),
198
- url: 'http://redhat.com',
199
- },
200
196
  action: {
201
- title: __('New Subscription Allocation'),
202
- url: 'http://redhat.com',
197
+ title: __('Import a Manifest to Begin'),
198
+ url: '/subscriptions',
203
199
  },
204
200
  });
205
201
 
@@ -270,7 +266,6 @@ class UpstreamSubscriptionsPage extends Component {
270
266
  }
271
267
 
272
268
  UpstreamSubscriptionsPage.propTypes = {
273
- history: PropTypes.shape({ push: PropTypes.func }).isRequired,
274
269
  loadUpstreamSubscriptions: PropTypes.func.isRequired,
275
270
  saveUpstreamSubscriptions: PropTypes.func.isRequired,
276
271
  upstreamSubscriptions: PropTypes.shape({
@@ -10,6 +10,7 @@ jest.mock('foremanReact/components/BreadcrumbBar');
10
10
 
11
11
  describe('upstream subscriptions page', () => {
12
12
  let shallowWrapper;
13
+ const mockHistory = { push: () => {} };
13
14
  beforeEach(() => {
14
15
  shallowWrapper = shallow(<UpstreamSubscriptionsPage
15
16
  upstreamSubscriptions={successState}
@@ -18,7 +19,6 @@ describe('upstream subscriptions page', () => {
18
19
  history={mockHistory}
19
20
  />);
20
21
  });
21
- const mockHistory = { push: () => {} };
22
22
 
23
23
  it('should render', async () => {
24
24
  expect(toJson(shallowWrapper)).toMatchSnapshot();
@@ -25,6 +25,7 @@ exports[`upstream subscriptions page should render 1`] = `
25
25
  <LoadingState
26
26
  loading={false}
27
27
  loadingText="Loading"
28
+ timeout={300}
28
29
  >
29
30
  <Row
30
31
  bsClass="row"
@@ -141,15 +142,11 @@ exports[`upstream subscriptions page should render 1`] = `
141
142
  emptyState={
142
143
  Object {
143
144
  "action": Object {
144
- "title": "New Subscription Allocation",
145
- "url": "http://redhat.com",
145
+ "title": "Import a Manifest to Begin",
146
+ "url": "/subscriptions",
146
147
  },
147
148
  "description": "Subscription Allocations allow you to export subscriptions from the Red Hat Customer Portal to an on-premise subscription management application such as Red Hat Satellite.",
148
149
  "docUrl": "http://redhat.com",
149
- "documentation": Object {
150
- "title": "Learn more about Subscription Allocations",
151
- "url": "http://redhat.com",
152
- },
153
150
  "header": "There are no Subscription Allocations to display",
154
151
  }
155
152
  }
@@ -10,9 +10,11 @@ jest.mock('../../../move_to_foreman/foreman_toast_notifications');
10
10
 
11
11
  describe('subscriptions page', () => {
12
12
  const noop = () => {};
13
+ const organization = { owner_details: { upstreamConsumer: 'blah' } };
13
14
 
14
15
  it('should render', async () => {
15
16
  const page = shallow(<SubscriptionsPage
17
+ organization={organization}
16
18
  subscriptions={successState}
17
19
  loadSetting={loadSetting}
18
20
  loadSubscriptions={loadSubscriptions}
@@ -53,6 +53,7 @@ exports[`subscriptions page should render 1`] = `
53
53
  >
54
54
  <LinkContainer
55
55
  activeClassName="active"
56
+ disabled={false}
56
57
  exact={false}
57
58
  replace={false}
58
59
  strict={false}
@@ -93,7 +94,7 @@ exports[`subscriptions page should render 1`] = `
93
94
  title="Delete"
94
95
  tooltipId="delete-subscriptions-button-tooltip"
95
96
  tooltipPlacement="top"
96
- tooltipText="This is disabled because no subscriptions are selected"
97
+ tooltipText="This is disabled because no subscriptions are selected."
97
98
  />
98
99
  </FormGroup>
99
100
  </div>
@@ -111,13 +112,23 @@ exports[`subscriptions page should render 1`] = `
111
112
  id="subscriptions-table"
112
113
  >
113
114
  <SubscriptionsTable
115
+ emptyState={
116
+ Object {
117
+ "action": Object {
118
+ "onClick": [Function],
119
+ "title": "Import a Manifest",
120
+ },
121
+ "description": "Import a Manifest to manage your Entitlements.",
122
+ "header": "There are no Subscriptions to display",
123
+ }
124
+ }
114
125
  loadSubscriptions={[Function]}
115
126
  onDeleteSubscriptions={[Function]}
116
127
  onSubscriptionDeleteModalClose={[Function]}
117
128
  subscriptionDeleteModalOpen={false}
118
129
  subscriptions={
119
130
  Object {
120
- "availableQuantities": Object {},
131
+ "availableQuantities": null,
121
132
  "itemCount": 81,
122
133
  "loading": false,
123
134
  "pagination": Object {
@@ -184,6 +195,7 @@ exports[`subscriptions page should render 1`] = `
184
195
  "tasks": Array [],
185
196
  }
186
197
  }
198
+ task={null}
187
199
  toggleDeleteButton={[Function]}
188
200
  updateQuantity={[Function]}
189
201
  />
@@ -10,7 +10,7 @@ export const initialState = Immutable({
10
10
  },
11
11
  itemCount: 0,
12
12
  quantitiesLoading: false,
13
- availableQuantities: {},
13
+ availableQuantities: null,
14
14
  tasks: [],
15
15
  });
16
16
 
@@ -243,7 +243,7 @@ export const successState = Immutable({
243
243
  },
244
244
  itemCount: 81,
245
245
  quantitiesLoading: false,
246
- availableQuantities: {},
246
+ availableQuantities: null,
247
247
  tasks: [],
248
248
  });
249
249
 
@@ -256,7 +256,7 @@ export const errorState = Immutable({
256
256
  itemCount: 0,
257
257
  results: [],
258
258
  quantitiesLoading: false,
259
- availableQuantities: {},
259
+ availableQuantities: null,
260
260
  tasks: [],
261
261
  });
262
262
 
@@ -278,6 +278,7 @@ export const loadingQuantitiesState = Immutable({
278
278
  export const quantitiesErrorState = Immutable({
279
279
  ...successState,
280
280
  quantitiesLoading: false,
281
+ availableQuantities: {},
281
282
  });
282
283
 
283
284
  export const successActions = [
@@ -34,15 +34,18 @@ export const entitlementsInlineEditFormatter =
34
34
  );
35
35
  },
36
36
  renderEdit: (value, additionalData) => {
37
- const { availableQuantity } = additionalData.rowData;
37
+ const { availableQuantity, availableQuantityLoaded } = additionalData.rowData;
38
38
 
39
39
  const className = inlineEditController.hasChanged(additionalData)
40
40
  ? 'editable editing changed'
41
41
  : 'editable editing';
42
42
 
43
- const maxMessage = (availableQuantity < 1)
44
- ? __('Unlimited')
45
- : sprintf(__('Max %(availableQuantity)s'), { availableQuantity });
43
+ let maxMessage;
44
+ if (availableQuantityLoaded && (availableQuantity !== undefined)) {
45
+ maxMessage = (availableQuantity < 1)
46
+ ? __('Unlimited')
47
+ : sprintf(__('Max %(availableQuantity)s'), { availableQuantity });
48
+ }
46
49
 
47
50
  const validation = validateQuantity(value, availableQuantity);
48
51
 
@@ -55,7 +58,7 @@ export const entitlementsInlineEditFormatter =
55
58
  // The same issue prevents from correct switching inputs on TAB.
56
59
  // See the reactabular code for details:
57
60
  // https://github.com/reactabular/reactabular/blob/master/packages/reactabular-table/src/body-row.js#L58
58
- <Spinner loading={availableQuantity === undefined} size="xs">
61
+ <Spinner loading={!availableQuantityLoaded} size="xs">
59
62
  <FormGroup
60
63
  validationState={validation.state}
61
64
  >
@@ -11,19 +11,11 @@ import Dialog from '../../../../move_to_foreman/components/common/Dialog';
11
11
  import { recordsValid } from '../../SubscriptionValidations';
12
12
  import { createSubscriptionsTableSchema } from './SubscriptionsTableSchema';
13
13
  import { buildTableRows, groupSubscriptionsByProductId, buildPools } from './SubscriptionsTableHelpers';
14
-
15
- const emptyStateData = {
16
- header: __('There are no Subscriptions to display'),
17
- description: __('Add Subscriptions to this Allocation to manage your Entitlements.'),
18
- documentation: {
19
- title: __('Learn more about adding Subscriptions to Allocations'),
20
- url: 'http://redhat.com',
21
- },
22
- action: {
23
- title: __('Add Subscriptions'),
24
- url: 'subscriptions/add',
25
- },
26
- };
14
+ import { renderTaskStartedToast } from '../../../Tasks/helpers';
15
+ import {
16
+ BLOCKING_FOREMAN_TASK_TYPES,
17
+ MANIFEST_TASKS_BULK_SEARCH_ID,
18
+ } from '../../SubscriptionConstants';
27
19
 
28
20
  class SubscriptionsTable extends Component {
29
21
  constructor(props) {
@@ -117,7 +109,15 @@ class SubscriptionsTable extends Component {
117
109
  confirmEdit() {
118
110
  this.showUpdateConfirm(false);
119
111
  if (Object.keys(this.state.updatedQuantity).length > 0) {
120
- this.props.updateQuantity(buildPools(this.state.updatedQuantity));
112
+ this.props.updateQuantity(buildPools(this.state.updatedQuantity))
113
+ .then(() =>
114
+ this.props.bulkSearch({
115
+ search_id: MANIFEST_TASKS_BULK_SEARCH_ID,
116
+ type: 'all',
117
+ active_only: true,
118
+ action_types: BLOCKING_FOREMAN_TASK_TYPES,
119
+ }))
120
+ .then(() => renderTaskStartedToast(this.props.task));
121
121
  }
122
122
  this.enableEditing(false);
123
123
  }
@@ -140,8 +140,9 @@ class SubscriptionsTable extends Component {
140
140
  }
141
141
 
142
142
  render() {
143
- const { subscriptions } = this.props;
143
+ const { subscriptions, emptyState } = this.props;
144
144
  const { groupedSubscriptions } = this.state;
145
+ const allSubscriptionResults = subscriptions.results;
145
146
 
146
147
  const groupingController = {
147
148
  isCollapseable: ({ rowData }) =>
@@ -185,7 +186,8 @@ class SubscriptionsTable extends Component {
185
186
  },
186
187
  };
187
188
 
188
- const checkAllRowsSelected = () => this.state.rows.length === this.state.selectedRows.length;
189
+ const checkAllRowsSelected = () =>
190
+ allSubscriptionResults.length === this.state.selectedRows.length;
189
191
 
190
192
  const updateDeleteButton = () => {
191
193
  this.props.toggleDeleteButton(this.state.selectedRows.length > 0);
@@ -201,7 +203,7 @@ class SubscriptionsTable extends Component {
201
203
  );
202
204
  } else {
203
205
  this.setState(
204
- { selectedRows: this.state.rows.map(row => row.id) },
206
+ { selectedRows: allSubscriptionResults.map(row => row.id) },
205
207
  updateDeleteButton,
206
208
  );
207
209
  }
@@ -229,7 +231,7 @@ class SubscriptionsTable extends Component {
229
231
  };
230
232
 
231
233
  let bodyMessage;
232
- if (subscriptions.results.length === 0 && subscriptions.searchIsActive) {
234
+ if (allSubscriptionResults.length === 0 && subscriptions.searchIsActive) {
233
235
  bodyMessage = __('No subscriptions match your search criteria.');
234
236
  }
235
237
 
@@ -243,7 +245,7 @@ class SubscriptionsTable extends Component {
243
245
  <LoadingState loading={subscriptions.loading} loadingText={__('Loading')}>
244
246
  <ForemanTable
245
247
  columns={columnsDefinition}
246
- emptyState={emptyStateData}
248
+ emptyState={emptyState}
247
249
  bodyMessage={bodyMessage}
248
250
  rows={this.state.rows}
249
251
  components={{
@@ -329,6 +331,7 @@ class SubscriptionsTable extends Component {
329
331
  SubscriptionsTable.propTypes = {
330
332
  loadSubscriptions: PropTypes.func.isRequired,
331
333
  updateQuantity: PropTypes.func.isRequired,
334
+ emptyState: PropTypes.shape({}).isRequired,
332
335
  subscriptions: PropTypes.shape({
333
336
  results: PropTypes.array,
334
337
  }).isRequired,
@@ -336,6 +339,13 @@ SubscriptionsTable.propTypes = {
336
339
  onDeleteSubscriptions: PropTypes.func.isRequired,
337
340
  onSubscriptionDeleteModalClose: PropTypes.func.isRequired,
338
341
  toggleDeleteButton: PropTypes.func.isRequired,
342
+ task: PropTypes.shape({}),
343
+ bulkSearch: PropTypes.func,
344
+ };
345
+
346
+ SubscriptionsTable.defaultProps = {
347
+ task: { humanized: {} },
348
+ bulkSearch: undefined,
339
349
  };
340
350
 
341
351
  export default SubscriptionsTable;
@@ -1,15 +1,22 @@
1
1
  const buildTableRow = (subscription, availableQuantities, updatedQuantity) => {
2
+ const availableQuantityLoaded = !!availableQuantities;
3
+ const availableQuantity = availableQuantityLoaded
4
+ ? availableQuantities[subscription.id]
5
+ : null;
6
+
2
7
  if (updatedQuantity[subscription.id]) {
3
8
  return {
4
9
  ...subscription,
5
10
  entitlementsChanged: true,
6
11
  quantity: updatedQuantity[subscription.id],
7
- availableQuantity: availableQuantities[subscription.id],
12
+ availableQuantity,
13
+ availableQuantityLoaded,
8
14
  };
9
15
  }
10
16
  return {
11
17
  ...subscription,
12
- availableQuantity: availableQuantities[subscription.id],
18
+ availableQuantity,
19
+ availableQuantityLoaded,
13
20
  };
14
21
  };
15
22
 
@@ -97,9 +97,9 @@ export const createSubscriptionsTableSchema = (
97
97
  },
98
98
  cell: {
99
99
  formatters: [
100
- cell => (
100
+ (value, { rowData }) => (
101
101
  <td>
102
- <Icon type="fa" name={cell.virt_who ? 'check' : 'minus'} />
102
+ <Icon type="fa" name={rowData.virt_who ? 'check' : 'minus'} />
103
103
  </td>
104
104
  ),
105
105
  ],
@@ -0,0 +1,110 @@
1
+ // import React from 'react';
2
+ import { shallow } from 'enzyme';
3
+ import toJson from 'enzyme-to-json';
4
+ import editFormatter from '../EntitlementsInlineEditFormatter';
5
+
6
+ describe('EntitlementsInlineEditFormatter', () => {
7
+ const data = rowData => ({
8
+ rowData,
9
+ });
10
+
11
+ const mockController = (options = {}) => {
12
+ const { editing = true, changed = false } = options;
13
+ return {
14
+ isEditing: () => editing,
15
+ hasChanged: () => changed,
16
+ };
17
+ };
18
+
19
+ describe('edit mode', () => {
20
+ describe('when available quantities are being loaded', () => {
21
+ it('renders spinner', async () => {
22
+ const controller = mockController();
23
+ const value = 100;
24
+ const formatter = editFormatter(controller)(value, data({
25
+ availableQuantityLoaded: false,
26
+ }));
27
+
28
+ expect(toJson(shallow(formatter))).toMatchSnapshot();
29
+ });
30
+ });
31
+
32
+ describe('when available quantities are loaded', () => {
33
+ it('renders edit field and max available', async () => {
34
+ const controller = mockController();
35
+ const value = 100;
36
+ const formatter = editFormatter(controller)(value, data({
37
+ availableQuantityLoaded: true,
38
+ availableQuantity: 500,
39
+ }));
40
+
41
+ expect(toJson(shallow(formatter))).toMatchSnapshot();
42
+ });
43
+
44
+ it('renders edit field and unlimited message', async () => {
45
+ const controller = mockController();
46
+ const value = 100;
47
+ const formatter = editFormatter(controller)(value, data({
48
+ availableQuantityLoaded: true,
49
+ availableQuantity: -1,
50
+ }));
51
+
52
+ expect(toJson(shallow(formatter))).toMatchSnapshot();
53
+ });
54
+
55
+ it('renders validation message', async () => {
56
+ const controller = mockController();
57
+ const value = 200;
58
+ const formatter = editFormatter(controller)(value, data({
59
+ availableQuantityLoaded: true,
60
+ availableQuantity: 100,
61
+ }));
62
+
63
+ expect(toJson(shallow(formatter))).toMatchSnapshot();
64
+ });
65
+
66
+ it('renders changed values', async () => {
67
+ const controller = mockController({ changed: true });
68
+ const value = 100;
69
+ const formatter = editFormatter(controller)(value, data({
70
+ availableQuantityLoaded: true,
71
+ availableQuantity: 200,
72
+ }));
73
+
74
+ expect(toJson(shallow(formatter))).toMatchSnapshot();
75
+ });
76
+ });
77
+
78
+ describe('when available quantities failed to load', () => {
79
+ it('renders just the edit field', async () => {
80
+ const controller = mockController();
81
+ const value = 200;
82
+ const formatter = editFormatter(controller)(value, data({
83
+ availableQuantityLoaded: true,
84
+ }));
85
+
86
+ expect(toJson(shallow(formatter))).toMatchSnapshot();
87
+ });
88
+ });
89
+ });
90
+
91
+ describe('value mode', () => {
92
+ it('renders the value', async () => {
93
+ const controller = mockController({ editing: false });
94
+ const value = 200;
95
+ const formatter = editFormatter(controller)(value, data({}));
96
+
97
+ expect(toJson(shallow(formatter))).toMatchSnapshot();
98
+ });
99
+
100
+ it('renders unlimited for -1', async () => {
101
+ const controller = mockController({ editing: false });
102
+ const value = 200;
103
+ const formatter = editFormatter(controller)(value, data({
104
+ available: -1,
105
+ }));
106
+
107
+ expect(toJson(shallow(formatter))).toMatchSnapshot();
108
+ });
109
+ });
110
+ });