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
@@ -0,0 +1,3 @@
1
+ const state = { katello: { setOrganization: { currentId: 1 } } };
2
+ const store = { getState: () => state };
3
+ export default store;
@@ -1,17 +1,19 @@
1
1
  import React from 'react';
2
2
  import { shallow } from 'enzyme';
3
3
  import toJson from 'enzyme-to-json';
4
+ import { mock as mockApi } from '../../mockRequest';
4
5
 
5
6
  import Search from '../Search';
6
7
 
7
8
  describe('Search component', () => {
8
9
  const getBaseProps = () => ({
9
10
  onSearch: () => {},
10
- getAutoCompleteParams: () => ({}),
11
+ getAutoCompleteParams: () => ({ endpoint: '/fake' }),
11
12
  });
12
13
 
13
14
  describe('rendering', () => {
14
15
  it('renders correctly', () => {
16
+ mockApi.onGet('/katello/api/v2/fake').reply(200, []);
15
17
  const component = shallow(<Search {...getBaseProps()} />);
16
18
 
17
19
  expect(toJson(component)).toMatchSnapshot();
@@ -0,0 +1,3 @@
1
+ #select-org.well {
2
+ margin-top: 30px;
3
+ }
@@ -0,0 +1,41 @@
1
+ import { foremanApi, foremanEndpoint } from '../../services/api';
2
+ import {
3
+ GET_ORGANIZATIONS_LIST_SUCCESS,
4
+ GET_ORGANIZATIONS_LIST_FAILURE,
5
+ CHANGE_CURRENT_ORGANIZATION_SUCCESS,
6
+ CHANGE_CURRENT_ORGANIZATION_FAILURE,
7
+ GET_ORGANIZATIONS_LIST_REQUEST,
8
+ } from '../../redux/consts';
9
+
10
+ export const getOrganiztionsList = () => (dispatch) => {
11
+ dispatch({ type: GET_ORGANIZATIONS_LIST_REQUEST });
12
+ foremanApi
13
+ .get('/organizations')
14
+ .then(({ data }) => {
15
+ dispatch({
16
+ type: GET_ORGANIZATIONS_LIST_SUCCESS,
17
+ payload: data,
18
+ });
19
+ })
20
+ .catch((result) => {
21
+ dispatch({
22
+ type: GET_ORGANIZATIONS_LIST_FAILURE,
23
+ payload: result,
24
+ });
25
+ });
26
+ };
27
+
28
+ export const changeCurrentOrgaziation = orgID => dispatch => foremanEndpoint
29
+ .get(`organizations/${orgID}/select`)
30
+ .then(() => {
31
+ dispatch({
32
+ type: CHANGE_CURRENT_ORGANIZATION_SUCCESS,
33
+ payload: orgID,
34
+ });
35
+ })
36
+ .catch(() => {
37
+ dispatch({
38
+ type: CHANGE_CURRENT_ORGANIZATION_FAILURE,
39
+ payload: orgID,
40
+ });
41
+ });
@@ -0,0 +1,33 @@
1
+ import Immutable from 'seamless-immutable';
2
+ import {
3
+ GET_ORGANIZATIONS_LIST_SUCCESS,
4
+ GET_ORGANIZATIONS_LIST_REQUEST,
5
+ GET_ORGANIZATIONS_LIST_FAILURE,
6
+ CHANGE_CURRENT_ORGANIZATION_SUCCESS,
7
+ } from '../../redux/consts';
8
+
9
+ const initialState = Immutable({ loading: false });
10
+ export default (state = initialState, action) => {
11
+ const { payload } = action;
12
+
13
+ switch (action.type) {
14
+ case GET_ORGANIZATIONS_LIST_REQUEST:
15
+ return state.set('loading', true);
16
+
17
+ case GET_ORGANIZATIONS_LIST_SUCCESS:
18
+ return state
19
+ .set('list', payload.results)
20
+ .set('loading', false);
21
+
22
+ case CHANGE_CURRENT_ORGANIZATION_SUCCESS:
23
+ return state
24
+ .set('currentId', payload)
25
+ .set('loading', false);
26
+
27
+ case GET_ORGANIZATIONS_LIST_FAILURE:
28
+ return state
29
+ .set('error', payload);
30
+ default:
31
+ return state;
32
+ }
33
+ };
@@ -0,0 +1,116 @@
1
+ import React, { Component } from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import { Form, Button } from 'patternfly-react';
4
+ import { withRouter } from 'react-router';
5
+ import { bindActionCreators } from 'redux';
6
+ import { connect } from 'react-redux';
7
+ import Select from '../../move_to_pf/Select/Select';
8
+ import * as SelectOrgActions from './SelectOrgAction';
9
+ import reducer from './SelectOrgReducer';
10
+ import { LoadingState } from '../../move_to_pf/LoadingState';
11
+ import './SelectOrg.scss';
12
+
13
+ class SetOrganization extends Component {
14
+ constructor(props) {
15
+ super(props);
16
+ this.onSelectItem = this.onSelectItem.bind(this);
17
+ this.onSend = this.onSend.bind(this);
18
+ this.state = { disabled: true };
19
+ }
20
+
21
+ componentDidMount() {
22
+ this.props.getOrganiztionsList();
23
+ }
24
+
25
+ onSelectItem(e) {
26
+ this.setState({
27
+ item: e.target.options[e.target.selectedIndex].text,
28
+ id: e.target.value,
29
+ disabled: false,
30
+ });
31
+ }
32
+
33
+ onSend() {
34
+ const {
35
+ history,
36
+ changeCurrentOrgaziation,
37
+ redirectPath,
38
+ } = this.props;
39
+ const { id, item } = this.state;
40
+
41
+ changeCurrentOrgaziation(`${id}-${item}`).then(() =>
42
+ history.push({
43
+ pathname: redirectPath,
44
+ state: { orgChanged: this.state.item },
45
+ }));
46
+ }
47
+
48
+ render() {
49
+ const {
50
+ list,
51
+ loading,
52
+ } = this.props;
53
+
54
+ return (
55
+ <div id="select-org" className="well col-sm-6 col-sm-offset-3">
56
+ <LoadingState loading={loading} loadingText={__('Loading')}>
57
+ <Form>
58
+ <h1 className="text-center">{__('Select an Organization')}</h1>
59
+ <p className="text-center">
60
+ {__('The page you are attempting to access requires selecting a specific organization.')}
61
+ </p>
62
+ <p className="text-center">
63
+ {__('Please select one from the list below and you will be redirected.')}
64
+ </p>
65
+
66
+ <div className="form-group">
67
+ <div className="col-sm-6 col-sm-offset-3">
68
+ <Select
69
+ value={this.state.id}
70
+ placeholder={__('Select an organization')}
71
+ id="organization"
72
+ name="organization"
73
+ className="form-control"
74
+ options={list}
75
+ onChange={this.onSelectItem}
76
+ />
77
+
78
+ </div>
79
+
80
+ <div className="col-sm-3">
81
+ <Button disabled={this.state.disabled} className="btn btn-primary" onClick={this.onSend}>
82
+ {__('Select')}
83
+ </Button>
84
+ </div>
85
+ </div>
86
+ </Form>
87
+ </LoadingState>
88
+ </div>
89
+ );
90
+ }
91
+ }
92
+
93
+
94
+ SetOrganization.propTypes = {
95
+ list: PropTypes.arrayOf(PropTypes.object),
96
+ loading: PropTypes.bool.isRequired,
97
+ changeCurrentOrgaziation: PropTypes.func.isRequired,
98
+ history: PropTypes.shape({}).isRequired,
99
+ redirectPath: PropTypes.string.isRequired,
100
+ getOrganiztionsList: PropTypes.func.isRequired,
101
+ };
102
+
103
+ SetOrganization.defaultProps = {
104
+ list: [],
105
+ };
106
+
107
+ const mapStateToProps = state => ({
108
+ orgId: state.katello.setOrganization.currentId,
109
+ list: state.katello.setOrganization.list,
110
+ loading: state.katello.setOrganization.loading,
111
+ });
112
+ export const setOrganization = reducer;
113
+ const mapDispatchToProps = dispatch =>
114
+ bindActionCreators(SelectOrgActions, dispatch);
115
+
116
+ export default connect(mapStateToProps, mapDispatchToProps)(withRouter(SetOrganization));
@@ -0,0 +1,28 @@
1
+ import React, { Component } from 'react';
2
+ import { orgId } from '../../services/api';
3
+ import SetOrganization from '../SelectOrg/SetOrganization';
4
+ import titleWithCaret from '../../helpers/caret';
5
+
6
+ function withOrganization(WrappedComponent, redirectPath) {
7
+ return class CheckOrg extends Component {
8
+ componentDidUpdate(prevProps) {
9
+ const { location } = this.props;
10
+
11
+ // TODO: use topbar react component
12
+ const orgTitle = location.state && location.state.orgChanged;
13
+ const prevOrgTitle = prevProps.location.state && prevProps.location.state.orgChanged;
14
+
15
+ if (orgTitle !== prevOrgTitle) {
16
+ document.getElementById('organization-dropdown').children[0].innerHTML = titleWithCaret(orgTitle);
17
+ }
18
+ }
19
+ render() {
20
+ if (!orgId()) {
21
+ return <SetOrganization redirectPath={redirectPath} />;
22
+ }
23
+ return <WrappedComponent {...this.props} />;
24
+ }
25
+ };
26
+ }
27
+
28
+ export default withOrganization;
@@ -2,25 +2,32 @@ import Repos from '../../scenes/RedHatRepositories';
2
2
  import Subscriptions from '../../scenes/Subscriptions';
3
3
  import UpstreamSubscriptions from '../../scenes/Subscriptions/UpstreamSubscriptions/index';
4
4
  import SubscriptionDetails from '../../scenes/Subscriptions/Details';
5
+ import SetOrganization from '../../components/SelectOrg/SetOrganization';
6
+ import WithOrganization from '../../components/WithOrganization/withOrganization';
5
7
 
6
8
  // eslint-disable-next-line import/prefer-default-export
7
9
  export const links = [
8
10
  {
9
11
  text: 'RH Repos',
10
12
  path: 'redhat_repositories',
11
- component: Repos,
13
+ component: WithOrganization(Repos, '/redhat_repositories'),
12
14
  },
13
15
  {
14
16
  text: 'RH Subscriptions',
15
17
  path: 'subscriptions',
16
- component: Subscriptions,
18
+ component: WithOrganization(Subscriptions, '/subscriptions'),
17
19
  },
18
20
  {
19
21
  path: 'subscriptions/add',
20
22
  component: UpstreamSubscriptions,
21
23
  },
22
24
  {
25
+ // eslint-disable-next-line no-useless-escape
23
26
  path: 'subscriptions/:id(\[0-9]*$\)',
24
27
  component: SubscriptionDetails,
25
28
  },
29
+ {
30
+ path: 'organization_select',
31
+ component: SetOrganization,
32
+ },
26
33
  ];
@@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
3
3
  import { BrowserRouter as Router } from 'react-router-dom';
4
4
  import { bindActionCreators } from 'redux';
5
5
  import { connect } from 'react-redux';
6
-
6
+ import { orgId } from '../../services/api';
7
7
  import * as actions from '../../scenes/Organizations/OrganizationActions';
8
8
  import reducer from '../../scenes/Organizations/OrganizationReducer';
9
9
  import Routes from './Routes';
@@ -20,7 +20,9 @@ class Application extends Component {
20
20
  }
21
21
 
22
22
  loadData() {
23
- this.props.loadOrganization();
23
+ if (orgId()) {
24
+ this.props.loadOrganization();
25
+ }
24
26
  }
25
27
 
26
28
  render() {
@@ -0,0 +1,6 @@
1
+ // runs before each test to make sure console.error output will
2
+ // fail a test (i.e. default PropType missing). Check the error
3
+ // output and traceback for actual error.
4
+ global.console.error = (error) => {
5
+ throw new Error(error);
6
+ };
@@ -0,0 +1,6 @@
1
+ import React from 'react';
2
+ import ReactDOMServer from 'react-dom/server';
3
+
4
+ const htmlCaret = title => title + ReactDOMServer.renderToStaticMarkup(<span className="caret" />);
5
+
6
+ export default htmlCaret;
@@ -2,7 +2,7 @@ import axios from 'axios';
2
2
  import MockAdapter from 'axios-mock-adapter';
3
3
 
4
4
  // TODO: figure out way to reuse this from foreman
5
- const mock = new MockAdapter(axios);
5
+ export const mock = new MockAdapter(axios);
6
6
  const methods = {
7
7
  GET: 'onGet',
8
8
  POST: 'onPost',
@@ -15,10 +15,10 @@ const errorResponse = msg => ({ displayMessage: msg });
15
15
  export const mockRequest = ({
16
16
  method = 'GET',
17
17
  url,
18
- data = null,
18
+ data,
19
19
  status = 200,
20
20
  response = null,
21
- }) => mock[methods[method]](url, data).reply(status, response);
21
+ }) => mock[methods[method.toUpperCase()]](url, data).reply(status, response);
22
22
 
23
23
  export const mockErrorRequest = ({
24
24
  status = 500,
@@ -1,11 +1,41 @@
1
- export default {
2
- urlBuilder(controller, action, id = undefined) {
3
- return `/${controller}/${id ? `${id}/` : ''}${action}`;
4
- },
1
+ import { addToast } from 'foremanReact/redux/actions/toasts';
2
+
3
+ const urlBuilder = (controller, action, id = undefined) =>
4
+ `/${controller}/${id ? `${id}/` : ''}${action}`;
5
+
6
+ const urlWithSearch = (base, searchQuery) =>
7
+ `/${base}?search=${searchQuery}`;
8
+
9
+ const stringIsInteger = (value) => {
10
+ // checking for positive integers only
11
+ const reg = new RegExp('^[0-9]+$');
12
+ return reg.test(value);
13
+ };
5
14
 
6
- urlWithSearch(base, searchQuery) {
7
- return `/${base}?search=${searchQuery}`;
8
- },
15
+ export const getResponseErrorMsgs = ({ data }) => {
16
+ if (data) {
17
+ const messages = (data.errors || data.displayMessage || data.message || data.error);
18
+ return (Array.isArray(messages) ? messages : [messages]);
19
+ }
20
+ return [];
21
+ };
22
+
23
+ export const apiError = (actionType, result) => (dispatch) => {
24
+ const messages = getResponseErrorMsgs(result.response);
25
+ dispatch({
26
+ type: actionType,
27
+ payload: {
28
+ result,
29
+ messages,
30
+ },
31
+ });
32
+ messages.forEach((msg) => {
33
+ dispatch(addToast({
34
+ type: 'error',
35
+ message: msg,
36
+ sticky: true,
37
+ }));
38
+ });
9
39
  };
10
40
 
11
41
  export const KEY_CODES = {
@@ -14,4 +44,11 @@ export const KEY_CODES = {
14
44
  ESCAPE_KEY: 27,
15
45
  };
16
46
 
17
- export const getResponseError = ({ data }) => data && (data.displayMessage || data.error);
47
+ export default {
48
+ urlBuilder,
49
+ urlWithSearch,
50
+ getResponseErrorMsgs,
51
+ apiError,
52
+ KEY_CODES,
53
+ stringIsInteger,
54
+ };
@@ -13,6 +13,7 @@ const EmptyState = (props) => {
13
13
  documentationButton = __('Documentation'),
14
14
  docUrl,
15
15
  action,
16
+ actionButton,
16
17
  secondayActions,
17
18
  } = props;
18
19
  const defaultDocumantion = `${documentationLabel} <a href=${docUrl}>${documentationButton}</a>`;
@@ -30,11 +31,23 @@ const EmptyState = (props) => {
30
31
  )}
31
32
  {action && (
32
33
  <PfEmptyState.Action>
33
- <LinkContainer to={action.url}>
34
- <Button href={action.url} bsStyle="primary" bsSize="large">
34
+ {action.url && (
35
+ <LinkContainer to={action.url}>
36
+ <Button href={action.url} bsStyle="primary" bsSize="large">
37
+ {action.title}
38
+ </Button>
39
+ </LinkContainer>
40
+ )}
41
+ {action.onClick && (
42
+ <Button onClick={action.onClick} bsStyle="primary" bsSize="large">
35
43
  {action.title}
36
44
  </Button>
37
- </LinkContainer>
45
+ )}
46
+ </PfEmptyState.Action>
47
+ )}
48
+ {actionButton && (
49
+ <PfEmptyState.Action>
50
+ {actionButton}
38
51
  </PfEmptyState.Action>
39
52
  )}
40
53
  {secondayActions && (
@@ -2,7 +2,7 @@ import React from 'react';
2
2
  import PropTypes from 'prop-types';
3
3
  import { Table as PfTable } from 'patternfly-react';
4
4
  import { noop } from 'foremanReact/common/helpers';
5
- import EmptyState from '../../emptyState';
5
+ import EmptyState from '../../EmptyState';
6
6
  import PaginationRow from '../../../../../components/PaginationRow/index';
7
7
 
8
8
  import TableBody from './TableBody';
@@ -4,7 +4,7 @@ exports[`CollapseSubscriptionGroupButton renders CollapseSubscriptionGroupButton
4
4
  <Icon
5
5
  className="collapse-subscription-group-button"
6
6
  name="angle-right"
7
- onClick={[Function]}
7
+ onClick={[MockFunction]}
8
8
  type="fa"
9
9
  />
10
10
  `;
@@ -13,7 +13,7 @@ exports[`CollapseSubscriptionGroupButton renders CollapseSubscriptionGroupButton
13
13
  <Icon
14
14
  className="collapse-subscription-group-button"
15
15
  name="angle-down"
16
- onClick={[Function]}
16
+ onClick={[MockFunction]}
17
17
  type="fa"
18
18
  />
19
19
  `;
@@ -9,7 +9,7 @@ exports[`TableSelectionCell renders TableSelectionCell 1`] = `
9
9
  checked={true}
10
10
  id="some id"
11
11
  label="some label"
12
- onChange={[Function]}
12
+ onChange={[MockFunction]}
13
13
  />
14
14
  some after
15
15
  </TableSelectionCell>
@@ -9,7 +9,7 @@ exports[`TableSelectionHeaderCell renders TableSelectionHeaderCell 1`] = `
9
9
  checked={true}
10
10
  id="some id"
11
11
  label="some label"
12
- onChange={[Function]}
12
+ onChange={[MockFunction]}
13
13
  />
14
14
  </TableSelectionHeading>
15
15
  `;
@@ -1,35 +1,48 @@
1
- import React from 'react';
1
+ import React, { Component } from 'react';
2
2
  import PropTypes from 'prop-types';
3
3
  import { Spinner } from 'patternfly-react';
4
4
  import './LoadingState.scss';
5
5
 
6
- const LoadingState = ({
7
- loading,
8
- loadingText,
9
- children,
10
- }) => {
11
- if (loading) {
12
- return (
6
+ class LoadingState extends Component {
7
+ constructor(props) {
8
+ super(props);
9
+ this.state = {
10
+ render: false,
11
+ };
12
+ }
13
+
14
+ componentDidMount() {
15
+ setTimeout(() => {
16
+ this.setState({ render: true });
17
+ }, this.props.timeout);
18
+ }
19
+
20
+ render() {
21
+ const { loading, loadingText, children } = this.props;
22
+ const spinner = (
13
23
  <div className="loading-state">
14
24
  <Spinner loading={loading} size="lg" />
15
25
  <p>{loadingText}</p>
16
- </div>
17
- );
18
- }
19
-
20
- return children;
21
- };
26
+ </div>);
22
27
 
28
+ if (loading) {
29
+ return this.state.render ? spinner : null;
30
+ }
31
+ return children;
32
+ }
33
+ }
23
34
  LoadingState.propTypes = {
24
35
  loading: PropTypes.bool,
25
36
  loadingText: PropTypes.string,
26
37
  children: PropTypes.node,
38
+ timeout: PropTypes.number,
27
39
  };
28
40
 
29
41
  LoadingState.defaultProps = {
30
42
  loading: false,
31
43
  loadingText: __('Loading'),
32
44
  children: null,
45
+ timeout: 300,
33
46
  };
34
47
 
35
48
  export default LoadingState;
@@ -6,6 +6,7 @@ import { shallow } from 'enzyme';
6
6
  import toJson from 'enzyme-to-json';
7
7
 
8
8
  import { LoadingState } from './index';
9
+ jest.useFakeTimers();
9
10
 
10
11
  test('Loading State renders properly while loading', () => {
11
12
  const component = shallow(
@@ -13,8 +14,10 @@ test('Loading State renders properly while loading', () => {
13
14
  <p>Loading Complete</p>
14
15
  </LoadingState>
15
16
  );
16
-
17
- expect(toJson(component.render())).toMatchSnapshot();
17
+ jest.runAllTimers();
18
+ component.update();
19
+ expect(component.state('render')).toEqual(true);
20
+ expect(toJson(component.render())).toMatchSnapshot();
18
21
  });
19
22
 
20
23
  test('Loading State renders properly while not loading', () => {
@@ -23,6 +26,7 @@ test('Loading State renders properly while not loading', () => {
23
26
  <p>Loading Complete</p>
24
27
  </LoadingState>
25
28
  );
26
-
27
- expect(toJson(component.render())).toMatchSnapshot();
29
+ jest.runAllTimers();
30
+ component.update();
31
+ expect(toJson(component.render())).toMatchSnapshot();
28
32
  });
@@ -0,0 +1,40 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+
4
+ const Select = ({
5
+ placeholder, onChange, options, disabled, value,
6
+ }) => {
7
+ const renderOptions = arr =>
8
+ arr.map(({ name, id }) => (
9
+ <option key={id} value={id}>
10
+ {name}
11
+ </option>
12
+ ));
13
+
14
+ return (
15
+ <select
16
+ disabled={disabled}
17
+ className="form-control"
18
+ value={value}
19
+ onChange={onChange}
20
+ >
21
+ <option value="" disabled >{placeholder}</option>
22
+ {renderOptions(options)}
23
+ </select>
24
+ );
25
+ };
26
+
27
+ export default Select;
28
+
29
+ Select.propTypes = {
30
+ onChange: PropTypes.func.isRequired,
31
+ options: PropTypes.arrayOf(PropTypes.object).isRequired,
32
+ disabled: PropTypes.bool,
33
+ placeholder: PropTypes.string.isRequired,
34
+ value: PropTypes.string,
35
+ };
36
+
37
+ Select.defaultProps = {
38
+ disabled: false,
39
+ value: '',
40
+ };
@@ -3,6 +3,7 @@
3
3
  import React from 'react';
4
4
  import ReactDOM from 'react-dom';
5
5
  import { FormControl } from 'react-bootstrap';
6
+ import PropTypes from 'prop-types';
6
7
 
7
8
  require('jquery');
8
9
  require('bootstrap-select');
@@ -52,8 +53,10 @@ class BootstrapSelect extends React.Component {
52
53
  render() {
53
54
  // TODO: these classes are required because foreman assumes that all selects should use select2 and jquery multiselect
54
55
  // TODO: see also http://projects.theforeman.org/issues/21952
56
+ const { noneSelectedText } = this.props;
57
+
55
58
  return <FormControl {...this.props}
56
- data-none-selected-text={__('Nothing selected')}
59
+ data-none-selected-text={noneSelectedText}
57
60
  data-selected-text-format="count>3"
58
61
  data-count-selected-text={__('{0} items selected')}
59
62
  componentClass="select"
@@ -62,4 +65,12 @@ class BootstrapSelect extends React.Component {
62
65
  }
63
66
  }
64
67
 
68
+ BootstrapSelect.propTypes = {
69
+ noneSelectedText: PropTypes.string,
70
+ }
71
+
72
+ BootstrapSelect.defaultProps = {
73
+ noneSelectedText: __('Nothing selected'),
74
+ }
75
+
65
76
  export default BootstrapSelect;
@@ -34,7 +34,6 @@ export const createEnabledRepoParams = (extendedParams = {}) => {
34
34
  export const loadEnabledRepos = (extendedParams = {}) => (dispatch) => {
35
35
  dispatch({ type: ENABLED_REPOSITORIES_REQUEST, params: extendedParams });
36
36
  const { searchParams, repoParams } = createEnabledRepoParams(extendedParams);
37
-
38
37
  api
39
38
  .get('/repositories', {}, repoParams)
40
39
  .then(({ data }) => {