Use Nova profiles in node tagging form
Change-Id: I722fa9e3d7e90fa6f8d40a5667f0854a51d165ef Partial-Bug: 1750821
This commit is contained in:
parent
2a5536ef93
commit
ac0bb63abe
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
fixes:
|
||||||
|
- |
|
||||||
|
Fixes `bug 1750821 <https://launchpad.net/bugs/1750821>`__
|
||||||
|
Use Nova profiles in node tagging form instead of role profiles
|
|
@ -0,0 +1,52 @@
|
||||||
|
/**
|
||||||
|
* Copyright 2018 Red Hat Inc.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
* not use this file except in compliance with the License. You may obtain
|
||||||
|
* a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Map } from 'immutable';
|
||||||
|
import { FlavorsState, Flavor } from '../../js/immutableRecords/flavors';
|
||||||
|
import { getFlavorProfiles } from '../../js/selectors/flavors';
|
||||||
|
|
||||||
|
describe('flavor selectors', () => {
|
||||||
|
const state = {
|
||||||
|
flavors: new FlavorsState({
|
||||||
|
flavors: Map({
|
||||||
|
id1: new Flavor({
|
||||||
|
id: 'id1',
|
||||||
|
name: 'flavor 1',
|
||||||
|
extra_specs: Map({
|
||||||
|
'capabilities:profile': 'profile'
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
id2: new Flavor({
|
||||||
|
id: 'id2',
|
||||||
|
name: 'flavor 2',
|
||||||
|
extra_specs: Map({
|
||||||
|
'capabilities:profile': 'a profile'
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
id3: new Flavor({
|
||||||
|
id: 'id3',
|
||||||
|
name: 'flavor 3',
|
||||||
|
extra_specs: Map({})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
it('selects flavor profiles', () => {
|
||||||
|
expect(getFlavorProfiles(state).size).toEqual(2);
|
||||||
|
expect(getFlavorProfiles(state).first()).toEqual('a profile');
|
||||||
|
});
|
||||||
|
});
|
|
@ -24,7 +24,6 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
import Formsy from 'formsy-react';
|
import Formsy from 'formsy-react';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
getAvailableNodeProfiles,
|
|
||||||
getFilteredNodes,
|
getFilteredNodes,
|
||||||
getNodesOperationInProgress
|
getNodesOperationInProgress
|
||||||
} from '../../selectors/nodes';
|
} from '../../selectors/nodes';
|
||||||
|
@ -236,7 +235,6 @@ class NodesTableView extends React.Component {
|
||||||
onCancel={() => this.setState({ showDeleteModal: false })}
|
onCancel={() => this.setState({ showDeleteModal: false })}
|
||||||
/>
|
/>
|
||||||
<TagNodesModal
|
<TagNodesModal
|
||||||
availableProfiles={this.props.availableProfiles.toArray()}
|
|
||||||
onProfileSelected={this.onTagNodesSubmit.bind(this)}
|
onProfileSelected={this.onTagNodesSubmit.bind(this)}
|
||||||
onCancel={() =>
|
onCancel={() =>
|
||||||
this.setState({ showTagNodesModal: false, submitParameters: {} })
|
this.setState({ showTagNodesModal: false, submitParameters: {} })
|
||||||
|
@ -249,7 +247,6 @@ class NodesTableView extends React.Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
NodesTableView.propTypes = {
|
NodesTableView.propTypes = {
|
||||||
availableProfiles: ImmutablePropTypes.list.isRequired,
|
|
||||||
children: PropTypes.node,
|
children: PropTypes.node,
|
||||||
deleteNodes: PropTypes.func.isRequired,
|
deleteNodes: PropTypes.func.isRequired,
|
||||||
formErrors: ImmutablePropTypes.list,
|
formErrors: ImmutablePropTypes.list,
|
||||||
|
@ -270,7 +267,6 @@ NodesTableView.defaultProps = {
|
||||||
|
|
||||||
function mapStateToProps(state) {
|
function mapStateToProps(state) {
|
||||||
return {
|
return {
|
||||||
availableProfiles: getAvailableNodeProfiles(state),
|
|
||||||
nodes: getFilteredNodes(state),
|
nodes: getFilteredNodes(state),
|
||||||
nodesInProgress: state.nodes.get('nodesInProgress'),
|
nodesInProgress: state.nodes.get('nodesInProgress'),
|
||||||
nodesOperationInProgress: getNodesOperationInProgress(state),
|
nodesOperationInProgress: getNodesOperationInProgress(state),
|
||||||
|
|
|
@ -16,27 +16,13 @@
|
||||||
|
|
||||||
import { defineMessages, FormattedMessage, injectIntl } from 'react-intl';
|
import { defineMessages, FormattedMessage, injectIntl } from 'react-intl';
|
||||||
import Formsy from 'formsy-react';
|
import Formsy from 'formsy-react';
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
|
||||||
import { Link } from 'react-router-dom';
|
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import HorizontalSelect from '../../ui/forms/HorizontalSelect';
|
import HorizontalSelect from '../../ui/forms/HorizontalSelect';
|
||||||
import HorizontalInput from '../../ui/forms/HorizontalInput';
|
import HorizontalInput from '../../ui/forms/HorizontalInput';
|
||||||
import InlineNotification from '../../ui/InlineNotification';
|
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
activateDeploymentPlan: {
|
|
||||||
id: 'TagNodesForm.activateDeploymentPlan',
|
|
||||||
defaultMessage: 'Activate a Deployment plan',
|
|
||||||
description: 'First part of noRolesInfo message - the contents of a link'
|
|
||||||
},
|
|
||||||
noRolesInfo: {
|
|
||||||
id: 'TagNodesForm.noRolesInfo',
|
|
||||||
defaultMessage: '{link} to select profiles which match available Roles',
|
|
||||||
description:
|
|
||||||
'A second part of noRolesInfo message - rest of the text after link'
|
|
||||||
},
|
|
||||||
confirm: {
|
confirm: {
|
||||||
id: 'TagNodesForm.confirm',
|
id: 'TagNodesForm.confirm',
|
||||||
defaultMessage: 'Tag Nodes'
|
defaultMessage: 'Tag Nodes'
|
||||||
|
@ -119,7 +105,7 @@ class TagNodesForm extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { intl: { formatMessage }, onCancel, roles } = this.props;
|
const { intl: { formatMessage }, onCancel } = this.props;
|
||||||
return (
|
return (
|
||||||
<Formsy
|
<Formsy
|
||||||
ref="tagNodesForm"
|
ref="tagNodesForm"
|
||||||
|
@ -130,20 +116,6 @@ class TagNodesForm extends React.Component {
|
||||||
onInvalid={this.disableButton.bind(this)}
|
onInvalid={this.disableButton.bind(this)}
|
||||||
>
|
>
|
||||||
<div className="modal-body">
|
<div className="modal-body">
|
||||||
{roles.isEmpty() && (
|
|
||||||
<InlineNotification type="info">
|
|
||||||
<FormattedMessage
|
|
||||||
{...messages.noRolesInfo}
|
|
||||||
values={{
|
|
||||||
link: (
|
|
||||||
<Link to="/plans">
|
|
||||||
<FormattedMessage {...messages.activateDeploymentPlan} />
|
|
||||||
</Link>
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</InlineNotification>
|
|
||||||
)}
|
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<HorizontalSelect
|
<HorizontalSelect
|
||||||
name="profile"
|
name="profile"
|
||||||
|
@ -197,7 +169,6 @@ TagNodesForm.propTypes = {
|
||||||
intl: PropTypes.object.isRequired,
|
intl: PropTypes.object.isRequired,
|
||||||
onCancel: PropTypes.func.isRequired,
|
onCancel: PropTypes.func.isRequired,
|
||||||
onSubmit: PropTypes.func.isRequired,
|
onSubmit: PropTypes.func.isRequired,
|
||||||
profiles: PropTypes.array.isRequired,
|
profiles: PropTypes.array.isRequired
|
||||||
roles: ImmutablePropTypes.list.isRequired
|
|
||||||
};
|
};
|
||||||
export default injectIntl(TagNodesForm);
|
export default injectIntl(TagNodesForm);
|
||||||
|
|
|
@ -21,11 +21,9 @@ import { ModalHeader, ModalTitle } from 'react-bootstrap';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { getAvailableNodeProfiles } from '../../../selectors/nodes';
|
import { getFlavorProfiles } from '../../../selectors/flavors';
|
||||||
import { getCurrentPlan } from '../../../selectors/plans';
|
|
||||||
import { getRoles } from '../../../selectors/roles';
|
|
||||||
import { CloseModalXButton, Modal } from '../../ui/Modals';
|
import { CloseModalXButton, Modal } from '../../ui/Modals';
|
||||||
import RolesActions from '../../../actions/RolesActions';
|
import FlavorsActions from '../../../actions/FlavorsActions';
|
||||||
import TagNodesForm from './TagNodesForm';
|
import TagNodesForm from './TagNodesForm';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
|
@ -37,18 +35,11 @@ const messages = defineMessages({
|
||||||
|
|
||||||
class TagNodesModal extends React.Component {
|
class TagNodesModal extends React.Component {
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
const { currentPlan, fetchRoles } = this.props;
|
this.props.fetchFlavors();
|
||||||
currentPlan && fetchRoles(currentPlan.name);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const { show, onCancel, onProfileSelected, profiles } = this.props;
|
||||||
show,
|
|
||||||
onCancel,
|
|
||||||
onProfileSelected,
|
|
||||||
availableProfiles,
|
|
||||||
roles
|
|
||||||
} = this.props;
|
|
||||||
return (
|
return (
|
||||||
<Modal show={show} onHide={onCancel}>
|
<Modal show={show} onHide={onCancel}>
|
||||||
<ModalHeader>
|
<ModalHeader>
|
||||||
|
@ -61,31 +52,26 @@ class TagNodesModal extends React.Component {
|
||||||
<TagNodesForm
|
<TagNodesForm
|
||||||
onCancel={onCancel}
|
onCancel={onCancel}
|
||||||
onSubmit={onProfileSelected}
|
onSubmit={onProfileSelected}
|
||||||
profiles={availableProfiles.toArray()}
|
profiles={profiles.toArray()}
|
||||||
roles={roles.toList()}
|
|
||||||
/>
|
/>
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
TagNodesModal.propTypes = {
|
TagNodesModal.propTypes = {
|
||||||
availableProfiles: ImmutablePropTypes.list.isRequired,
|
fetchFlavors: PropTypes.func.isRequired,
|
||||||
currentPlan: ImmutablePropTypes.record,
|
|
||||||
fetchRoles: PropTypes.func.isRequired,
|
|
||||||
onCancel: PropTypes.func.isRequired,
|
onCancel: PropTypes.func.isRequired,
|
||||||
onProfileSelected: PropTypes.func.isRequired,
|
onProfileSelected: PropTypes.func.isRequired,
|
||||||
roles: ImmutablePropTypes.map.isRequired,
|
profiles: ImmutablePropTypes.map.isRequired,
|
||||||
show: PropTypes.bool.isRequired
|
show: PropTypes.bool.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
const mapStateToProps = state => ({
|
||||||
availableProfiles: getAvailableNodeProfiles(state),
|
profiles: getFlavorProfiles(state)
|
||||||
currentPlan: getCurrentPlan(state),
|
|
||||||
roles: getRoles(state)
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const mapDispatchToProps = dispatch => ({
|
const mapDispatchToProps = dispatch => ({
|
||||||
fetchRoles: planName => dispatch(RolesActions.fetchRoles(planName))
|
fetchFlavors: () => dispatch(FlavorsActions.fetchFlavors())
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(TagNodesModal);
|
export default connect(mapStateToProps, mapDispatchToProps)(TagNodesModal);
|
||||||
|
|
|
@ -16,8 +16,11 @@
|
||||||
|
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
|
|
||||||
const flavors = state => state.flavors;
|
export const getFlavors = state => state.flavors.flavors;
|
||||||
|
|
||||||
export const getFlavors = createSelector([flavors], flavors =>
|
export const getFlavorProfiles = createSelector([getFlavors], flavors =>
|
||||||
flavors.flavors.sortBy(f => f.name)
|
flavors
|
||||||
|
.map(flavor => flavor.getIn(['extra_specs', 'capabilities:profile']))
|
||||||
|
.filter(profile => profile !== undefined)
|
||||||
|
.sort()
|
||||||
);
|
);
|
||||||
|
|
|
@ -15,10 +15,9 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
import { List, Map, Set } from 'immutable';
|
import { List, Map } from 'immutable';
|
||||||
|
|
||||||
import { getFilterByName } from './filters';
|
import { getFilterByName } from './filters';
|
||||||
import { getRoles } from './roles';
|
|
||||||
import { IntrospectionStatus } from '../immutableRecords/nodes';
|
import { IntrospectionStatus } from '../immutableRecords/nodes';
|
||||||
import { parseNodeCapabilities } from '../utils/nodes';
|
import { parseNodeCapabilities } from '../utils/nodes';
|
||||||
|
|
||||||
|
@ -119,30 +118,6 @@ export const getFilteredNodes = createSelector(
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
|
||||||
* Return a list of profiles collected across all nodes
|
|
||||||
*/
|
|
||||||
export const getProfilesList = createSelector(getNodes, nodes =>
|
|
||||||
nodes
|
|
||||||
.reduce((profiles, v, k) => {
|
|
||||||
const profile = getNodeCapabilities(v).profile;
|
|
||||||
return profile ? profiles.push(profile) : profiles;
|
|
||||||
}, List())
|
|
||||||
.sort()
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return a list of profiles merged with role identifiers
|
|
||||||
*/
|
|
||||||
export const getAvailableNodeProfiles = createSelector(
|
|
||||||
[getProfilesList, getRoles],
|
|
||||||
(profiles, roles) =>
|
|
||||||
Set(roles.toList().map(r => r.identifier))
|
|
||||||
.union(profiles)
|
|
||||||
.toList()
|
|
||||||
.sort()
|
|
||||||
);
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* booleam, returns true if there are any nodes with operation in progress
|
* booleam, returns true if there are any nodes with operation in progress
|
||||||
*/
|
*/
|
||||||
|
|
Loading…
Reference in New Issue