katello 3.8.0.rc1 → 3.8.0.rc2
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of katello might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/app/assets/javascripts/katello/common/index.js +1 -0
- data/app/assets/javascripts/katello/sync_management/index.js +1 -0
- data/app/controllers/katello/api/registry/registry_proxies_controller.rb +1 -1
- data/app/controllers/katello/api/v2/environments_controller.rb +0 -1
- data/app/controllers/katello/api/v2/ostree_branches_controller.rb +1 -1
- data/app/controllers/katello/api/v2/repository_sets_controller.rb +10 -1
- data/app/controllers/katello/remote_execution_controller.rb +6 -6
- data/app/helpers/katello/hosts_and_hostgroups_helper.rb +37 -9
- data/app/lib/katello/resources/registry.rb +4 -4
- data/app/models/katello/authorization/repository.rb +2 -1
- data/app/models/katello/content_view.rb +12 -4
- data/app/models/katello/glue/candlepin/owner.rb +0 -8
- data/app/models/katello/glue/candlepin/pool.rb +11 -11
- data/app/models/katello/kt_environment.rb +0 -6
- data/app/models/katello/product_content.rb +4 -1
- data/app/models/katello/rpm.rb +13 -5
- data/app/services/katello/ui_notifications/pulp/proxy_disk_space.rb +3 -1
- data/app/views/overrides/activation_keys/_host_environment_select.html.erb +2 -3
- data/config/katello.yaml.example +5 -0
- data/config/routes.rb +1 -0
- data/db/seeds.d/75-job_templates.rb +5 -2
- data/engines/bastion_katello/app/assets/javascripts/bastion_katello/environments/details/views/environment-details.html +43 -8
- data/engines/bastion_katello/app/assets/javascripts/bastion_katello/products/discovery/discovery.controller.js +17 -2
- data/engines/bastion_katello/app/assets/javascripts/bastion_katello/products/discovery/views/discovery-create.html +1 -1
- data/engines/bastion_katello/app/assets/javascripts/bastion_katello/products/discovery/views/discovery.html +1 -1
- data/engines/bastion_katello/app/assets/stylesheets/bastion_katello/bastion_katello.scss +5 -0
- data/lib/katello/version.rb +1 -1
- data/package.json +11 -7
- data/webpack/__mocks__/foremanReact/components/BreadcrumbBar.js +3 -0
- data/webpack/__mocks__/foremanReact/redux/actions/toasts.js +8 -0
- data/webpack/__mocks__/foremanReact/redux.js +3 -0
- data/webpack/components/Search/Search.test.js +3 -1
- data/webpack/components/SelectOrg/SelectOrg.scss +3 -0
- data/webpack/components/SelectOrg/SelectOrgAction.js +41 -0
- data/webpack/components/SelectOrg/SelectOrgReducer.js +33 -0
- data/webpack/components/SelectOrg/SetOrganization.js +116 -0
- data/webpack/components/WithOrganization/withOrganization.js +28 -0
- data/webpack/containers/Application/config.js +9 -2
- data/webpack/containers/Application/index.js +4 -2
- data/webpack/global_test_setup.js +6 -0
- data/webpack/helpers/caret.js +6 -0
- data/webpack/mockRequest.js +3 -3
- data/webpack/move_to_foreman/common/helpers.js +45 -8
- data/webpack/move_to_foreman/components/common/{emptyState → EmptyState}/index.js +16 -3
- data/webpack/move_to_foreman/components/common/table/components/Table.js +1 -1
- data/webpack/move_to_foreman/components/common/table/components/__snapshots__/CollapseSubscriptionGroupButton.test.js.snap +2 -2
- data/webpack/move_to_foreman/components/common/table/components/__snapshots__/TableSelectionCell.test.js.snap +1 -1
- data/webpack/move_to_foreman/components/common/table/components/__snapshots__/TableSelectionHeaderCell.test.js.snap +1 -1
- data/webpack/move_to_pf/LoadingState/LoadingState.js +27 -14
- data/webpack/move_to_pf/LoadingState/LoadingState.test.js +8 -4
- data/webpack/move_to_pf/Select/Select.js +40 -0
- data/webpack/move_to_pf/react-bootstrap-select/index.js +12 -1
- data/webpack/redux/actions/RedHatRepositories/enabled.js +0 -1
- data/webpack/redux/actions/RedHatRepositories/helpers.js +5 -5
- data/webpack/redux/actions/RedHatRepositories/sets.js +1 -1
- data/webpack/redux/consts.js +6 -0
- data/webpack/redux/reducers/index.js +2 -0
- data/webpack/scenes/RedHatRepositories/components/EnabledRepository.js +14 -23
- data/webpack/scenes/RedHatRepositories/components/EnabledRepositoryContent.js +34 -0
- data/webpack/scenes/RedHatRepositories/components/RepositorySetRepository.js +1 -1
- data/webpack/scenes/RedHatRepositories/components/SearchBar.js +1 -0
- data/webpack/scenes/RedHatRepositories/components/__tests__/EnabledRepository.test.js +36 -0
- data/webpack/scenes/RedHatRepositories/components/__tests__/EnabledRepositoryContent.test.js +27 -0
- data/webpack/scenes/RedHatRepositories/components/__tests__/__snapshots__/EnabledRepository.test.js.snap +25 -0
- data/webpack/scenes/RedHatRepositories/components/__tests__/__snapshots__/EnabledRepositoryContent.test.js.snap +47 -0
- data/webpack/scenes/RedHatRepositories/components/__tests__/__snapshots__/RecommendedRepositorySetsToggler.test.js.snap +3 -1
- data/webpack/scenes/RedHatRepositories/index.js +7 -3
- data/webpack/scenes/RedHatRepositories/index.scss +1 -0
- data/webpack/scenes/Subscriptions/Details/SubscriptionDetailActions.js +3 -8
- data/webpack/scenes/Subscriptions/Details/SubscriptionDetailProducts.js +5 -3
- data/webpack/scenes/Subscriptions/Details/SubscriptionDetailReducer.js +1 -1
- data/webpack/scenes/Subscriptions/Details/SubscriptionDetails.js +44 -6
- data/webpack/scenes/Subscriptions/Details/SubscriptionDetails.scss +4 -0
- data/webpack/scenes/Subscriptions/Details/__tests__/SubscriptionDetailReducer.test.js +3 -1
- data/webpack/scenes/Subscriptions/Details/__tests__/SubscriptionDetails.test.js +2 -1
- data/webpack/scenes/Subscriptions/Details/__tests__/__snapshots__/SubscriptionDetailProducts.test.js.snap +113 -23
- data/webpack/scenes/Subscriptions/Details/__tests__/__snapshots__/SubscriptionDetails.test.js.snap +23 -14
- data/webpack/scenes/Subscriptions/Details/__tests__/subscriptionDetails.fixtures.js +3 -4
- data/webpack/scenes/Subscriptions/Details/index.js +2 -2
- data/webpack/scenes/Subscriptions/Manifest/ManageManifestModal.js +78 -34
- data/webpack/scenes/Subscriptions/Manifest/ManifestActions.js +5 -24
- data/webpack/scenes/Subscriptions/Manifest/ManifestHistoryReducer.js +9 -1
- data/webpack/scenes/Subscriptions/Manifest/__tests__/ManageManifestModal.test.js +3 -0
- data/webpack/scenes/Subscriptions/Manifest/__tests__/ManifestActions.test.js +20 -8
- data/webpack/scenes/Subscriptions/Manifest/__tests__/ManifestHistoryReducer.test.js +3 -1
- data/webpack/scenes/Subscriptions/Manifest/__tests__/__snapshots__/ManageManifestModal.test.js.snap +34 -7
- data/webpack/scenes/Subscriptions/Manifest/__tests__/manifest.fixtures.js +9 -16
- data/webpack/scenes/Subscriptions/Manifest/index.js +1 -0
- data/webpack/scenes/Subscriptions/SubscriptionActions.js +5 -26
- data/webpack/scenes/Subscriptions/SubscriptionConstants.js +1 -0
- data/webpack/scenes/Subscriptions/SubscriptionHelpers.js +3 -0
- data/webpack/scenes/Subscriptions/SubscriptionReducer.js +11 -4
- data/webpack/scenes/Subscriptions/SubscriptionsPage.js +31 -36
- data/webpack/scenes/Subscriptions/UpstreamSubscriptions/UpstreamSubscriptionsActions.js +3 -12
- data/webpack/scenes/Subscriptions/UpstreamSubscriptions/UpstreamSubscriptionsPage.js +57 -27
- data/webpack/scenes/Subscriptions/UpstreamSubscriptions/UpstreamSubscriptionsReducer.js +2 -3
- data/webpack/scenes/Subscriptions/UpstreamSubscriptions/UpstreamSubscriptionsTableSchema.js +10 -5
- data/webpack/scenes/Subscriptions/UpstreamSubscriptions/__tests__/UpstreamSubscriptionsActions.test.js +10 -5
- data/webpack/scenes/Subscriptions/UpstreamSubscriptions/__tests__/UpstreamSubscriptionsPage.test.js +50 -5
- data/webpack/scenes/Subscriptions/UpstreamSubscriptions/__tests__/UpstreamSubscriptionsReducer.test.js +8 -3
- data/webpack/scenes/Subscriptions/UpstreamSubscriptions/__tests__/__snapshots__/UpstreamSubscriptionsPage.test.js.snap +21 -11
- data/webpack/scenes/Subscriptions/UpstreamSubscriptions/__tests__/upstreamSubscriptions.fixtures.js +5 -8
- data/webpack/scenes/Subscriptions/__tests__/SubscriptionsPage.test.js +2 -0
- data/webpack/scenes/Subscriptions/__tests__/SubscriptionsReducer.test.js +9 -3
- data/webpack/scenes/Subscriptions/__tests__/__snapshots__/SubscriptionsPage.test.js.snap +14 -2
- data/webpack/scenes/Subscriptions/__tests__/subscriptions.fixtures.js +11 -17
- data/webpack/scenes/Subscriptions/components/SubscriptionsTable/EntitlementsInlineEditFormatter.js +8 -5
- data/webpack/scenes/Subscriptions/components/SubscriptionsTable/SubscriptionsTable.js +45 -58
- data/webpack/scenes/Subscriptions/components/SubscriptionsTable/SubscriptionsTableHelpers.js +11 -4
- data/webpack/scenes/Subscriptions/components/SubscriptionsTable/SubscriptionsTableSchema.js +2 -2
- data/webpack/scenes/Subscriptions/components/SubscriptionsTable/__tests__/EntitlementsInlineEditFormatter.test.js +110 -0
- data/webpack/scenes/Subscriptions/components/SubscriptionsTable/__tests__/SubscriptionsTable.test.js +16 -3
- data/webpack/scenes/Subscriptions/components/SubscriptionsTable/__tests__/__snapshots__/EntitlementsInlineEditFormatter.test.js.snap +228 -0
- data/webpack/scenes/Subscriptions/components/SubscriptionsTable/__tests__/__snapshots__/SubscriptionsTable.test.js.snap +392 -365
- data/webpack/scenes/Subscriptions/index.js +1 -0
- data/webpack/scenes/Tasks/helpers.js +52 -0
- data/webpack/services/api/index.js +17 -1
- data/webpack/services/api/testHelpers.js +28 -0
- data/webpack/test_setup.js +2 -0
- metadata +24 -5
- data/config/katello.yaml +0 -89
- data/webpack/services/api/fixtures.js +0 -353
@@ -1,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,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
|
-
|
23
|
+
if (orgId()) {
|
24
|
+
this.props.loadOrganization();
|
25
|
+
}
|
24
26
|
}
|
25
27
|
|
26
28
|
render() {
|
data/webpack/mockRequest.js
CHANGED
@@ -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
|
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
|
-
|
2
|
-
|
3
|
-
|
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
|
-
|
7
|
-
|
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
|
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
|
-
|
34
|
-
<
|
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
|
-
|
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 '../../
|
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={[
|
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={[
|
16
|
+
onClick={[MockFunction]}
|
17
17
|
type="fa"
|
18
18
|
/>
|
19
19
|
`;
|
@@ -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
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
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
|
-
|
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
|
-
|
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={
|
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 }) => {
|