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
@@ -73,14 +73,19 @@ exports[`manage manifest modal should render 1`] = `
73
73
  <FormGroup
74
74
  bsClass="form-group"
75
75
  >
76
- <ControlLabel
77
- bsClass="control-label"
78
- className="col-sm-3"
79
- htmlFor="cdnUrl"
80
- srOnly={false}
76
+ <Col
77
+ bsClass="col"
78
+ componentClass="div"
79
+ sm={3}
81
80
  >
82
- Red Hat CDN URL
83
- </ControlLabel>
81
+ <ControlLabel
82
+ bsClass="control-label"
83
+ htmlFor="cdnUrl"
84
+ srOnly={false}
85
+ >
86
+ Red Hat CDN URL
87
+ </ControlLabel>
88
+ </Col>
84
89
  <Col
85
90
  bsClass="col"
86
91
  componentClass="div"
@@ -96,6 +101,27 @@ exports[`manage manifest modal should render 1`] = `
96
101
  />
97
102
  </Col>
98
103
  </FormGroup>
104
+ <FormGroup
105
+ bsClass="form-group"
106
+ >
107
+ <Col
108
+ bsClass="col"
109
+ componentClass="div"
110
+ sm={3}
111
+ smOffset={3}
112
+ >
113
+ <Button
114
+ active={false}
115
+ block={false}
116
+ bsClass="btn"
117
+ bsStyle="default"
118
+ disabled={false}
119
+ onClick={[Function]}
120
+ >
121
+ Update
122
+ </Button>
123
+ </Col>
124
+ </FormGroup>
99
125
  <br />
100
126
  <h5>
101
127
  Subscription Manifest
