katello 3.7.0 → 3.7.1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of katello might be problematic. Click here for more details.

Files changed (113) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/javascripts/katello/common/index.js +1 -0
  3. data/app/assets/javascripts/katello/sync_management/index.js +1 -0
  4. data/app/controllers/katello/api/v2/host_packages_controller.rb +1 -5
  5. data/app/controllers/katello/remote_execution_controller.rb +6 -6
  6. data/app/helpers/katello/hosts_and_hostgroups_helper.rb +37 -9
  7. data/app/lib/actions/katello/host/hypervisors_update.rb +82 -22
  8. data/app/lib/actions/pulp/consumer/abstract_content_action.rb +12 -0
  9. data/app/lib/actions/pulp/consumer/content_install.rb +1 -1
  10. data/app/lib/actions/pulp/consumer/content_uninstall.rb +1 -1
  11. data/app/lib/actions/pulp/consumer/content_update.rb +1 -1
  12. data/app/models/katello/concerns/subscription_facet_host_extensions.rb +1 -1
  13. data/app/models/katello/content_view.rb +12 -4
  14. data/app/models/katello/glue/candlepin/pool.rb +11 -11
  15. data/app/models/katello/host/content_facet.rb +2 -1
  16. data/app/models/katello/rpm.rb +14 -6
  17. data/app/models/katello/subscription_status.rb +1 -1
  18. data/app/services/katello/candlepin/consumer.rb +8 -0
  19. data/app/views/overrides/activation_keys/_host_environment_select.html.erb +2 -3
  20. data/config/routes.rb +1 -0
  21. data/db/migrate/20180612163403_add_foreign_key_to_hypervisor_id.rb +3 -0
  22. data/db/seeds.d/75-job_templates.rb +5 -2
  23. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/content-hosts/bulk/content-hosts-bulk-repository-sets-modal.controller.js +4 -3
  24. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/content-hosts/bulk/content-hosts-bulk-subscriptions-modal.controller.js +4 -1
  25. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/content-hosts/content/content-host-packages-installed.controller.js +1 -1
  26. data/engines/bastion_katello/app/assets/javascripts/bastion_katello/products/discovery/views/discovery-create.html +1 -1
  27. data/engines/bastion_katello/app/assets/stylesheets/bastion_katello/bastion_katello.scss +5 -0
  28. data/lib/katello/tasks/clean_backend_objects.rake +12 -3
  29. data/lib/katello/version.rb +1 -1
  30. data/package.json +10 -7
  31. data/webpack/__mocks__/foremanReact/redux.js +3 -0
  32. data/webpack/__mocks__/foremanReact/redux/actions/toasts.js +2 -0
  33. data/webpack/components/Search/Search.test.js +3 -1
  34. data/webpack/components/SelectOrg/SelectOrg.scss +3 -0
  35. data/webpack/components/SelectOrg/SelectOrgAction.js +41 -0
  36. data/webpack/components/SelectOrg/SelectOrgReducer.js +33 -0
  37. data/webpack/components/SelectOrg/SetOrganization.js +116 -0
  38. data/webpack/components/WithOrganization/withOrganization.js +28 -0
  39. data/webpack/containers/Application/config.js +9 -2
  40. data/webpack/containers/Application/index.js +4 -2
  41. data/webpack/global_test_setup.js +6 -0
  42. data/webpack/helpers/caret.js +6 -0
  43. data/webpack/move_to_foreman/components/common/{emptyState → EmptyState}/index.js +16 -3
  44. data/webpack/move_to_foreman/components/common/table/components/Table.js +1 -1
  45. data/webpack/move_to_foreman/components/common/table/components/__snapshots__/CollapseSubscriptionGroupButton.test.js.snap +2 -2
  46. data/webpack/move_to_foreman/components/common/table/components/__snapshots__/TableSelectionCell.test.js.snap +1 -1
  47. data/webpack/move_to_foreman/components/common/table/components/__snapshots__/TableSelectionHeaderCell.test.js.snap +1 -1
  48. data/webpack/move_to_pf/LoadingState/LoadingState.js +27 -14
  49. data/webpack/move_to_pf/LoadingState/LoadingState.test.js +8 -4
  50. data/webpack/move_to_pf/Select/Select.js +40 -0
  51. data/webpack/move_to_pf/react-bootstrap-select/index.js +12 -1
  52. data/webpack/redux/actions/RedHatRepositories/enabled.js +0 -1
  53. data/webpack/redux/actions/RedHatRepositories/helpers.js +5 -5
  54. data/webpack/redux/consts.js +6 -0
  55. data/webpack/redux/reducers/index.js +2 -0
  56. data/webpack/scenes/Products/ProductActions.js +24 -0
  57. data/webpack/scenes/Products/ProductConstants.js +3 -0
  58. data/webpack/scenes/Products/__tests__/ProductActions.test.js +40 -0
  59. data/webpack/scenes/Products/__tests__/products.fixtures.js +90 -0
  60. data/webpack/scenes/RedHatRepositories/components/EnabledRepository.js +14 -23
  61. data/webpack/scenes/RedHatRepositories/components/EnabledRepositoryContent.js +34 -0
  62. data/webpack/scenes/RedHatRepositories/components/RepositorySetRepository.js +1 -1
  63. data/webpack/scenes/RedHatRepositories/components/SearchBar.js +1 -0
  64. data/webpack/scenes/RedHatRepositories/components/__tests__/EnabledRepository.test.js +36 -0
  65. data/webpack/scenes/RedHatRepositories/components/__tests__/EnabledRepositoryContent.test.js +27 -0
  66. data/webpack/scenes/RedHatRepositories/components/__tests__/__snapshots__/EnabledRepository.test.js.snap +25 -0
  67. data/webpack/scenes/RedHatRepositories/components/__tests__/__snapshots__/EnabledRepositoryContent.test.js.snap +47 -0
  68. data/webpack/scenes/RedHatRepositories/components/__tests__/__snapshots__/RecommendedRepositorySetsToggler.test.js.snap +3 -1
  69. data/webpack/scenes/RedHatRepositories/index.js +7 -3
  70. data/webpack/scenes/RedHatRepositories/index.scss +1 -0
  71. data/webpack/scenes/Subscriptions/Details/SubscriptionDetailActions.js +1 -1
  72. data/webpack/scenes/Subscriptions/Details/SubscriptionDetailEnabledProducts.js +54 -0
  73. data/webpack/scenes/Subscriptions/Details/SubscriptionDetailProduct.js +29 -0
  74. data/webpack/scenes/Subscriptions/Details/SubscriptionDetailReducer.js +29 -0
  75. data/webpack/scenes/Subscriptions/Details/SubscriptionDetails.js +67 -22
  76. data/webpack/scenes/Subscriptions/Details/SubscriptionDetails.scss +9 -0
  77. data/webpack/scenes/Subscriptions/Details/__tests__/SubscriptionDetailEnabledProducts.test.js +18 -0
  78. data/webpack/scenes/Subscriptions/Details/__tests__/SubscriptionDetailProduct.test.js +13 -0
  79. data/webpack/scenes/Subscriptions/Details/__tests__/SubscriptionDetails.test.js +6 -0
  80. data/webpack/scenes/Subscriptions/Details/__tests__/__snapshots__/SubscriptionDetailEnabledProducts.test.js.snap +45 -0
  81. data/webpack/scenes/Subscriptions/Details/__tests__/__snapshots__/SubscriptionDetailProduct.test.js.snap +67 -0
  82. data/webpack/scenes/Subscriptions/Details/__tests__/__snapshots__/SubscriptionDetails.test.js.snap +497 -410
  83. data/webpack/scenes/Subscriptions/Details/__tests__/subscriptionDetails.fixtures.js +4 -0
  84. data/webpack/scenes/Subscriptions/Details/index.js +3 -1
  85. data/webpack/scenes/Subscriptions/Manifest/ManageManifestModal.js +78 -34
  86. data/webpack/scenes/Subscriptions/Manifest/ManifestHistoryReducer.js +8 -0
  87. data/webpack/scenes/Subscriptions/Manifest/__tests__/ManageManifestModal.test.js +3 -0
  88. data/webpack/scenes/Subscriptions/Manifest/__tests__/__snapshots__/ManageManifestModal.test.js.snap +34 -7
  89. data/webpack/scenes/Subscriptions/Manifest/index.js +1 -0
  90. data/webpack/scenes/Subscriptions/SubscriptionConstants.js +1 -0
  91. data/webpack/scenes/Subscriptions/SubscriptionHelpers.js +3 -0
  92. data/webpack/scenes/Subscriptions/SubscriptionReducer.js +6 -2
  93. data/webpack/scenes/Subscriptions/SubscriptionsPage.js +31 -36
  94. data/webpack/scenes/Subscriptions/UpstreamSubscriptions/UpstreamSubscriptionsPage.js +2 -7
  95. data/webpack/scenes/Subscriptions/UpstreamSubscriptions/__tests__/UpstreamSubscriptionsPage.test.js +1 -1
  96. data/webpack/scenes/Subscriptions/UpstreamSubscriptions/__tests__/__snapshots__/UpstreamSubscriptionsPage.test.js.snap +3 -6
  97. data/webpack/scenes/Subscriptions/__tests__/SubscriptionsPage.test.js +2 -0
  98. data/webpack/scenes/Subscriptions/__tests__/__snapshots__/SubscriptionsPage.test.js.snap +14 -2
  99. data/webpack/scenes/Subscriptions/__tests__/subscriptions.fixtures.js +4 -3
  100. data/webpack/scenes/Subscriptions/components/SubscriptionsTable/EntitlementsInlineEditFormatter.js +8 -5
  101. data/webpack/scenes/Subscriptions/components/SubscriptionsTable/SubscriptionsTable.js +29 -19
  102. data/webpack/scenes/Subscriptions/components/SubscriptionsTable/SubscriptionsTableHelpers.js +9 -2
  103. data/webpack/scenes/Subscriptions/components/SubscriptionsTable/SubscriptionsTableSchema.js +2 -2
  104. data/webpack/scenes/Subscriptions/components/SubscriptionsTable/__tests__/EntitlementsInlineEditFormatter.test.js +110 -0
  105. data/webpack/scenes/Subscriptions/components/SubscriptionsTable/__tests__/SubscriptionsTable.test.js +15 -3
  106. data/webpack/scenes/Subscriptions/components/SubscriptionsTable/__tests__/__snapshots__/EntitlementsInlineEditFormatter.test.js.snap +228 -0
  107. data/webpack/scenes/Subscriptions/components/SubscriptionsTable/__tests__/__snapshots__/SubscriptionsTable.test.js.snap +54 -21
  108. data/webpack/scenes/Subscriptions/index.js +1 -0
  109. data/webpack/scenes/Tasks/helpers.js +52 -0
  110. data/webpack/services/api/index.js +17 -1
  111. data/webpack/test_setup.js +2 -0
  112. metadata +31 -4
  113. data/config/katello.yaml +0 -89
