katello 3.8.0.rc1 → 3.8.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 (123) 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/registry/registry_proxies_controller.rb +1 -1
  5. data/app/controllers/katello/api/v2/environments_controller.rb +0 -1
  6. data/app/controllers/katello/api/v2/ostree_branches_controller.rb +1 -1
  7. data/app/controllers/katello/api/v2/repository_sets_controller.rb +10 -1
  8. data/app/controllers/katello/remote_execution_controller.rb +6 -6
  9. data/app/helpers/katello/hosts_and_hostgroups_helper.rb +37 -9
  10. data/app/lib/katello/resources/registry.rb +4 -4
  11. data/app/models/katello/authorization/repository.rb +2 -1
  12. data/app/models/katello/content_view.rb +12 -4
  13. data/app/models/katello/glue/candlepin/owner.rb +0 -8
  14. data/app/models/katello/glue/candlepin/pool.rb +11 -11
  15. data/app/models/katello/kt_environment.rb +0 -6
  16. data/app/models/katello/product_content.rb +4 -1
  17. data/app/models/katello/rpm.rb +13 -5
  18. data/app/services/katello/ui_notifications/pulp/proxy_disk_space.rb +3 -1
  19. data/app/views/overrides/activation_keys/_host_environment_select.html.erb +2 -3
  20. data/config/katello.yaml.example +5 -0
  21. data/config/routes.rb +1 -0
  22. data/db/seeds.d/75-job_templates.rb +5 -2
  23. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/environments/details/views/environment-details.html +43 -8
  24. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/products/discovery/discovery.controller.js +17 -2
  25. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/products/discovery/views/discovery-create.html +1 -1
  26. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/products/discovery/views/discovery.html +1 -1
  27. data/engines/bastion_katello/app/assets/stylesheets/bastion_katello/bastion_katello.scss +5 -0
  28. data/lib/katello/version.rb +1 -1
  29. data/package.json +11 -7
  30. data/webpack/__mocks__/foremanReact/components/BreadcrumbBar.js +3 -0
  31. data/webpack/__mocks__/foremanReact/redux/actions/toasts.js +8 -0
  32. data/webpack/__mocks__/foremanReact/redux.js +3 -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/mockRequest.js +3 -3
  44. data/webpack/move_to_foreman/common/helpers.js +45 -8
  45. data/webpack/move_to_foreman/components/common/{emptyState → EmptyState}/index.js +16 -3
  46. data/webpack/move_to_foreman/components/common/table/components/Table.js +1 -1
  47. data/webpack/move_to_foreman/components/common/table/components/__snapshots__/CollapseSubscriptionGroupButton.test.js.snap +2 -2
  48. data/webpack/move_to_foreman/components/common/table/components/__snapshots__/TableSelectionCell.test.js.snap +1 -1
  49. data/webpack/move_to_foreman/components/common/table/components/__snapshots__/TableSelectionHeaderCell.test.js.snap +1 -1
  50. data/webpack/move_to_pf/LoadingState/LoadingState.js +27 -14
  51. data/webpack/move_to_pf/LoadingState/LoadingState.test.js +8 -4
  52. data/webpack/move_to_pf/Select/Select.js +40 -0
  53. data/webpack/move_to_pf/react-bootstrap-select/index.js +12 -1
  54. data/webpack/redux/actions/RedHatRepositories/enabled.js +0 -1
  55. data/webpack/redux/actions/RedHatRepositories/helpers.js +5 -5
  56. data/webpack/redux/actions/RedHatRepositories/sets.js +1 -1
  57. data/webpack/redux/consts.js +6 -0
  58. data/webpack/redux/reducers/index.js +2 -0
  59. data/webpack/scenes/RedHatRepositories/components/EnabledRepository.js +14 -23
  60. data/webpack/scenes/RedHatRepositories/components/EnabledRepositoryContent.js +34 -0
  61. data/webpack/scenes/RedHatRepositories/components/RepositorySetRepository.js +1 -1
  62. data/webpack/scenes/RedHatRepositories/components/SearchBar.js +1 -0
  63. data/webpack/scenes/RedHatRepositories/components/__tests__/EnabledRepository.test.js +36 -0
  64. data/webpack/scenes/RedHatRepositories/components/__tests__/EnabledRepositoryContent.test.js +27 -0
  65. data/webpack/scenes/RedHatRepositories/components/__tests__/__snapshots__/EnabledRepository.test.js.snap +25 -0
  66. data/webpack/scenes/RedHatRepositories/components/__tests__/__snapshots__/EnabledRepositoryContent.test.js.snap +47 -0
  67. data/webpack/scenes/RedHatRepositories/components/__tests__/__snapshots__/RecommendedRepositorySetsToggler.test.js.snap +3 -1
  68. data/webpack/scenes/RedHatRepositories/index.js +7 -3
  69. data/webpack/scenes/RedHatRepositories/index.scss +1 -0
  70. data/webpack/scenes/Subscriptions/Details/SubscriptionDetailActions.js +3 -8
  71. data/webpack/scenes/Subscriptions/Details/SubscriptionDetailProducts.js +5 -3
  72. data/webpack/scenes/Subscriptions/Details/SubscriptionDetailReducer.js +1 -1
  73. data/webpack/scenes/Subscriptions/Details/SubscriptionDetails.js +44 -6
  74. data/webpack/scenes/Subscriptions/Details/SubscriptionDetails.scss +4 -0
  75. data/webpack/scenes/Subscriptions/Details/__tests__/SubscriptionDetailReducer.test.js +3 -1
  76. data/webpack/scenes/Subscriptions/Details/__tests__/SubscriptionDetails.test.js +2 -1
  77. data/webpack/scenes/Subscriptions/Details/__tests__/__snapshots__/SubscriptionDetailProducts.test.js.snap +113 -23
  78. data/webpack/scenes/Subscriptions/Details/__tests__/__snapshots__/SubscriptionDetails.test.js.snap +23 -14
  79. data/webpack/scenes/Subscriptions/Details/__tests__/subscriptionDetails.fixtures.js +3 -4
  80. data/webpack/scenes/Subscriptions/Details/index.js +2 -2
  81. data/webpack/scenes/Subscriptions/Manifest/ManageManifestModal.js +78 -34
  82. data/webpack/scenes/Subscriptions/Manifest/ManifestActions.js +5 -24
  83. data/webpack/scenes/Subscriptions/Manifest/ManifestHistoryReducer.js +9 -1
  84. data/webpack/scenes/Subscriptions/Manifest/__tests__/ManageManifestModal.test.js +3 -0
  85. data/webpack/scenes/Subscriptions/Manifest/__tests__/ManifestActions.test.js +20 -8
  86. data/webpack/scenes/Subscriptions/Manifest/__tests__/ManifestHistoryReducer.test.js +3 -1
  87. data/webpack/scenes/Subscriptions/Manifest/__tests__/__snapshots__/ManageManifestModal.test.js.snap +34 -7
  88. data/webpack/scenes/Subscriptions/Manifest/__tests__/manifest.fixtures.js +9 -16
  89. data/webpack/scenes/Subscriptions/Manifest/index.js +1 -0
  90. data/webpack/scenes/Subscriptions/SubscriptionActions.js +5 -26
  91. data/webpack/scenes/Subscriptions/SubscriptionConstants.js +1 -0
  92. data/webpack/scenes/Subscriptions/SubscriptionHelpers.js +3 -0
  93. data/webpack/scenes/Subscriptions/SubscriptionReducer.js +11 -4
  94. data/webpack/scenes/Subscriptions/SubscriptionsPage.js +31 -36
  95. data/webpack/scenes/Subscriptions/UpstreamSubscriptions/UpstreamSubscriptionsActions.js +3 -12
  96. data/webpack/scenes/Subscriptions/UpstreamSubscriptions/UpstreamSubscriptionsPage.js +57 -27
  97. data/webpack/scenes/Subscriptions/UpstreamSubscriptions/UpstreamSubscriptionsReducer.js +2 -3
  98. data/webpack/scenes/Subscriptions/UpstreamSubscriptions/UpstreamSubscriptionsTableSchema.js +10 -5
  99. data/webpack/scenes/Subscriptions/UpstreamSubscriptions/__tests__/UpstreamSubscriptionsActions.test.js +10 -5
  100. data/webpack/scenes/Subscriptions/UpstreamSubscriptions/__tests__/UpstreamSubscriptionsPage.test.js +50 -5
  101. data/webpack/scenes/Subscriptions/UpstreamSubscriptions/__tests__/UpstreamSubscriptionsReducer.test.js +8 -3
  102. data/webpack/scenes/Subscriptions/UpstreamSubscriptions/__tests__/__snapshots__/UpstreamSubscriptionsPage.test.js.snap +21 -11
  103. data/webpack/scenes/Subscriptions/UpstreamSubscriptions/__tests__/upstreamSubscriptions.fixtures.js +5 -8
  104. data/webpack/scenes/Subscriptions/__tests__/SubscriptionsPage.test.js +2 -0
  105. data/webpack/scenes/Subscriptions/__tests__/SubscriptionsReducer.test.js +9 -3
  106. data/webpack/scenes/Subscriptions/__tests__/__snapshots__/SubscriptionsPage.test.js.snap +14 -2
  107. data/webpack/scenes/Subscriptions/__tests__/subscriptions.fixtures.js +11 -17
  108. data/webpack/scenes/Subscriptions/components/SubscriptionsTable/EntitlementsInlineEditFormatter.js +8 -5
  109. data/webpack/scenes/Subscriptions/components/SubscriptionsTable/SubscriptionsTable.js +45 -58
  110. data/webpack/scenes/Subscriptions/components/SubscriptionsTable/SubscriptionsTableHelpers.js +11 -4
  111. data/webpack/scenes/Subscriptions/components/SubscriptionsTable/SubscriptionsTableSchema.js +2 -2
  112. data/webpack/scenes/Subscriptions/components/SubscriptionsTable/__tests__/EntitlementsInlineEditFormatter.test.js +110 -0
  113. data/webpack/scenes/Subscriptions/components/SubscriptionsTable/__tests__/SubscriptionsTable.test.js +16 -3
  114. data/webpack/scenes/Subscriptions/components/SubscriptionsTable/__tests__/__snapshots__/EntitlementsInlineEditFormatter.test.js.snap +228 -0
  115. data/webpack/scenes/Subscriptions/components/SubscriptionsTable/__tests__/__snapshots__/SubscriptionsTable.test.js.snap +392 -365
  116. data/webpack/scenes/Subscriptions/index.js +1 -0
  117. data/webpack/scenes/Tasks/helpers.js +52 -0
  118. data/webpack/services/api/index.js +17 -1
  119. data/webpack/services/api/testHelpers.js +28 -0
  120. data/webpack/test_setup.js +2 -0
  121. metadata +24 -5
  122. data/config/katello.yaml +0 -89
  123. data/webpack/services/api/fixtures.js +0 -353
