katello 3.7.0.rc2 → 3.7.0

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 (49) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/katello/api/v2/repository_sets_controller.rb +10 -1
  3. data/app/models/katello/glue/candlepin/owner.rb +0 -8
  4. data/app/models/katello/product_content.rb +4 -1
  5. data/app/services/katello/ui_notifications/pulp/proxy_disk_space.rb +3 -1
  6. data/app/views/overrides/activation_keys/_host_environment_select.html.erb +1 -1
  7. data/lib/katello/version.rb +1 -1
  8. data/package.json +2 -1
  9. data/webpack/__mocks__/foremanReact/components/BreadcrumbBar.js +3 -0
  10. data/webpack/__mocks__/foremanReact/redux/actions/toasts.js +6 -0
  11. data/webpack/mockRequest.js +3 -3
  12. data/webpack/move_to_foreman/common/helpers.js +45 -8
  13. data/webpack/redux/actions/RedHatRepositories/sets.js +1 -1
  14. data/webpack/scenes/Subscriptions/Details/SubscriptionDetailActions.js +2 -7
  15. data/webpack/scenes/Subscriptions/Details/SubscriptionDetailReducer.js +1 -1
  16. data/webpack/scenes/Subscriptions/Details/SubscriptionDetails.js +44 -6
  17. data/webpack/scenes/Subscriptions/Details/__tests__/SubscriptionDetailReducer.test.js +3 -1
  18. data/webpack/scenes/Subscriptions/Details/__tests__/SubscriptionDetails.test.js +0 -1
  19. data/webpack/scenes/Subscriptions/Details/__tests__/__snapshots__/SubscriptionDetails.test.js.snap +22 -14
  20. data/webpack/scenes/Subscriptions/Details/__tests__/subscriptionDetails.fixtures.js +3 -4
  21. data/webpack/scenes/Subscriptions/Details/index.js +2 -2
  22. data/webpack/scenes/Subscriptions/Manifest/ManifestActions.js +5 -24
  23. data/webpack/scenes/Subscriptions/Manifest/ManifestHistoryReducer.js +1 -1
  24. data/webpack/scenes/Subscriptions/Manifest/__tests__/ManifestActions.test.js +20 -8
  25. data/webpack/scenes/Subscriptions/Manifest/__tests__/ManifestHistoryReducer.test.js +3 -1
  26. data/webpack/scenes/Subscriptions/Manifest/__tests__/manifest.fixtures.js +9 -16
  27. data/webpack/scenes/Subscriptions/SubscriptionActions.js +5 -26
  28. data/webpack/scenes/Subscriptions/SubscriptionReducer.js +6 -2
  29. data/webpack/scenes/Subscriptions/SubscriptionsPage.js +13 -10
  30. data/webpack/scenes/Subscriptions/UpstreamSubscriptions/UpstreamSubscriptionsActions.js +3 -12
  31. data/webpack/scenes/Subscriptions/UpstreamSubscriptions/UpstreamSubscriptionsPage.js +55 -20
  32. data/webpack/scenes/Subscriptions/UpstreamSubscriptions/UpstreamSubscriptionsReducer.js +2 -3
  33. data/webpack/scenes/Subscriptions/UpstreamSubscriptions/UpstreamSubscriptionsTableSchema.js +10 -5
  34. data/webpack/scenes/Subscriptions/UpstreamSubscriptions/__tests__/UpstreamSubscriptionsActions.test.js +10 -5
  35. data/webpack/scenes/Subscriptions/UpstreamSubscriptions/__tests__/UpstreamSubscriptionsPage.test.js +50 -5
  36. data/webpack/scenes/Subscriptions/UpstreamSubscriptions/__tests__/UpstreamSubscriptionsReducer.test.js +8 -3
  37. data/webpack/scenes/Subscriptions/UpstreamSubscriptions/__tests__/__snapshots__/UpstreamSubscriptionsPage.test.js.snap +18 -5
  38. data/webpack/scenes/Subscriptions/UpstreamSubscriptions/__tests__/upstreamSubscriptions.fixtures.js +5 -8
  39. data/webpack/scenes/Subscriptions/__tests__/SubscriptionsReducer.test.js +9 -3
  40. data/webpack/scenes/Subscriptions/__tests__/__snapshots__/SubscriptionsPage.test.js.snap +1 -0
  41. data/webpack/scenes/Subscriptions/__tests__/subscriptions.fixtures.js +10 -14
  42. data/webpack/scenes/Subscriptions/components/SubscriptionsTable/SubscriptionsTable.js +16 -39
  43. data/webpack/scenes/Subscriptions/components/SubscriptionsTable/SubscriptionsTableHelpers.js +2 -2
  44. data/webpack/scenes/Subscriptions/components/SubscriptionsTable/__tests__/SubscriptionsTable.test.js +1 -0
  45. data/webpack/scenes/Subscriptions/components/SubscriptionsTable/__tests__/__snapshots__/SubscriptionsTable.test.js.snap +349 -355
  46. data/webpack/scenes/Subscriptions/index.js +1 -2
  47. data/webpack/services/api/testHelpers.js +28 -0
  48. metadata +7 -5
  49. data/webpack/services/api/fixtures.js +0 -353