@@ -3,6 +3,10 @@ import { toastErrorAction, failureAction } from '../../../../services/api/testHe
3
3
 
4
4
  export const initialState = Immutable({
5
5
  loading: false,
6
+ enabledProducts: {
7
+ results: [],
8
+ total: 0,
9
+ },
6
10
  });
7
11
 
8
12
 
@@ -2,6 +2,7 @@ import { bindActionCreators } from 'redux';
2
2
  import { connect } from 'react-redux';
3
3
  import { withRouter } from 'react-router';
4
4
  import reducer from './SubscriptionDetailReducer';
5
+ import { loadProducts } from '../../Products/ProductActions';
5
6
  import * as subscriptionDetailActions from './SubscriptionDetailActions';
6
7
  import SubscriptionDetails from './SubscriptionDetails';
7
8
 
@@ -11,7 +12,8 @@ const mapStateToProps = state => ({
11
12
  });
12
13
 
13
14
  // map action dispatchers to props
14
- const mapDispatchToProps = dispatch => bindActionCreators(subscriptionDetailActions, dispatch);
15
+ const actions = { ...subscriptionDetailActions, loadProducts };
16
+ const mapDispatchToProps = dispatch => bindActionCreators(actions, dispatch);
15
17
 
16
18
  export const subscriptionDetails = reducer;