@@ -1,5 +1,3 @@
1
- import axios from 'axios';
2
- import MockAdapter from 'axios-mock-adapter';
3
1
  import configureMockStore from 'redux-mock-store';
4
2
  import thunk from 'redux-thunk';
5
3
  import Immutable from 'seamless-immutable';
@@ -13,10 +11,10 @@ import {
13
11
  import { getTaskSuccessResponse } from '../../../Tasks/__tests__/task.fixtures';
14
12
 
15
13
  import { loadUpstreamSubscriptions, saveUpstreamSubscriptions } from '../UpstreamSubscriptionsActions';
14
+ import { mock as mockApi, mockErrorRequest } from '../../../../mockRequest';
16
15
 
17
16
  const mockStore = configureMockStore([thunk]);
18
17
  const store = mockStore({ subscriptions: Immutable({}) });
19
- const mockApi = new MockAdapter(axios);
20
18
 
21
19
  afterEach(() => {
22
20
  store.clearActions();
@@ -28,7 +26,10 @@ describe('upstream subscription actions', () => {
28
26
 
29
27
  describe('creates UPSTREAM_SUBSCRIPTIONS_REQUEST', () => {
30
28
  it('and then fails with 422', () => {
31
- mockApi.onGet(url).reply(422);
29
+ mockErrorRequest({
30
+ url,
31
+ status: 422,
32
+ });
32
33
 
33
34
  return store.dispatch(loadUpstreamSubscriptions())
34
35
  .then(() => expect(store.getActions()).toEqual(getFailureActions));
@@ -48,7 +49,11 @@ describe('upstream subscription actions', () => {
48
49
  };
49
50
 
50
51
  it('and then fails with 422', () => {
51
- mockApi.onPost(url).reply(422);
52
+ mockErrorRequest({
53
+ url,
54
+ status: 422,
55
+ method: 'POST',
56
+ });
52
57
 
53
58
  return store.dispatch(saveUpstreamSubscriptions(subscriptionData))
54
59
  .then(() => expect(store.getActions()).toEqual(saveFailureActions));
@@ -6,17 +6,62 @@ import { successState } from './upstreamSubscriptions.fixtures';
6
6
  import { loadUpstreamSubscriptions, saveUpstreamSubscriptions } from '../UpstreamSubscriptionsActions';
7
7
 
8
8
  jest.mock('../../../../move_to_foreman/foreman_toast_notifications');
9
+ jest.mock('foremanReact/components/BreadcrumbBar');
9
10
 
10
11
  describe('upstream subscriptions page', () => {
11
- const mockHistory = { push: () => {} };
12
-
13
- it('should render', async () => {
14
- const page = shallow(<UpstreamSubscriptionsPage
12
+ let shallowWrapper;
13
+ beforeEach(() => {
14
+ shallowWrapper = shallow(<UpstreamSubscriptionsPage
15
15
  upstreamSubscriptions={successState}
16
16
  loadUpstreamSubscriptions={loadUpstreamSubscriptions}
17
17
  saveUpstreamSubscriptions={saveUpstreamSubscriptions}
18
18
  history={mockHistory}
19
19
  />);
20
- expect(toJson(page)).toMatchSnapshot();
20
+ });
21
+ const mockHistory = { push: () => {} };
22
+
23
+ it('should render', async () => {
24
+ expect(toJson(shallowWrapper)).toMatchSnapshot();
25
+ });
26
+
27
+ it('should validate correct subscription quantities', async () => {
28
+ const validPools = [
29
+ { available: 10, updatedQuantity: 5 },
30
+ { available: 10, updatedQuantity: '5' },
31
+ { available: 10, updatedQuantity: '10' },
32
+ { available: 10, updatedQuantity: '1' },
33
+ { available: -1, updatedQuantity: '1000' },
34
+ ];
35
+ validPools.forEach((pool, i) => {
36
+ // using object with index attribute to print out index on failure,
37
+ // jest doesn't support messages on failure :(
38
+ const result = shallowWrapper.instance().quantityValidation(pool)[0];
39
+ expect({ index: i, result }).toEqual({ index: i, result: true });
40
+ });
41
+ });
42
+
43
+ it('should invalidate incorrect subscription quantities', async () => {
44
+ const invalidPools = [
45
+ { available: 10, updatedQuantity: 11 },
46
+ { available: 10, updatedQuantity: 'foo' },
47
+ { available: 10, updatedQuantity: 0 },
48
+ { available: 10, updatedQuantity: '0' },
49
+ { available: 10, updatedQuantity: '11' },
50
+ { available: 10, updatedQuantity: '2.0' },
51
+ { available: 10, updatedQuantity: '2/3' },
52
+ { available: -1, updatedQuantity: '-1' },
53
+ { available: -1, updatedQuantity: '0' },
54
+ { available: -1, updatedQuantity: 'foo' },
55
+ { available: -1, updatedQuantity: '2/3' },
56
+ { available: -1, updatedQuantity: '2.0' },
57
+ { available: -1, updatedQuantity: '99999999999' },
58
+ ];
59
+
60
+ invalidPools.forEach((pool, i) => {
61
+ // using object with index attribute to print out index on failure,
62
+ // jest doesn't support messages on failure :(
63
+ const result = shallowWrapper.instance().quantityValidation(pool)[0];
64
+ expect({ index: i, result }).toEqual({ index: i, result: false });
65
+ });
21
66
  });
22
67
  });
@@ -37,7 +37,9 @@ describe('upstream subscriptions reducer', () => {
37
37
  it('should have error on UPSTREAM_SUBSCRIPTIONS_FAILURE', () => {
38
38
  expect(reducer(initialState, {
39
39
  type: types.UPSTREAM_SUBSCRIPTIONS_FAILURE,
40
- error: 'Unable to process request.',
40
+ payload: {
41
+ message: 'Unable to process request.',
42
+ },
41
43
  })).toEqual(errorState);
42
44
  });
43
45
 
@@ -48,10 +50,13 @@ describe('upstream subscriptions reducer', () => {
48
50
  })).toEqual(saveSuccessState);
49
51
  });
50
52
 
51
- it('should have error on SAVE_UPSTREAM_SUBSCRIPTIONS_SUCCESS', () => {
53
+ it('should have error on SAVE_UPSTREAM_SUBSCRIPTIONS_FAILURE', () => {
52
54
  expect(reducer(initialSaveState, {
53
55
  type: types.SAVE_UPSTREAM_SUBSCRIPTIONS_FAILURE,
54
- result: errorResult,
56
+ payload: {
57
+ message: 'Unable to process request.',
58
+ result: errorResult,
59
+ },
55
60
  })).toEqual(saveErrorState);
56
61
  });
57
62
  });
@@ -6,12 +6,26 @@ exports[`upstream subscriptions page should render 1`] = `
6
6
  componentClass="div"
7
7
  fluid={false}
8
8
  >
9
- <h1>
10
- Add Subscriptions
11
- </h1>
9
+ <BreadcrumbsBar
10
+ data={
11
+ Object {
12
+ "breadcrumbItems": Array [
13
+ Object {
14
+ "caption": "Subscriptions",
15
+ "onClick": [Function],
16
+ },
17
+ Object {
18
+ "caption": "Add Subscriptions",
19
+ },
20
+ ],
21
+ "isSwitchable": false,
22
+ }
23
+ }
24
+ />
12
25
  <LoadingState
13
26
  loading={false}
14
27
  loadingText="Loading"
28
+ timeout={300}
15
29
  >
16
30
  <Row
17
31
  bsClass="row"
@@ -107,7 +121,7 @@ exports[`upstream subscriptions page should render 1`] = `
107
121
  ],
108
122
  "label": "Available Entitlements",
109
123
  },
110
- "property": "quantity",
124
+ "property": "available",
111
125
  },
112
126
  Object {
113
127
  "cell": Object {
@@ -128,15 +142,11 @@ exports[`upstream subscriptions page should render 1`] = `
128
142
  emptyState={
129
143
  Object {
130
144
  "action": Object {
131
- "title": "New Subscription Allocation",
132
- "url": "http://redhat.com",
145
+ "title": "Import a Manifest to Begin",
146
+ "url": "/subscriptions",
133
147
  },
134
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.",
135
149
  "docUrl": "http://redhat.com",
136
- "documentation": Object {
137
- "title": "Learn more about Subscription Allocations",
138
- "url": "http://redhat.com",
139
- },
140
150
  "header": "There are no Subscription Allocations to display",
141
151
  }
142
152
  }
@@ -193,7 +203,7 @@ exports[`upstream subscriptions page should render 1`] = `
193
203
  block={false}
194
204
  bsClass="btn"
195
205
  bsStyle="primary"
196
- disabled={false}
206
+ disabled={true}
197
207
  onClick={[Function]}
198
208
  type="submit"
199
209
  >
@@ -1,5 +1,6 @@
1
1
  import Immutable from 'seamless-immutable';
2
2
  import { getTaskSuccessResponse } from '../../../Tasks/__tests__/task.fixtures';
3
+ import { toastErrorAction, failureAction } from '../../../../services/api/testHelpers';
3
4
 
4
5
  export const initialState = Immutable({
5
6
  loading: true,
@@ -135,10 +136,8 @@ export const getFailureActions = [
135
136
  {
136
137
  type: 'UPSTREAM_SUBSCRIPTIONS_REQUEST',
137
138
  },
138
- {
139
- result: new Error('Request failed with status code 422'),
140
- type: 'UPSTREAM_SUBSCRIPTIONS_FAILURE',
141
- },
139
+ failureAction('UPSTREAM_SUBSCRIPTIONS_FAILURE'),
140
+ toastErrorAction(),
142
141
  ];
143
142
 
144
143
  export const saveSuccessActions = [
@@ -155,8 +154,6 @@ export const saveFailureActions = [
155
154
  {
156
155
  type: 'SAVE_UPSTREAM_SUBSCRIPTIONS_REQUEST',
157
156
  },
158
- {
159
- result: new Error('Request failed with status code 422'),
160
- type: 'SAVE_UPSTREAM_SUBSCRIPTIONS_FAILURE',
161
- },
157
+ failureAction('SAVE_UPSTREAM_SUBSCRIPTIONS_FAILURE'),
158
+ toastErrorAction(),
162
159
  ];
@@ -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}
@@ -34,14 +34,18 @@ describe('subscriptions reducer', () => {
34
34
  it('should have error on SUBSCRIPTIONS_FAILURE', () => {
35
35
  expect(reducer(initialState, {
36
36
  type: types.SUBSCRIPTIONS_FAILURE,
37
- error: 'Unable to process request.',
37
+ payload: {
38
+ message: 'Unable to process request.',
39
+ },
38
40
  })).toEqual(errorState);
39
41
  });
40
42
 
41
43
  it('should have error on UPDATE_QUANTITY_FAILURE', () => {
42
44
  expect(reducer(initialState, {
43
45
  type: types.UPDATE_QUANTITY_FAILURE,
44
- error: 'Unable to process request.',
46
+ payload: {
47
+ message: 'Unable to process request.',
48
+ },
45
49
  })).toEqual(errorState);
46
50
  });
47
51
 
@@ -61,7 +65,9 @@ describe('subscriptions reducer', () => {
61
65
  it('should have error on SUBSCRIPTIONS_QUANTITIES_FAILURE', () => {
62
66
  expect(reducer(successState, {
63
67
  type: types.SUBSCRIPTIONS_QUANTITIES_FAILURE,
64
- error: 'Unable to process request.',
68
+ payload: {
69
+ message: 'Unable to process request.',
70
+ },
65
71
  })).toEqual(quantitiesErrorState);
66
72
  });
67
73
  });
@@ -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
  />
@@ -1,4 +1,5 @@
1
1
  import Immutable from 'seamless-immutable';
2
+ import { toastErrorAction, failureAction } from '../../../services/api/testHelpers';
2
3
 
3
4
  export const initialState = Immutable({
4
5
  loading: true,
@@ -9,7 +10,7 @@ export const initialState = Immutable({
9
10
  },
10
11
  itemCount: 0,
11
12
  quantitiesLoading: false,
12
- availableQuantities: {},
13
+ availableQuantities: null,
13
14
  tasks: [],
14
15
  });
15
16
 
@@ -242,13 +243,12 @@ export const successState = Immutable({
242
243
  },
243
244
  itemCount: 81,
244
245
  quantitiesLoading: false,
245
- availableQuantities: {},
246
+ availableQuantities: null,
246
247
  tasks: [],
247
248
  });
248
249
 
249
250
  export const errorState = Immutable({
250
251
  loading: false,
251
- error: 'Unable to process request.',
252
252
  pagination: {
253
253
  page: 0,
254
254
  perPage: 20,
@@ -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,7 +278,7 @@ export const loadingQuantitiesState = Immutable({
278
278
  export const quantitiesErrorState = Immutable({
279
279
  ...successState,
280
280
  quantitiesLoading: false,
281
- quantitiesError: 'Unable to process request.',
281
+ availableQuantities: {},
282
282
  });
283
283
 
284
284
  export const successActions = [
@@ -310,10 +310,8 @@ export const failureActions = [
310
310
  {
311
311
  type: 'SUBSCRIPTIONS_REQUEST',
312
312
  },
313
- {
314
- error: 'Request failed with status code 422',
315
- type: 'SUBSCRIPTIONS_FAILURE',
316
- },
313
+ failureAction('SUBSCRIPTIONS_FAILURE'),
314
+ toastErrorAction(),
317
315
  ];
318
316
 
319
317
  export const poolsUpdate = [{
@@ -340,20 +338,16 @@ export const updateQuantityFailureActions = [
340
338
  type: 'UPDATE_QUANTITY_REQUEST',
341
339
  quantities: poolsUpdate,
342
340
  },
343
- {
344
- error: 'Request failed with status code 422',
345
- type: 'UPDATE_QUANTITY_FAILURE',
346
- },
341
+ failureAction('UPDATE_QUANTITY_FAILURE'),
342
+ toastErrorAction(),
347
343
  ];
348
344
 
349
345
  export const loadQuantitiesFailureActions = [
350
346
  {
351
347
  type: 'SUBSCRIPTIONS_QUANTITIES_REQUEST',
352
348
  },
353
- {
354
- error: 'Request failed with status code 500',
355
- type: 'SUBSCRIPTIONS_QUANTITIES_FAILURE',
356
- },
349
+ failureAction('SUBSCRIPTIONS_QUANTITIES_FAILURE', 'Request failed with status code 500'),
350
+ toastErrorAction('Request failed with status code 500'),
357
351
  ];
358
352
 
359
353
  export const loadQuantitiesSuccessActions = [
@@ -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
  >
@@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
3
3
  import classNames from 'classnames';
4
4
  import { sprintf } from 'jed';
5
5
  import { cloneDeep, findIndex, isEqual } from 'lodash';
6
- import { Table, Alert } from 'patternfly-react';
6
+ import { Table } from 'patternfly-react';
7
7
  import { LoadingState } from '../../../../move_to_pf/LoadingState';
8
8
  import { Table as ForemanTable, TableBody as ForemanTableBody } from '../../../../move_to_foreman/components/common/table';
9
9
  import ConfirmDialog from '../../../../move_to_foreman/components/common/ConfirmDialog';
@@ -11,36 +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
- };
27
-
28
- const ErrorAlerts = ({ errors }) => {
29
- const alerts = errors.filter(Boolean).map(e => (
30
- <Alert type={Alert.ALERT_TYPE_ERROR} key={e}>
31
- <span>{e}</span>
32
- </Alert>
33
- ));
34
-
35
- return (
36
- <div>
37
- {alerts}
38
- </div>
39
- );
40
- };
41
- ErrorAlerts.propTypes = {
42
- errors: PropTypes.arrayOf(PropTypes.string).isRequired,
43
- };
14
+ import { renderTaskStartedToast } from '../../../Tasks/helpers';
15
+ import {
16
+ BLOCKING_FOREMAN_TASK_TYPES,
17
+ MANIFEST_TASKS_BULK_SEARCH_ID,
18
+ } from '../../SubscriptionConstants';
44
19
 
45
20
  class SubscriptionsTable extends Component {
46
21
  constructor(props) {
@@ -49,7 +24,7 @@ class SubscriptionsTable extends Component {
49
24
  this.state = {
50
25
  rows: undefined,
51
26
  subscriptions: undefined,
52
- groupdSubscriptions: undefined,
27
+ groupedSubscriptions: undefined,
53
28
  updatedQuantity: {},
54
29
  editing: false,
55
30
  showUpdateConfirmDialog: false,
@@ -64,14 +39,14 @@ class SubscriptionsTable extends Component {
64
39
  nextProps.subscriptions !== undefined &&
65
40
  !isEqual(nextProps.subscriptions, prevState.subscriptions)
66
41
  ) {
67
- const groupdSubscriptions = groupSubscriptionsByProductId(nextProps.subscriptions);
42
+ const groupedSubscriptions = groupSubscriptionsByProductId(nextProps.subscriptions);
68
43
  const rows = buildTableRows(
69
- groupdSubscriptions,
44
+ groupedSubscriptions,
70
45
  nextProps.subscriptions.availableQuantities,
71
46
  prevState.updatedQuantity,
72
47
  );
73
48
 
74
- return { rows, groupdSubscriptions, subscriptions: nextProps.subscriptions };
49
+ return { rows, groupedSubscriptions, subscriptions: nextProps.subscriptions };
75
50
  }
76
51
 
77
52
  return null;
@@ -79,19 +54,19 @@ class SubscriptionsTable extends Component {
79
54
 
80
55
  toggleSubscriptionGroup(groupId) {
81
56
  const { subscriptions } = this.props;
82
- const { groupdSubscriptions, updatedQuantity } = this.state;
83
- const { open } = groupdSubscriptions[groupId];
57
+ const { groupedSubscriptions, updatedQuantity } = this.state;
58
+ const { open } = groupedSubscriptions[groupId];
84
59
 
85
- groupdSubscriptions[groupId].open = !open;
60
+ groupedSubscriptions[groupId].open = !open;
86
61
 
87
62
 
88
63
  const rows = buildTableRows(
89
- groupdSubscriptions,
64
+ groupedSubscriptions,
90
65
  subscriptions.availableQuantities,
91
66
  updatedQuantity,
92
67
  );
93
68
 
94
- this.setState({ rows, groupdSubscriptions });
69
+ this.setState({ rows, groupedSubscriptions });
95
70
  }
96
71
 
97
72
  enableEditing(editingState) {
@@ -102,11 +77,11 @@ class SubscriptionsTable extends Component {
102
77
  }
103
78
 
104
79
  updateRows(updatedQuantity) {
105
- const { groupdSubscriptions } = this.state;
80
+ const { groupedSubscriptions } = this.state;
106
81
  const { subscriptions } = this.props;
107
82
 
108
83
  const rows = buildTableRows(
109
- groupdSubscriptions,
84
+ groupedSubscriptions,
110
85
  subscriptions.availableQuantities,
111
86
  updatedQuantity,
112
87
  );
@@ -134,7 +109,15 @@ class SubscriptionsTable extends Component {
134
109
  confirmEdit() {
135
110
  this.showUpdateConfirm(false);
136
111
  if (Object.keys(this.state.updatedQuantity).length > 0) {
137
- 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));
138
121
  }
139
122
  this.enableEditing(false);
140
123
  }
@@ -157,16 +140,17 @@ class SubscriptionsTable extends Component {
157
140
  }
158
141
 
159
142
  render() {
160
- const { subscriptions } = this.props;
161
- const { groupdSubscriptions } = this.state;
143
+ const { subscriptions, emptyState } = this.props;
144
+ const { groupedSubscriptions } = this.state;
145
+ const allSubscriptionResults = subscriptions.results;
162
146
 
163
147
  const groupingController = {
164
148
  isCollapseable: ({ rowData }) =>
165
149
  // it is the first subscription in the group
166
- rowData.id === groupdSubscriptions[rowData.product_id].subscriptions[0].id &&
150
+ rowData.id === groupedSubscriptions[rowData.product_id].subscriptions[0].id &&
167
151
  // the group contains more then one subscription
168
- groupdSubscriptions[rowData.product_id].subscriptions.length > 1,
169
- isCollapsed: ({ rowData }) => !groupdSubscriptions[rowData.product_id].open,
152
+ groupedSubscriptions[rowData.product_id].subscriptions.length > 1,
153
+ isCollapsed: ({ rowData }) => !groupedSubscriptions[rowData.product_id].open,
170
154
  toggle: ({ rowData }) => this.toggleSubscriptionGroup(rowData.product_id),
171
155
  };
172
156
 
@@ -202,7 +186,8 @@ class SubscriptionsTable extends Component {
202
186
  },
203
187
  };
204
188
 
205
- const checkAllRowsSelected = () => this.state.rows.length === this.state.selectedRows.length;
189
+ const checkAllRowsSelected = () =>
190
+ allSubscriptionResults.length === this.state.selectedRows.length;
206
191
 
207
192
  const updateDeleteButton = () => {
208
193
  this.props.toggleDeleteButton(this.state.selectedRows.length > 0);
@@ -218,7 +203,7 @@ class SubscriptionsTable extends Component {
218
203
  );
219
204
  } else {
220
205
  this.setState(
221
- { selectedRows: this.state.rows.map(row => row.id) },
206
+ { selectedRows: allSubscriptionResults.map(row => row.id) },
222
207
  updateDeleteButton,
223
208
  );
224
209
  }
@@ -246,7 +231,7 @@ class SubscriptionsTable extends Component {
246
231
  };
247
232
 
248
233
  let bodyMessage;
249
- if (subscriptions.results.length === 0 && subscriptions.searchIsActive) {
234
+ if (allSubscriptionResults.length === 0 && subscriptions.searchIsActive) {
250
235
  bodyMessage = __('No subscriptions match your search criteria.');
251
236
  }
252
237
 
@@ -258,15 +243,9 @@ class SubscriptionsTable extends Component {
258
243
 
259
244
  return (
260
245
  <LoadingState loading={subscriptions.loading} loadingText={__('Loading')}>
261
- <ErrorAlerts
262
- errors={[
263
- subscriptions.error,
264
- subscriptions.quantitiesError,
265
- ]}
266
- />
267
246
  <ForemanTable
268
247
  columns={columnsDefinition}
269
- emptyState={emptyStateData}
248
+ emptyState={emptyState}
270
249
  bodyMessage={bodyMessage}
271
250
  rows={this.state.rows}
272
251
  components={{
@@ -352,6 +331,7 @@ class SubscriptionsTable extends Component {
352
331
  SubscriptionsTable.propTypes = {
353
332
  loadSubscriptions: PropTypes.func.isRequired,
354
333
  updateQuantity: PropTypes.func.isRequired,
334
+ emptyState: PropTypes.shape({}).isRequired,
355
335
  subscriptions: PropTypes.shape({
356
336
  results: PropTypes.array,
357
337
  }).isRequired,
@@ -359,6 +339,13 @@ SubscriptionsTable.propTypes = {
359
339
  onDeleteSubscriptions: PropTypes.func.isRequired,
360
340
  onSubscriptionDeleteModalClose: PropTypes.func.isRequired,
361
341
  toggleDeleteButton: PropTypes.func.isRequired,
342
+ task: PropTypes.shape({}),
343
+ bulkSearch: PropTypes.func,
344
+ };
345
+
346
+ SubscriptionsTable.defaultProps = {
347
+ task: { humanized: {} },
348
+ bulkSearch: undefined,
362
349
  };
363
350
 
364
351
  export default SubscriptionsTable;