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.
- checksums.yaml +4 -4
- data/app/assets/javascripts/katello/common/index.js +1 -0
- data/app/assets/javascripts/katello/sync_management/index.js +1 -0
- data/app/controllers/katello/api/registry/registry_proxies_controller.rb +1 -1
- data/app/controllers/katello/api/v2/environments_controller.rb +0 -1
- data/app/controllers/katello/api/v2/ostree_branches_controller.rb +1 -1
- data/app/controllers/katello/api/v2/repository_sets_controller.rb +10 -1
- data/app/controllers/katello/remote_execution_controller.rb +6 -6
- data/app/helpers/katello/hosts_and_hostgroups_helper.rb +37 -9
- data/app/lib/katello/resources/registry.rb +4 -4
- data/app/models/katello/authorization/repository.rb +2 -1
- data/app/models/katello/content_view.rb +12 -4
- data/app/models/katello/glue/candlepin/owner.rb +0 -8
- data/app/models/katello/glue/candlepin/pool.rb +11 -11
- data/app/models/katello/kt_environment.rb +0 -6
- data/app/models/katello/product_content.rb +4 -1
- data/app/models/katello/rpm.rb +13 -5
- data/app/services/katello/ui_notifications/pulp/proxy_disk_space.rb +3 -1
- data/app/views/overrides/activation_keys/_host_environment_select.html.erb +2 -3
- data/config/katello.yaml.example +5 -0
- data/config/routes.rb +1 -0
- data/db/seeds.d/75-job_templates.rb +5 -2
- data/engines/bastion_katello/app/assets/javascripts/bastion_katello/environments/details/views/environment-details.html +43 -8
- data/engines/bastion_katello/app/assets/javascripts/bastion_katello/products/discovery/discovery.controller.js +17 -2
- data/engines/bastion_katello/app/assets/javascripts/bastion_katello/products/discovery/views/discovery-create.html +1 -1
- data/engines/bastion_katello/app/assets/javascripts/bastion_katello/products/discovery/views/discovery.html +1 -1
- data/engines/bastion_katello/app/assets/stylesheets/bastion_katello/bastion_katello.scss +5 -0
- data/lib/katello/version.rb +1 -1
- data/package.json +11 -7
- data/webpack/__mocks__/foremanReact/components/BreadcrumbBar.js +3 -0
- data/webpack/__mocks__/foremanReact/redux/actions/toasts.js +8 -0
- data/webpack/__mocks__/foremanReact/redux.js +3 -0
- data/webpack/components/Search/Search.test.js +3 -1
- data/webpack/components/SelectOrg/SelectOrg.scss +3 -0
- data/webpack/components/SelectOrg/SelectOrgAction.js +41 -0
- data/webpack/components/SelectOrg/SelectOrgReducer.js +33 -0
- data/webpack/components/SelectOrg/SetOrganization.js +116 -0
- data/webpack/components/WithOrganization/withOrganization.js +28 -0
- data/webpack/containers/Application/config.js +9 -2
- data/webpack/containers/Application/index.js +4 -2
- data/webpack/global_test_setup.js +6 -0
- data/webpack/helpers/caret.js +6 -0
- data/webpack/mockRequest.js +3 -3
- data/webpack/move_to_foreman/common/helpers.js +45 -8
- data/webpack/move_to_foreman/components/common/{emptyState → EmptyState}/index.js +16 -3
- data/webpack/move_to_foreman/components/common/table/components/Table.js +1 -1
- data/webpack/move_to_foreman/components/common/table/components/__snapshots__/CollapseSubscriptionGroupButton.test.js.snap +2 -2
- data/webpack/move_to_foreman/components/common/table/components/__snapshots__/TableSelectionCell.test.js.snap +1 -1
- data/webpack/move_to_foreman/components/common/table/components/__snapshots__/TableSelectionHeaderCell.test.js.snap +1 -1
- data/webpack/move_to_pf/LoadingState/LoadingState.js +27 -14
- data/webpack/move_to_pf/LoadingState/LoadingState.test.js +8 -4
- data/webpack/move_to_pf/Select/Select.js +40 -0
- data/webpack/move_to_pf/react-bootstrap-select/index.js +12 -1
- data/webpack/redux/actions/RedHatRepositories/enabled.js +0 -1
- data/webpack/redux/actions/RedHatRepositories/helpers.js +5 -5
- data/webpack/redux/actions/RedHatRepositories/sets.js +1 -1
- data/webpack/redux/consts.js +6 -0
- data/webpack/redux/reducers/index.js +2 -0
- data/webpack/scenes/RedHatRepositories/components/EnabledRepository.js +14 -23
- data/webpack/scenes/RedHatRepositories/components/EnabledRepositoryContent.js +34 -0
- data/webpack/scenes/RedHatRepositories/components/RepositorySetRepository.js +1 -1
- data/webpack/scenes/RedHatRepositories/components/SearchBar.js +1 -0
- data/webpack/scenes/RedHatRepositories/components/__tests__/EnabledRepository.test.js +36 -0
- data/webpack/scenes/RedHatRepositories/components/__tests__/EnabledRepositoryContent.test.js +27 -0
- data/webpack/scenes/RedHatRepositories/components/__tests__/__snapshots__/EnabledRepository.test.js.snap +25 -0
- data/webpack/scenes/RedHatRepositories/components/__tests__/__snapshots__/EnabledRepositoryContent.test.js.snap +47 -0
- data/webpack/scenes/RedHatRepositories/components/__tests__/__snapshots__/RecommendedRepositorySetsToggler.test.js.snap +3 -1
- data/webpack/scenes/RedHatRepositories/index.js +7 -3
- data/webpack/scenes/RedHatRepositories/index.scss +1 -0
- data/webpack/scenes/Subscriptions/Details/SubscriptionDetailActions.js +3 -8
- data/webpack/scenes/Subscriptions/Details/SubscriptionDetailProducts.js +5 -3
- data/webpack/scenes/Subscriptions/Details/SubscriptionDetailReducer.js +1 -1
- data/webpack/scenes/Subscriptions/Details/SubscriptionDetails.js +44 -6
- data/webpack/scenes/Subscriptions/Details/SubscriptionDetails.scss +4 -0
- data/webpack/scenes/Subscriptions/Details/__tests__/SubscriptionDetailReducer.test.js +3 -1
- data/webpack/scenes/Subscriptions/Details/__tests__/SubscriptionDetails.test.js +2 -1
- data/webpack/scenes/Subscriptions/Details/__tests__/__snapshots__/SubscriptionDetailProducts.test.js.snap +113 -23
- data/webpack/scenes/Subscriptions/Details/__tests__/__snapshots__/SubscriptionDetails.test.js.snap +23 -14
- data/webpack/scenes/Subscriptions/Details/__tests__/subscriptionDetails.fixtures.js +3 -4
- data/webpack/scenes/Subscriptions/Details/index.js +2 -2
- data/webpack/scenes/Subscriptions/Manifest/ManageManifestModal.js +78 -34
- data/webpack/scenes/Subscriptions/Manifest/ManifestActions.js +5 -24
- data/webpack/scenes/Subscriptions/Manifest/ManifestHistoryReducer.js +9 -1
- data/webpack/scenes/Subscriptions/Manifest/__tests__/ManageManifestModal.test.js +3 -0
- data/webpack/scenes/Subscriptions/Manifest/__tests__/ManifestActions.test.js +20 -8
- data/webpack/scenes/Subscriptions/Manifest/__tests__/ManifestHistoryReducer.test.js +3 -1
- data/webpack/scenes/Subscriptions/Manifest/__tests__/__snapshots__/ManageManifestModal.test.js.snap +34 -7
- data/webpack/scenes/Subscriptions/Manifest/__tests__/manifest.fixtures.js +9 -16
- data/webpack/scenes/Subscriptions/Manifest/index.js +1 -0
- data/webpack/scenes/Subscriptions/SubscriptionActions.js +5 -26
- data/webpack/scenes/Subscriptions/SubscriptionConstants.js +1 -0
- data/webpack/scenes/Subscriptions/SubscriptionHelpers.js +3 -0
- data/webpack/scenes/Subscriptions/SubscriptionReducer.js +11 -4
- data/webpack/scenes/Subscriptions/SubscriptionsPage.js +31 -36
- data/webpack/scenes/Subscriptions/UpstreamSubscriptions/UpstreamSubscriptionsActions.js +3 -12
- data/webpack/scenes/Subscriptions/UpstreamSubscriptions/UpstreamSubscriptionsPage.js +57 -27
- data/webpack/scenes/Subscriptions/UpstreamSubscriptions/UpstreamSubscriptionsReducer.js +2 -3
- data/webpack/scenes/Subscriptions/UpstreamSubscriptions/UpstreamSubscriptionsTableSchema.js +10 -5
- data/webpack/scenes/Subscriptions/UpstreamSubscriptions/__tests__/UpstreamSubscriptionsActions.test.js +10 -5
- data/webpack/scenes/Subscriptions/UpstreamSubscriptions/__tests__/UpstreamSubscriptionsPage.test.js +50 -5
- data/webpack/scenes/Subscriptions/UpstreamSubscriptions/__tests__/UpstreamSubscriptionsReducer.test.js +8 -3
- data/webpack/scenes/Subscriptions/UpstreamSubscriptions/__tests__/__snapshots__/UpstreamSubscriptionsPage.test.js.snap +21 -11
- data/webpack/scenes/Subscriptions/UpstreamSubscriptions/__tests__/upstreamSubscriptions.fixtures.js +5 -8
- data/webpack/scenes/Subscriptions/__tests__/SubscriptionsPage.test.js +2 -0
- data/webpack/scenes/Subscriptions/__tests__/SubscriptionsReducer.test.js +9 -3
- data/webpack/scenes/Subscriptions/__tests__/__snapshots__/SubscriptionsPage.test.js.snap +14 -2
- data/webpack/scenes/Subscriptions/__tests__/subscriptions.fixtures.js +11 -17
- data/webpack/scenes/Subscriptions/components/SubscriptionsTable/EntitlementsInlineEditFormatter.js +8 -5
- data/webpack/scenes/Subscriptions/components/SubscriptionsTable/SubscriptionsTable.js +45 -58
- data/webpack/scenes/Subscriptions/components/SubscriptionsTable/SubscriptionsTableHelpers.js +11 -4
- data/webpack/scenes/Subscriptions/components/SubscriptionsTable/SubscriptionsTableSchema.js +2 -2
- data/webpack/scenes/Subscriptions/components/SubscriptionsTable/__tests__/EntitlementsInlineEditFormatter.test.js +110 -0
- data/webpack/scenes/Subscriptions/components/SubscriptionsTable/__tests__/SubscriptionsTable.test.js +16 -3
- data/webpack/scenes/Subscriptions/components/SubscriptionsTable/__tests__/__snapshots__/EntitlementsInlineEditFormatter.test.js.snap +228 -0
- data/webpack/scenes/Subscriptions/components/SubscriptionsTable/__tests__/__snapshots__/SubscriptionsTable.test.js.snap +392 -365
- data/webpack/scenes/Subscriptions/index.js +1 -0
- data/webpack/scenes/Tasks/helpers.js +52 -0
- data/webpack/services/api/index.js +17 -1
- data/webpack/services/api/testHelpers.js +28 -0
- data/webpack/test_setup.js +2 -0
- metadata +24 -5
- data/config/katello.yaml +0 -89
- 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
|
-
|
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
|
-
|
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));
|
data/webpack/scenes/Subscriptions/UpstreamSubscriptions/__tests__/UpstreamSubscriptionsPage.test.js
CHANGED
@@ -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
|
-
|
12
|
-
|
13
|
-
|
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
|
-
|
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
|
-
|
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
|
53
|
+
it('should have error on SAVE_UPSTREAM_SUBSCRIPTIONS_FAILURE', () => {
|
52
54
|
expect(reducer(initialSaveState, {
|
53
55
|
type: types.SAVE_UPSTREAM_SUBSCRIPTIONS_FAILURE,
|
54
|
-
|
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
|
-
<
|
10
|
-
|
11
|
-
|
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": "
|
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": "
|
132
|
-
"url": "
|
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={
|
206
|
+
disabled={true}
|
197
207
|
onClick={[Function]}
|
198
208
|
type="submit"
|
199
209
|
>
|
data/webpack/scenes/Subscriptions/UpstreamSubscriptions/__tests__/upstreamSubscriptions.fixtures.js
CHANGED
@@ -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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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":
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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 = [
|
data/webpack/scenes/Subscriptions/components/SubscriptionsTable/EntitlementsInlineEditFormatter.js
CHANGED
@@ -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
|
-
|
44
|
-
|
45
|
-
|
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={
|
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
|
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
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
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
|
-
|
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
|
42
|
+
const groupedSubscriptions = groupSubscriptionsByProductId(nextProps.subscriptions);
|
68
43
|
const rows = buildTableRows(
|
69
|
-
|
44
|
+
groupedSubscriptions,
|
70
45
|
nextProps.subscriptions.availableQuantities,
|
71
46
|
prevState.updatedQuantity,
|
72
47
|
);
|
73
48
|
|
74
|
-
return { rows,
|
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 {
|
83
|
-
const { open } =
|
57
|
+
const { groupedSubscriptions, updatedQuantity } = this.state;
|
58
|
+
const { open } = groupedSubscriptions[groupId];
|
84
59
|
|
85
|
-
|
60
|
+
groupedSubscriptions[groupId].open = !open;
|
86
61
|
|
87
62
|
|
88
63
|
const rows = buildTableRows(
|
89
|
-
|
64
|
+
groupedSubscriptions,
|
90
65
|
subscriptions.availableQuantities,
|
91
66
|
updatedQuantity,
|
92
67
|
);
|
93
68
|
|
94
|
-
this.setState({ rows,
|
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 {
|
80
|
+
const { groupedSubscriptions } = this.state;
|
106
81
|
const { subscriptions } = this.props;
|
107
82
|
|
108
83
|
const rows = buildTableRows(
|
109
|
-
|
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 {
|
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 ===
|
150
|
+
rowData.id === groupedSubscriptions[rowData.product_id].subscriptions[0].id &&
|
167
151
|
// the group contains more then one subscription
|
168
|
-
|
169
|
-
isCollapsed: ({ rowData }) => !
|
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 = () =>
|
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:
|
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 (
|
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={
|
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;
|