17
19
 
@@ -1,13 +1,15 @@
1
1
  import React, { Component } from 'react';
2
2
  import PropTypes from 'prop-types';
3
- import { Col, Tabs, Tab, Form, FormGroup, FormControl, ControlLabel } from 'react-bootstrap';
3
+ import { Col, Tabs, Tab, Form, FormGroup, FormControl, ControlLabel, Row } from 'react-bootstrap';
4
4
  import { bindMethods, Button, Icon, Modal, Spinner, OverlayTrigger, Tooltip } from 'patternfly-react';
5
5
  import { isEqual } from 'lodash';
6
6
  import TooltipButton from 'react-bootstrap-tooltip-button';
7
7
  import { LoadingState } from '../../../move_to_pf/LoadingState';
8
8
  import { Table } from '../../../move_to_foreman/components/common/table';
9
- import { columns } from './ManifestHistoryTableSchema';
10
9
  import ConfirmDialog from '../../../move_to_foreman/components/common/ConfirmDialog';
10
+ import { manifestExists } from '../SubscriptionHelpers';
11
+ import { columns } from './ManifestHistoryTableSchema';
12
+ import { renderTaskStartedToast } from '../../Tasks/helpers';
11
13
  import DeleteManifestModalText from './DeleteManifestModalText';