@@ -24,7 +24,7 @@ export default (state = initialState, action) => {
24
24
 
25
25
  case MANIFEST_HISTORY_FAILURE:
26
26
  return state.merge({
27
- error: action.error,
27
+ error: action.payload.message,
28
28
  loading: false,
29
29
  });
30
30
 
@@ -1,5 +1,3 @@
1
- import axios from 'axios';
2
- import MockAdapter from 'axios-mock-adapter';
3
1
  import thunk from 'redux-thunk';
4
2
  import Immutable from 'seamless-immutable';
5
3
  import configureMockStore from 'redux-mock-store';
@@ -15,12 +13,11 @@ import {
15
13
  deleteManifestSuccessActions,
16
14
  deleteManifestFailureActions,
17
15
  } from './manifest.fixtures';
18
-
19
16
  import { loadManifestHistory, uploadManifest, refreshManifest, deleteManifest } from '../ManifestActions';
17
+ import { mock as mockApi, mockErrorRequest } from '../../../../mockRequest';
20
18
 
21
19
  const mockStore = configureMockStore([thunk]);
22
20
  const store = mockStore({ manifest: Immutable({}) });
23
- const mockApi = new MockAdapter(axios);
24
21
 
25
22
  beforeEach(() => {
26
23
  store.clearActions();
@@ -43,7 +40,10 @@ describe('manifest actions', () => {
43
40
  const url = '/katello/api/v2/organizations/1/subscriptions/manifest_history';
44
41
 
45
42
  it('and then fails with 422', () => {
46
- mockApi.onGet(url).reply(422);
43
+ mockErrorRequest({
44
+ url,
45
+ status: 422,
46
+ });
47
47
 
48
48
  return store.dispatch(loadManifestHistory())
49
49
  .then(() => expect(store.getActions()).toEqual(manifestHistoryFailureActions));
@@ -61,7 +61,11 @@ describe('manifest actions', () => {
61
61
  const url = '/katello/api/v2/organizations/1/subscriptions/upload';
62
62
 
63
63
  it('and then fails with 422', () => {
64
- mockApi.onPost(url).reply(422);
64
+ mockErrorRequest({
65
+ url,
66
+ status: 422,
67
+ method: 'POST',
68
+ });
65
69
 
66
70
  return store.dispatch(uploadManifest())
67
71
  .then(() => expect(store.getActions()).toEqual(uploadManifestFailureActions));
@@ -79,7 +83,11 @@ describe('manifest actions', () => {
79
83
  const url = '/katello/api/v2/organizations/1/subscriptions/refresh_manifest';
80
84
 
81
85
  it('and then fails with 422', () => {
82
- mockApi.onPut(url).reply(422);
86
+ mockErrorRequest({
87
+ url,
88
+ status: 422,
89
+ method: 'PUT',
90
+ });
83
91
 
84
92
  return store.dispatch(refreshManifest())
85
93
  .then(() => expect(store.getActions()).toEqual(refreshManifestFailureActions));
@@ -97,7 +105,11 @@ describe('manifest actions', () => {
97
105
  const url = '/katello/api/v2/organizations/1/subscriptions/delete_manifest';
98
106
 
99
107
  it('and then fails with 422', () => {
100
- mockApi.onPost(url).reply(422);
108
+ mockErrorRequest({
109
+ url,
110
+ status: 422,
111
+ method: 'POST',
112
+ });
101
113
 
102
114
  return store.dispatch(deleteManifest())
103
115
  .then(() => expect(store.getActions()).toEqual(deleteManifestFailureActions));
@@ -30,7 +30,9 @@ describe('manifest history reducer', () => {
30
30
  it('should have error on MANIFEST_HISTORY_FAILURE', () => {
31
31
  expect(reducer(manifestHistoryInitialState, {
32
32
  type: types.MANIFEST_HISTORY_FAILURE,
33
- error: 'Unable to process request.',
33
+ payload: {
34
+ message: 'Unable to process request.',
35
+ },
34
36
  })).toEqual(manifestHistoryErrorState);
35
37
  });
36
38
  });
@@ -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
  ];
@@ -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;
@@ -24,6 +24,7 @@ const initialState = Immutable({
24
24
  ...initialApiState,
25
25
  quantitiesLoading: false,
26
26
  availableQuantities: {},
27
+ tasks: [],
27
28
  });
28
29
 
29
30
  const mapQuantities = (pools) => {
@@ -72,10 +73,14 @@ export default (state = initialState, action) => {
72
73
  return state.set('loading', false);
73
74
 
74
75
  case SUBSCRIPTIONS_FAILURE:
76
+ return state
77
+ .set('loading', false)
78
+ .set('results', [])
79
+ .set('itemCount', 0);
80
+
75
81
  case UPDATE_QUANTITY_FAILURE:
76
82
  case DELETE_SUBSCRIPTIONS_FAILURE:
77
83
  return state.merge({
78
- error: action.error,
79
84
  loading: false,
80
85
  });
81
86
 
@@ -92,7 +97,6 @@ export default (state = initialState, action) => {
92
97
  case SUBSCRIPTIONS_QUANTITIES_FAILURE: {
93
98
  return state.merge({
94
99
  quantitiesLoading: false,
95
- quantitiesError: action.error,
96
100
  });
97
101
  }
98
102
 
@@ -38,36 +38,39 @@ class SubscriptionsPage extends Component {
38
38
  }
39
39
 
40
40
  static getDerivedStateFromProps(nextProps, prevState) {
41
- const nextTaskId = nextProps.tasks[0] && nextProps.tasks[0].id;
41
+ const { tasks = [] } = nextProps;
42
+ const nextTaskId = tasks[0] && tasks[0].id;
42
43
 
43
- if (nextProps.tasks.length === 0 && prevState.polledTask != null) {
44
+ if (tasks.length === 0 && prevState.polledTask != null) {
44
45
  return { showTaskModal: false, polledTask: undefined };
45
- } else if (nextProps.tasks.length > 0 && nextTaskId !== prevState.polledTask) {
46
+ } else if (tasks.length > 0 && nextTaskId !== prevState.polledTask) {
46
47
  return {
47
48
  showTaskModal: true,
48
49
  manifestModalOpen: false,
49
- polledTask: nextProps.tasks[0].id,
50
+ polledTask: nextTaskId,
50
51
  };
51
52
  }
52
53
  return null;
53
54
  }
54
55
 
55
56
  componentDidUpdate(prevProps) {
56
- const { tasks } = this.props;
57
+ const { tasks = [] } = this.props;
58
+ const { tasks: prevTasks = [] } = prevProps;
59
+
57
60
  const numberOfTasks = tasks.length;
58
- const numberOfPrevTasks = prevProps.tasks.length;
61
+ const numberOfPrevTasks = prevTasks.length;
59
62
  let task;
60
63
 
61
64
  if (numberOfTasks > 0) {
62
- if (numberOfPrevTasks === 0 || prevProps.tasks[0].id !== tasks[0].id) {
63
- [task] = this.props.tasks;
65
+ if (numberOfPrevTasks === 0 || prevTasks[0].id !== tasks[0].id) {
66
+ [task] = tasks;
64
67
  this.handleDoneTask(task);
65
68
  }
66
69
  }
67
70
  }
68
71
 
69
72
  getDisabledReason(deleteButton) {
70
- const { tasks, subscriptions } = this.props;
73
+ const { tasks = [], subscriptions } = this.props;
71
74
  const { disconnected } = subscriptions;
72
75
  let disabledReason = null;
73
76
 
@@ -132,7 +135,7 @@ class SubscriptionsPage extends Component {
132
135
  }
133
136
 
134
137
  render() {
135
- const { tasks, subscriptions } = this.props;
138
+ const { tasks = [], subscriptions } = this.props;
136
139
  const { disconnected } = subscriptions;
137
140
  const taskInProgress = tasks.length > 0;
138
141
  const disableManifestActions = taskInProgress || disconnected;
@@ -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')}
@@ -212,7 +235,19 @@ class UpstreamSubscriptionsPage extends Component {
212
235
 
213
236
  return (
214
237
  <Grid bsClass="container-fluid">
215
- <h1>{__('Add Subscriptions')}</h1>
238
+ <BreadcrumbsBar data={{
239
+ isSwitchable: false,
240
+ breadcrumbItems: [
241
+ {
242
+ caption: __('Subscriptions'),
243
+ onClick: () => this.props.history.push('/subscriptions'),
244
+ },
245
+ {
246
+ caption: __('Add Subscriptions'),
247
+ },
248
+ ],
249
+ }}
250
+ />
216
251
 
217
252
  <LoadingState loading={upstreamSubscriptions.loading} loadingText={__('Loading')}>
218
253
  <Row>
@@ -240,8 +275,8 @@ UpstreamSubscriptionsPage.propTypes = {
240
275
  saveUpstreamSubscriptions: PropTypes.func.isRequired,
241
276
  upstreamSubscriptions: PropTypes.shape({
242
277
  task: PropTypes.shape({}),
243
- error: PropTypes.shape({}),
244
278
  }).isRequired,
279
+ history: PropTypes.shape({ push: PropTypes.func.isRequired }).isRequired,
245
280
  };
246
281
 
247
282
  export default UpstreamSubscriptionsPage;