@@ -215,6 +241,7 @@ exports[`manage manifest modal should render 1`] = `
215
241
  <LoadingState
216
242
  loading={false}
217
243
  loadingText="Loading"
244
+ timeout={300}
218
245
  >
219
246
  <Table
220
247
  columns={
@@ -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 manifestHistoryInitialState = Immutable({
4
5
  loading: true,
@@ -149,10 +150,8 @@ export const manifestHistoryFailureActions = [
149
150
  {
150
151
  type: 'MANIFEST_HISTORY_REQUEST',
151
152
  },
152
- {
153
- result: new Error('Request failed with status code 422'),
154
- type: 'MANIFEST_HISTORY_FAILURE',
155
- },
153
+ failureAction('MANIFEST_HISTORY_FAILURE'),
154
+ toastErrorAction(),
156
155
  ];
157
156
 
158
157
  export const uploadManifestSuccessActions = [
@@ -169,10 +168,8 @@ export const uploadManifestFailureActions = [
169
168
  {
170
169
  type: 'UPLOAD_MANIFEST_REQUEST',
171
170
  },
172
- {
173
- result: new Error('Request failed with status code 422'),
174
- type: 'UPLOAD_MANIFEST_FAILURE',
175
- },
171
+ failureAction('UPLOAD_MANIFEST_FAILURE'),
172
+ toastErrorAction(),
176
173
  ];
177
174
 
178
175
  export const refreshManifestSuccessActions = [
@@ -189,10 +186,8 @@ export const refreshManifestFailureActions = [
189
186
  {
190
187
  type: 'REFRESH_MANIFEST_REQUEST',
191
188
  },
192
- {
193
- result: new Error('Request failed with status code 422'),
194
- type: 'REFRESH_MANIFEST_FAILURE',
195
- },
189
+ failureAction('REFRESH_MANIFEST_FAILURE'),
190
+ toastErrorAction(),
196
191
  ];
197
192
 
198
193
  export const deleteManifestSuccessActions = [
@@ -209,8 +204,6 @@ export const deleteManifestFailureActions = [
209
204
  {
210
205
  type: 'DELETE_MANIFEST_REQUEST',
211
206
  },
212
- {
213
- result: new Error('Request failed with status code 422'),
214
- type: 'DELETE_MANIFEST_FAILURE',
215
- },
207
+ failureAction('DELETE_MANIFEST_FAILURE'),
208
+ toastErrorAction(),
216
209
  ];
@@ -15,6 +15,7 @@ import './Manifest.scss';
15
15
  const mapStateToProps = state => ({
16
16
  organization: state.katello.organization,
17
17
  manifestHistory: state.katello.manifestHistory,
18
+ taskDetails: state.katello.manifestHistory.taskDetails,
18
19
  });
19
20
 
20
21
  // map action dispatchers to props
@@ -16,7 +16,7 @@ import {
16
16
  DELETE_SUBSCRIPTIONS_FAILURE,
17
17
  } from './SubscriptionConstants';
18
18
  import { filterRHSubscriptions } from './SubscriptionHelpers.js';
19
- import { getResponseError } from '../../move_to_foreman/common/helpers.js';
19
+ import { apiError } from '../../move_to_foreman/common/helpers.js';
20
20
 
21
21
  export const createSubscriptionParams = (extendedParams = {}) => ({
22
22
  ...{ organization_id: orgId() },
@@ -35,12 +35,7 @@ export const loadAvailableQuantities = (extendedParams = {}) => (dispatch) => {
35
35
  response: data,
36
36
  });
37
37
  })
38
- .catch((result) => {
39
- dispatch({
40
- type: SUBSCRIPTIONS_QUANTITIES_FAILURE,
41
- error: getResponseError(result.response),
42
- });
43
- });
38
+ .catch(result => dispatch(apiError(SUBSCRIPTIONS_QUANTITIES_FAILURE, result)));
44
39
  };
45
40
 
46
41
  export const loadSubscriptions = (extendedParams = {}) => (dispatch) => {
@@ -60,12 +55,7 @@ export const loadSubscriptions = (extendedParams = {}) => (dispatch) => {
60
55
  dispatch(loadAvailableQuantities({ poolIds }));
61
56
  }
62
57
  })
63
- .catch((result) => {
64
- dispatch({
65
- type: SUBSCRIPTIONS_FAILURE,
66
- error: getResponseError(result.response),
67
- });
68
- });
58
+ .catch(result => dispatch(apiError(SUBSCRIPTIONS_FAILURE, result)));
69
59
  };
70
60
 
71
61
  export const updateQuantity = (quantities = {}) => (dispatch) => {
@@ -83,12 +73,7 @@ export const updateQuantity = (quantities = {}) => (dispatch) => {
83
73
  response: data,
84
74
  });
85
75
  })
86
- .catch((result) => {
87
- dispatch({
88
- type: UPDATE_QUANTITY_FAILURE,
89
- error: getResponseError(result.response),
90
- });
91
- });
76
+ .catch(result => dispatch(apiError(UPDATE_QUANTITY_FAILURE, result)));
92
77
  };
93
78
 
94
79
  export const deleteSubscriptions = poolIds => (dispatch) => {
@@ -106,13 +91,7 @@ export const deleteSubscriptions = poolIds => (dispatch) => {
106
91
  response: data,
107
92
  });
108
93
  })
109
- .catch((result) => {
110
- dispatch({
111
- type: DELETE_SUBSCRIPTIONS_FAILURE,
112
- result,
113
- });
114
- });
94
+ .catch(result => dispatch(apiError(DELETE_SUBSCRIPTIONS_FAILURE, result)));
115
95
  };
116
96
 
117
-
118
97
  export default loadSubscriptions;
@@ -21,6 +21,7 @@ export const BLOCKING_FOREMAN_TASK_TYPES = [
21
21
  'Actions::Katello::UpstreamSubscriptions::BindEntitlements',
22
22
  'Actions::Katello::UpstreamSubscriptions::UpdateEntitlement',
23
23
  'Actions::Katello::UpstreamSubscriptions::RemoveEntitlements',
24
+ 'Actions::Katello::UpstreamSubscriptions::UpdateEntitlements',
24
25
  ];
25
26
 
26
27
  export const MANIFEST_TASKS_BULK_SEARCH_ID = 'activeManifestTasksSearch';
@@ -1,3 +1,6 @@
1
1
  // eslint-disable-next-line import/prefer-default-export
2
2
  export const filterRHSubscriptions = subscriptions =>
3
3
  subscriptions.filter(sub => sub.available >= 0);
4
+
5
+ export const manifestExists = organization =>
6
+ organization.owner_details && organization.owner_details.upstreamConsumer;
@@ -23,7 +23,7 @@ import { GET_SETTING_SUCCESS } from '../../move_to_foreman/Settings/SettingsCons
23
23
  const initialState = Immutable({
24
24
  ...initialApiState,
25
25
  quantitiesLoading: false,
26
- availableQuantities: {},
26
+ availableQuantities: null,
27
27
  tasks: [],
28
28
  });
29
29
 
@@ -73,15 +73,22 @@ export default (state = initialState, action) => {
73
73
  return state.set('loading', false);
74
74
 
75
75
  case SUBSCRIPTIONS_FAILURE:
76
+ return state
77
+ .set('loading', false)
78
+ .set('results', [])
79
+ .set('itemCount', 0);
80
+
76
81
  case UPDATE_QUANTITY_FAILURE:
77
82
  case DELETE_SUBSCRIPTIONS_FAILURE:
78
83
  return state.merge({
79
- error: action.error,
80
84
  loading: false,
81
85
  });
82
86
 
83
87
  case SUBSCRIPTIONS_QUANTITIES_REQUEST:
84
- return state.set('quantitiesLoading', true);
88
+ return state.merge({
89
+ quantitiesLoading: true,
90
+ availableQuantities: null,
91
+ });
85
92
 
86
93
  case SUBSCRIPTIONS_QUANTITIES_SUCCESS: {
87
94
  return state.merge({
@@ -93,7 +100,7 @@ export default (state = initialState, action) => {
93
100
  case SUBSCRIPTIONS_QUANTITIES_FAILURE: {
94
101
  return state.merge({
95
102
  quantitiesLoading: false,
96
- quantitiesError: action.error,
103
+ availableQuantities: {},
97
104
  });
98
105
  }
99
106
 
@@ -1,15 +1,14 @@
1
1
  import React, { Component } from 'react';
2
- import ReactDOMServer from 'react-dom/server';
3
2
  import PropTypes from 'prop-types';
4
3
  import { LinkContainer } from 'react-router-bootstrap';
5
4
  import { Grid, Row, Col, Form, FormGroup } from 'react-bootstrap';
6
5
  import { Button } from 'patternfly-react';
7
6
  import TooltipButton from 'react-bootstrap-tooltip-button';
8
- import { notify } from '../../move_to_foreman/foreman_toast_notifications';
9
- import helpers from '../../move_to_foreman/common/helpers';
7
+ import { renderTaskFinishedToast } from '../Tasks/helpers';
10
8
  import ModalProgressBar from '../../move_to_foreman/components/common/ModalProgressBar';
11
9
  import ManageManifestModal from './Manifest/';
12
10
  import { SubscriptionsTable } from './components/SubscriptionsTable';
11
+ import { manifestExists } from './SubscriptionHelpers';
13
12
  import Search from '../../components/Search/index';
14
13
  import api, { orgId } from '../../services/api';
15
14
  import { createSubscriptionParams } from './SubscriptionActions.js';
@@ -70,7 +69,7 @@ class SubscriptionsPage extends Component {
70
69
  }
71
70
 
72
71
  getDisabledReason(deleteButton) {
73
- const { tasks = [], subscriptions } = this.props;
72
+ const { tasks = [], subscriptions, organization } = this.props;
74
73
  const { disconnected } = subscriptions;
75
74
  let disabledReason = null;
76
75
 
@@ -79,7 +78,9 @@ class SubscriptionsPage extends Component {
79
78
  } else if (tasks.length > 0) {
80
79
  disabledReason = __('This is disabled because a manifest related task is in progress.');
81
80
  } else if (deleteButton && !disabledReason) {
82
- disabledReason = __('This is disabled because no subscriptions are selected');
81
+ disabledReason = __('This is disabled because no subscriptions are selected.');
82
+ } else if (!manifestExists(organization)) {
83
+ disabledReason = __('This is disabled because no manifest has been uploaded.');
83
84
  }
84
85
 
85
86
  return disabledReason;
@@ -103,39 +104,14 @@ class SubscriptionsPage extends Component {
103
104
 
104
105
  pollTaskUntilDone(taskToPoll.id, {}, POLL_TASK_INTERVAL)
105
106
  .then((task) => {
106
- function getErrors() {
107
- return (
108
- <ul>
109
- {task.humanized.errors.map(error => (
110
- <li key={error}> {error} </li>
111
- ))}
112
- </ul>
113
- );
114
- }
115
-
116
- const message = (
117
- <span>
118
- <span>
119
- {`${__(`Task ${task.humanized.action} completed with a result of ${task.result}.`)} `}
120
- </span>
121
- {task.errors ? getErrors() : ''}
122
- <a href={helpers.urlBuilder('foreman_tasks/tasks', '', task.id)}>
123
- {__('Click here to go to the tasks page for the task.')}
124
- </a>
125
- </span>
126
- );
127
-
128
- notify({
129
- message: ReactDOMServer.renderToStaticMarkup(message),
130
- type: task.result,
131
- });
132
-
107
+ renderTaskFinishedToast(task);
133
108
  loadSubscriptions();
134
109
  });
135
110
  }
136
111
 
137
112
  render() {
138
- const { tasks = [], subscriptions } = this.props;
113
+ const { tasks = [], subscriptions, organization } = this.props;
114
+ const currentOrg = orgId();
139
115
  const { disconnected } = subscriptions;
140
116
  const taskInProgress = tasks.length > 0;
141
117
  const disableManifestActions = taskInProgress || disconnected;
@@ -153,7 +129,7 @@ class SubscriptionsPage extends Component {
153
129
  const getAutoCompleteParams = search => ({
154
130
  endpoint: '/subscriptions/auto_complete_search',
155
131
  params: {
156
- organization_id: orgId(),
132
+ organization_id: currentOrg,
157
133
  search,
158
134
  },
159
135
  });
@@ -187,8 +163,18 @@ class SubscriptionsPage extends Component {
187
163
  this.setState({ disableDeleteButton: !rowsSelected });
188
164
  };
189
165
 
166
+
190
167
  const csvParams = createSubscriptionParams({ search: this.state.searchQuery });
191
168
 
169
+ const emptyStateData = {
170
+ header: __('There are no Subscriptions to display'),
171
+ description: __('Import a Manifest to manage your Entitlements.'),
172
+ action: {
173
+ onClick: showManageManifestModal,
174
+ title: __('Import a Manifest'),
175
+ },
176
+ };
177
+
192
178
  return (
193
179
  <Grid bsClass="container-fluid">
194
180
  <Row>
@@ -208,7 +194,10 @@ class SubscriptionsPage extends Component {
208
194
 
209
195
  <div className="toolbar-pf-action-right">
210
196
  <FormGroup>
211
- <LinkContainer to="subscriptions/add" disabled={disableManifestActions}>
197
+ <LinkContainer
198
+ to="subscriptions/add"
199
+ disabled={disableManifestActions || !manifestExists(organization)}
200
+ >
212
201
  <TooltipButton
213
202
  tooltipId="add-subscriptions-button-tooltip"
214
203
  tooltipText={this.getDisabledReason()}
@@ -257,11 +246,14 @@ class SubscriptionsPage extends Component {
257
246
  <SubscriptionsTable
258
247
  loadSubscriptions={this.props.loadSubscriptions}
259
248
  updateQuantity={this.props.updateQuantity}
249
+ emptyState={emptyStateData}
260
250
  subscriptions={this.props.subscriptions}
261
251
  subscriptionDeleteModalOpen={this.state.subscriptionDeleteModalOpen}
262
252
  onSubscriptionDeleteModalClose={onSubscriptionDeleteModalClose}
263
253
  onDeleteSubscriptions={onDeleteSubscriptions}
264
254
  toggleDeleteButton={toggleDeleteButton}
255
+ task={task}
256
+ bulkSearch={this.props.bulkSearch}
265
257
  />
266
258
  <ModalProgressBar
267
259
  show={this.state.showTaskModal}
@@ -279,8 +271,10 @@ class SubscriptionsPage extends Component {
279
271
  SubscriptionsPage.propTypes = {
280
272
  loadSubscriptions: PropTypes.func.isRequired,
281
273
  updateQuantity: PropTypes.func.isRequired,
282
- subscriptions: PropTypes.shape().isRequired,
274
+ subscriptions: PropTypes.shape({}).isRequired,
275
+ organization: PropTypes.shape({}).isRequired,
283
276
  pollBulkSearch: PropTypes.func.isRequired,
277
+ bulkSearch: PropTypes.func,
284
278
  pollTaskUntilDone: PropTypes.func.isRequired,
285
279
  loadSetting: PropTypes.func.isRequired,
286
280
  tasks: PropTypes.arrayOf(PropTypes.shape({})),
@@ -289,6 +283,7 @@ SubscriptionsPage.propTypes = {
289
283
 
290
284
  SubscriptionsPage.defaultProps = {
291
285
  tasks: [],
286
+ bulkSearch: undefined,
292
287
  };
293
288
 
294
289
  export default SubscriptionsPage;
@@ -1,5 +1,6 @@
1
1
  import api, { orgId } from '../../../services/api';
2
2
  import { propsToSnakeCase } from '../../../services/index';
3
+ import { apiError } from '../../../move_to_foreman/common/helpers.js';
3
4
 
4
5
  import {
5
6
  UPSTREAM_SUBSCRIPTIONS_REQUEST,
@@ -27,12 +28,7 @@ export const loadUpstreamSubscriptions = (extendedParams = {}) => (dispatch) =>
27
28
  search: extendedParams.search,
28
29
  });
29
30
  })
30
- .catch((result) => {
31
- dispatch({
32
- type: UPSTREAM_SUBSCRIPTIONS_FAILURE,
33
- result,
34
- });
35
- });
31
+ .catch(result => dispatch(apiError(UPSTREAM_SUBSCRIPTIONS_FAILURE, result)));
36
32
  };
37
33
 
38
34
  export const saveUpstreamSubscriptions = upstreamSubscriptions => (dispatch) => {
@@ -50,12 +46,7 @@ export const saveUpstreamSubscriptions = upstreamSubscriptions => (dispatch) =>
50
46
  response: data,
51
47
  });
52
48
  })
53
- .catch((result) => {
54
- dispatch({
55
- type: SAVE_UPSTREAM_SUBSCRIPTIONS_FAILURE,
56
- result,
57
- });
58
- });
49
+ .catch(result => dispatch(apiError(SAVE_UPSTREAM_SUBSCRIPTIONS_FAILURE, result)));
59
50
  };
60
51
 
61
52
  export default loadUpstreamSubscriptions;
@@ -5,6 +5,7 @@ import PropTypes from 'prop-types';
5
5
  import { LinkContainer } from 'react-router-bootstrap';
6
6
  import { Grid, Row, Col } from 'react-bootstrap';
7
7
  import { bindMethods, Button } from 'patternfly-react';
8
+ import BreadcrumbsBar from 'foremanReact/components/BreadcrumbBar';
8
9
  import { LoadingState } from '../../../move_to_pf/LoadingState';
9
10
  import { notify } from '../../../move_to_foreman/foreman_toast_notifications';
10
11
  import helpers from '../../../move_to_foreman/common/helpers';
@@ -22,6 +23,7 @@ class UpstreamSubscriptionsPage extends Component {
22
23
  bindMethods(this, [
23
24
  'onChange',
24
25
  'saveUpstreamSubscriptions',
26
+ 'quantityValidationInput',
25
27
  ]);
26
28
  }
27
29
 
@@ -31,18 +33,17 @@ class UpstreamSubscriptionsPage extends Component {
31
33
 
32
34
  onChange(value, rowData) {
33
35
  const { selectedRows } = this.state;
34
- const newValue = parseInt(value, 10);
35
36
  const pool = {
36
37
  ...rowData,
37
38
  id: rowData.id,
38
- updatedQuantity: newValue,
39
+ updatedQuantity: value,
39
40
  selected: true,
40
41
  };
41
42
 
42
43
  const match = this.poolInSelectedRows(pool);
43
44
  const index = _.indexOf(selectedRows, match);
44
45
 
45
- if (newValue > 0) {
46
+ if (value) {
46
47
  if (match) {
47
48
  selectedRows.splice(index, 1, pool);
48
49
  } else {
@@ -55,6 +56,25 @@ class UpstreamSubscriptionsPage extends Component {
55
56
  this.setState({ selectedRows });
56
57
  }
57
58
 
59
+ // eslint-disable-next-line class-methods-use-this
60
+ quantityValidation(pool) {
61
+ const origQuantity = pool.updatedQuantity;
62
+ if (origQuantity && helpers.stringIsInteger(origQuantity)) {
63
+ const parsedQuantity = parseInt(origQuantity, 10);
64
+ const aboveZeroMsg = [false, __('Please enter a positive number above zero')];
65
+
66
+ if (parsedQuantity.toString().length > 10) return [false, __('Please limit number to 10 digits')];
67
+ if (!pool.available) return [false, __('No pools available')];
68
+ // handling unlimited subscriptions, they show as -1
69
+ if (pool.available === -1) return parsedQuantity ? [true, ''] : aboveZeroMsg;
70
+ if (parsedQuantity > pool.available) return [false, __(`Quantity must not be above ${pool.available}`)];
71
+ if (parsedQuantity <= 0) return aboveZeroMsg;
72
+ } else {
73
+ return [false, __('Please enter digits only')];
74
+ }
75
+ return [true, ''];
76
+ }
77
+
58
78
  poolInSelectedRows(pool) {
59
79
  const { selectedRows } = this.state;
60
80
 
@@ -64,16 +84,30 @@ class UpstreamSubscriptionsPage extends Component {
64
84
  );
65
85
  }
66
86
 
87
+ quantityValidationInput(pool) {
88
+ if (!pool || pool.updatedQuantity === undefined) return null;
89
+ if (this.quantityValidation(pool)[0]) {
90
+ return 'success';
91
+ }
92
+ return 'error';
93
+ }
94
+
95
+ validateSelectedRows() {
96
+ return Array.isArray(this.state.selectedRows) &&
97
+ this.state.selectedRows.length &&
98
+ this.state.selectedRows.every(pool => this.quantityValidation(pool)[0]);
99
+ }
100
+
67
101
  saveUpstreamSubscriptions() {
68
102
  const updatedPools = _.map(
69
103
  this.state.selectedRows,
70
- pool => ({ ...pool, quantity: pool.updatedQuantity }),
104
+ pool => ({ ...pool, quantity: parseInt(pool.updatedQuantity, 10) }),
71
105
  );
72
106
 
73
107
  const updatedSubscriptions = { pools: updatedPools };
74
108
 
75
109
  this.props.saveUpstreamSubscriptions(updatedSubscriptions).then(() => {
76
- const { task, error } = this.props.upstreamSubscriptions;
110
+ const { task } = this.props.upstreamSubscriptions;
77
111
 
78
112
  // TODO: could probably factor this out into a task response component
79
113
  if (task) {
@@ -88,18 +122,6 @@ class UpstreamSubscriptionsPage extends Component {
88
122
 
89
123
  notify({ message: ReactDOMServer.renderToStaticMarkup(message), type: 'success' });
90
124
  this.props.history.push('/subscriptions');
91
- } else {
92
- let errorMessages = [];
93
-
94
- if (error.errors) {
95
- errorMessages = error.errors;
96
- } else if (error.message) {
97
- errorMessages.push(error.message);
98
- }
99
-
100
- for (let i = 0; i < errorMessages.length; i += 1) {
101
- notify({ message: errorMessages[i], type: 'error' });
102
- }
103
125
  }
104
126
  });
105
127
  }
@@ -121,7 +143,8 @@ class UpstreamSubscriptionsPage extends Component {
121
143
  <Button
122
144
  bsStyle="primary"
123
145
  type="submit"
124
- disabled={upstreamSubscriptions.loading}
146
+ disabled={upstreamSubscriptions.loading ||
147
+ !this.validateSelectedRows()}
125
148
  onClick={this.saveUpstreamSubscriptions}
126
149
  >
127
150
  {__('Submit')}
@@ -170,13 +193,9 @@ class UpstreamSubscriptionsPage extends Component {
170
193
  description: __('Subscription Allocations allow you to export subscriptions from the Red Hat Customer Portal to ' +
171
194
  'an on-premise subscription management application such as Red Hat Satellite.'),
172
195
  docUrl: 'http://redhat.com',
173
- documentation: {
174
- title: __('Learn more about Subscription Allocations'),
175
- url: 'http://redhat.com',
176
- },
177
196
  action: {
178
- title: __('New Subscription Allocation'),
179
- url: 'http://redhat.com',
197
+ title: __('Import a Manifest to Begin'),
198
+ url: '/subscriptions',
180
199
  },
181
200
  });
182
201
 
@@ -212,7 +231,19 @@ class UpstreamSubscriptionsPage extends Component {
212
231
 
213
232
  return (
214
233
  <Grid bsClass="container-fluid">
215
- <h1>{__('Add Subscriptions')}</h1>
234
+ <BreadcrumbsBar data={{
235
+ isSwitchable: false,
236
+ breadcrumbItems: [
237
+ {
238
+ caption: __('Subscriptions'),
239
+ onClick: () => this.props.history.push('/subscriptions'),
240
+ },
241
+ {
242
+ caption: __('Add Subscriptions'),
243
+ },
244
+ ],
245
+ }}
246
+ />
216
247
 
217
248
  <LoadingState loading={upstreamSubscriptions.loading} loadingText={__('Loading')}>
218
249
  <Row>
@@ -235,13 +266,12 @@ class UpstreamSubscriptionsPage extends Component {
235
266
  }
236
267
 
237
268
  UpstreamSubscriptionsPage.propTypes = {
238
- history: PropTypes.shape({ push: PropTypes.func }).isRequired,
239
269
  loadUpstreamSubscriptions: PropTypes.func.isRequired,
240
270
  saveUpstreamSubscriptions: PropTypes.func.isRequired,
241
271
  upstreamSubscriptions: PropTypes.shape({
242
272
  task: PropTypes.shape({}),
243
- error: PropTypes.shape({}),
244
273
  }).isRequired,
274
+ history: PropTypes.shape({ push: PropTypes.func.isRequired }).isRequired,
245
275
  };
246
276
 
247
277
  export default UpstreamSubscriptionsPage;
@@ -38,7 +38,7 @@ export default (state = initialState, action) => {
38
38
 
39
39
  case UPSTREAM_SUBSCRIPTIONS_FAILURE:
40
40
  return state.merge({
41
- error: action.error,
41
+ error: action.payload.message,
42
42
  loading: false,
43
43
  });
44
44
 
@@ -49,8 +49,7 @@ export default (state = initialState, action) => {
49
49
  return state.set('task', action.response).set('loading', false);
50
50
 
51
51
  case SAVE_UPSTREAM_SUBSCRIPTIONS_FAILURE: {
52
- const error = action.result.response.data;
53
- return state.set('error', error).set('loading', false);
52
+ return state.set('error', action.payload.message).set('loading', false);
54
53
  }
55
54
 
56
55
  default:
@@ -1,5 +1,5 @@
1
1
  import React from 'react';
2
- import { FormGroup, FormControl, ControlLabel } from 'react-bootstrap';
2
+ import { FormGroup, FormControl, ControlLabel, HelpBlock } from 'react-bootstrap';
3
3
  import helpers from '../../../move_to_foreman/common/helpers';
4
4
  import {
5
5
  headerFormatter,
@@ -71,7 +71,7 @@ export const columns = (controller, selectionController) => [
71
71
  },
72
72
  },
73
73
  {
74
- property: 'quantity',
74
+ property: 'available',
75
75
  header: {
76
76
  label: __('Available Entitlements'),
77
77
  formatters: [headerFormatter],
@@ -96,22 +96,27 @@ export const columns = (controller, selectionController) => [
96
96
  formatters: [
97
97
  (value, { rowData }) => (
98
98
  <td>
99
- <FormGroup>
99
+ <FormGroup
100
+ validationState={controller.quantityValidationInput(rowData)}
101
+ >
100
102
  <ControlLabel srOnly>{__('Number to Allocate')}</ControlLabel>
101
103
  <FormControl
102
104
  type="text"
103
105
  onBlur={e => controller.onChange(e.target.value, rowData)}
104
106
  defaultValue={rowData.updatedQuantity}
107
+ onChange={(e) => {
108
+ controller.onChange(e.target.value, rowData);
109
+ }}
105
110
  onKeyDown={(e) => {
106
111
  const key = e.charCode ? e.charCode : e.keyCode;
107
112
  if (key === 13) {
108
- controller.onChange(e.target.value, rowData);
109
113
  controller.saveUpstreamSubscriptions();
110
114
  e.preventDefault();
111
115
  }
112
116
  }}
113
117
  />
114
- <div>{__('Max')} {rowData.quantity}</div>
118
+ {controller.quantityValidationInput(rowData) === 'error' &&
119
+ <HelpBlock>{controller.quantityValidation(rowData)[1]}</HelpBlock>}
115
120
  </FormGroup>
116
121
  </td>
117
122
  ),