12
14
  import {
13
15
  BLOCKING_FOREMAN_TASK_TYPES,
@@ -30,15 +32,11 @@ class ManageManifestModal extends Component {
30
32
  'uploadManifest',
31
33
  'refreshManifest',
32
34
  'deleteManifest',
33
- 'manifestExists',
34
35
  'disabledTooltipText',
36
+ 'updateRepositoryUrl',
35
37
  ]);
36
38
  }
37
39
 
38
- componentDidMount() {
39
- this.loadData();
40
- }
41
-
42
40
  static getDerivedStateFromProps(newProps, prevState) {
43
41
  if (
44
42
  !isEqual(newProps.showModal, prevState.showModal) ||
@@ -52,6 +50,10 @@ class ManageManifestModal extends Component {
52
50
  return null;
53
51
  }
54
52
 
53
+ componentDidMount() {
54
+ this.loadData();
55
+ }
56
+
55
57
  componentDidUpdate(prevProp, prevState) {
56
58
  const { actionInProgress } = this.state;
57
59
 
@@ -69,14 +71,27 @@ class ManageManifestModal extends Component {
69
71
  this.props.onClose();
70
72
  }
71
73
 
72
- saveOrganization(event) {
73
- this.props.saveOrganization({ redhat_repository_url: event.target.value });
74
+ updateRepositoryUrl(event) {
75
+ this.setState({ redhat_repository_url: event.target.value });
76
+ }
77
+
78
+ saveOrganization() {
79
+ this.props.saveOrganization({ redhat_repository_url: this.state.redhat_repository_url });
74
80
  }
75
81
 
76
82
  uploadManifest(fileList) {
77
83
  this.setState({ actionInProgress: true });
78
84
  if (fileList.length > 0) {
79
- this.props.uploadManifest(fileList[0]);
85
+ this.props
86
+ .uploadManifest(fileList[0])
87
+ .then(() =>
88
+ this.props.bulkSearch({
89
+ search_id: MANIFEST_TASKS_BULK_SEARCH_ID,
90
+ type: 'all',
91
+ active_only: true,
92
+ action_types: BLOCKING_FOREMAN_TASK_TYPES,
93
+ }))
94
+ .then(() => renderTaskStartedToast(this.props.taskDetails));
80
95
  }
81
96
  }
82
97
 
@@ -87,14 +102,16 @@ class ManageManifestModal extends Component {
87
102
 
88
103
  deleteManifest() {
89
104
  this.setState({ actionInProgress: true });
90
- this.props.deleteManifest()
105
+ this.props
106
+ .deleteManifest()
91
107
  .then(() =>
92
108
  this.props.bulkSearch({
93
109
  search_id: MANIFEST_TASKS_BULK_SEARCH_ID,
94
110
  type: 'all',
95
111
  active_only: true,
96
112
  action_types: BLOCKING_FOREMAN_TASK_TYPES,
97
- }));
113
+ }))
114
+ .then(() => renderTaskStartedToast(this.props.taskDetails));
98
115
  this.showDeleteManifestModal(false);
99
116
  }
100
117
 
@@ -111,15 +128,12 @@ class ManageManifestModal extends Component {
111
128
  return __('This is disabled because no manifest exists');
112
129
  }
113
130
 
114
- manifestExists() {
115
- const { organization } = this.props;
116
-
117
- return organization.owner_details && organization.owner_details.upstreamConsumer;
118
- }
119
-
120
131
  render() {
121
132
  const {
122
- manifestHistory, organization, disableManifestActions, disabledReason,
133
+ manifestHistory,
134
+ organization,
135
+ disableManifestActions,
136
+ disabledReason,
123
137
  } = this.props;
124
138
 
125
139
  const { actionInProgress } = this.state;
@@ -132,13 +146,23 @@ class ManageManifestModal extends Component {
132
146
  url: 'http://redhat.com',
133
147
  },
134
148
  });
135
-
149
+ const buttonLoading = (
150
+ <span>
151
+ {__('Updating...')}
152
+ <span className="fa fa-spinner fa-spin" />
153
+ </span>);
136
154
  const getManifestName = () => {
137
155
  let name = __('No Manifest Uploaded');
138
156
 
139
- if (organization.owner_details && organization.owner_details.upstreamConsumer) {
140
- const link = ['https://', organization.owner_details.upstreamConsumer.webUrl,
141
- organization.owner_details.upstreamConsumer.uuid].join('/');
157
+ if (
158
+ organization.owner_details &&
159
+ organization.owner_details.upstreamConsumer
160
+ ) {
161
+ const link = [
162
+ 'https://',
163
+ organization.owner_details.upstreamConsumer.webUrl,
164
+ organization.owner_details.upstreamConsumer.uuid,
165
+ ].join('/');
142
166
 
143
167
  name = (
144
168
  <a href={link}>{organization.owner_details.upstreamConsumer.name}</a>
@@ -151,7 +175,11 @@ class ManageManifestModal extends Component {
151
175
  return (
152
176
  <Modal show={this.state.showModal} onHide={this.hideModal}>
153
177
  <Modal.Header>
154
- <button className="close" onClick={this.hideModal} aria-label={__('Close')}>
178
+ <button
179
+ className="close"
180
+ onClick={this.hideModal}
181
+ aria-label={__('Close')}
182
+ >
155
183
  <Icon type="pf" name="close" />
156
184
  </button>
157
185
  <Modal.Title>{__('Manage Manifest')}</Modal.Title>
@@ -163,29 +191,43 @@ class ManageManifestModal extends Component {
163
191
  <h5>{__('Red Hat Provider Details')}</h5>
164
192
  <hr />
165
193
  <FormGroup>
166
- <ControlLabel className="col-sm-3" htmlFor="cdnUrl">
167
- {__('Red Hat CDN URL')}
168
- </ControlLabel>
194
+ <Col sm={3}>
195
+ <ControlLabel htmlFor="cdnUrl">
196
+ {__('Red Hat CDN URL')}
197
+ </ControlLabel>
198
+ </Col>
169
199
  <Col sm={9}>
170
200
  <FormControl
171
201
  id="cdnUrl"
172
202
  type="text"
173
- value={organization.redhat_repository_url || ''}
174
- onChange={this.saveOrganization}
203
+ value={this.state.redhat_repository_url || organization.redhat_repository_url || ''}
204
+ onChange={this.updateRepositoryUrl}
175
205
  />
176
206
  </Col>
177
207
  </FormGroup>
208
+ <FormGroup>
209
+ <Col smOffset={3} sm={3}>
210
+ <Button onClick={this.saveOrganization} disabled={organization.loading}>
211
+ {organization.loading ? buttonLoading : __('Update')}
212
+ </Button>
213
+ </Col>
214
+ </FormGroup>
178
215
  <br />
179
216
 
180
217
  <h5>{__('Subscription Manifest')}</h5>
181
218
  <hr />
182
219
 
183
220
  <FormGroup>
184
- <ControlLabel className="col-sm-3 control-label" htmlFor="usmaFile">
221
+ <ControlLabel
222
+ className="col-sm-3 control-label"
223
+ htmlFor="usmaFile"
224
+ >
185
225
  <OverlayTrigger
186
226
  overlay={
187
- <Tooltip id="usma-tooltip">{__('Upstream Subscription Management Application')}</Tooltip>
188
- }
227
+ <Tooltip id="usma-tooltip">
228
+ {__('Upstream Subscription Management Application')}
229
+ </Tooltip>
230
+ }
189
231
  placement="bottom"
190
232
  trigger={['hover', 'focus']}
191
233
  rootClose={false}
@@ -213,7 +255,7 @@ class ManageManifestModal extends Component {
213
255
  tooltipText={disabledReason}
214
256
  tooltipPlacement="top"
215
257
  title={__('Refresh')}
216
- disabled={!this.manifestExists() ||
258
+ disabled={!manifestExists(organization) ||
217
259
  actionInProgress || disableManifestActions}
218
260
  />
219
261
 
@@ -223,7 +265,7 @@ class ManageManifestModal extends Component {
223
265
  tooltipText={this.disabledTooltipText()}
224
266
  tooltipPlacement="top"
225
267
  title={__('Delete')}
226
- disabled={!this.manifestExists() || actionInProgress}
268
+ disabled={!manifestExists(organization) || actionInProgress}
227
269
  />
228
270
 
229
271
  <ConfirmDialog
@@ -278,9 +320,11 @@ ManageManifestModal.propTypes = {
278
320
  showModal: PropTypes.bool.isRequired,
279
321
  onClose: PropTypes.func,
280
322
  bulkSearch: PropTypes.func.isRequired,
323
+ taskDetails: PropTypes.shape({}),
281
324
  };
282
325
 
283
326
  ManageManifestModal.defaultProps = {
327
+ taskDetails: undefined,
284
328
  disableManifestActions: false,
285
329
  disabledReason: '',
286
330
  onClose() {},
@@ -4,6 +4,8 @@ import {
4
4
  MANIFEST_HISTORY_REQUEST,
5
5
  MANIFEST_HISTORY_SUCCESS,
6
6
  MANIFEST_HISTORY_FAILURE,
7
+ UPLOAD_MANIFEST_SUCCESS,
8
+ DELETE_MANIFEST_SUCCESS,
7
9
  } from './ManifestConstants';
8
10
 
9
11
  const initialState = Immutable({ loading: true, results: [] });
@@ -28,6 +30,12 @@ export default (state = initialState, action) => {
28
30
  loading: false,
29
31
  });
30
32
 
33
+ case UPLOAD_MANIFEST_SUCCESS:
34
+ return state.set('taskDetails', action.response);
35
+
36
+ case DELETE_MANIFEST_SUCCESS:
37
+ return state.set('taskDetails', action.response);
38
+
31
39
  default:
32
40
  return state;
33
41
  }
@@ -4,6 +4,8 @@ import toJson from 'enzyme-to-json';
4
4
  import ManageManifestModal from '../ManageManifestModal';
5
5
  import { manifestHistorySuccessState } from './manifest.fixtures';
6
6
 
7
+ jest.mock('../../../../move_to_foreman/foreman_toast_notifications');
8
+
7
9
  describe('manage manifest modal', () => {
8
10
  const noop = () => {};
9
11
  const organization = { id: 1, redhat_repository_url: 'https://redhat.com' };
@@ -17,6 +19,7 @@ describe('manage manifest modal', () => {
17
19
  organization={organization}
18
20
  loadOrganization={noop}
19
21
  saveOrganization={noop}
22
+ bulkSearch={noop}
20
23
  manifestHistory={manifestHistorySuccessState}
21
24
  taskInProgress={false}
22
25
  showModal
@@ -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={
@@ -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
@@ -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
 
@@ -85,7 +85,10 @@ export default (state = initialState, action) => {
85
85
  });
86
86
 
87
87
  case SUBSCRIPTIONS_QUANTITIES_REQUEST:
88
- return state.set('quantitiesLoading', true);
88
+ return state.merge({
89
+ quantitiesLoading: true,
90
+ availableQuantities: null,
91
+ });
89
92
 
90
93
  case SUBSCRIPTIONS_QUANTITIES_SUCCESS: {
91
94
  return state.merge({
@@ -97,6 +100,7 @@ export default (state = initialState, action) => {
97
100
  case SUBSCRIPTIONS_QUANTITIES_FAILURE: {
98
101
  return state.merge({
99
102
  quantitiesLoading: false,
103
+ availableQuantities: {},
100
104
  });
101
105
  }
102
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;