Use Nova profiles in node tagging form

Change-Id: I722fa9e3d7e90fa6f8d40a5667f0854a51d165ef
Partial-Bug: 1750821
(cherry picked from commit ac0bb63abe)
This commit is contained in:
Honza Pokorny 2018-02-21 09:37:58 -04:00 committed by Jiri Tomasek
parent 57e3d96237
commit 77f9ba2483
7 changed files with 75 additions and 87 deletions

View File

@ -0,0 +1,5 @@
---
fixes:
- |
Fixes `bug 1750821 <https://launchpad.net/bugs/1750821>`__
Use Nova profiles in node tagging form instead of role profiles

View File

@ -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');
});
});

View File

@ -24,7 +24,6 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
import Formsy from 'formsy-react';
import {
getAvailableNodeProfiles,
getFilteredNodes,
getNodesOperationInProgress
} from '../../selectors/nodes';
@ -236,7 +235,6 @@ class NodesTableView extends React.Component {
onCancel={() => this.setState({ showDeleteModal: false })}
/>
<TagNodesModal
availableProfiles={this.props.availableProfiles.toArray()}
onProfileSelected={this.onTagNodesSubmit.bind(this)}
onCancel={() =>
this.setState({ showTagNodesModal: false, submitParameters: {} })
@ -249,7 +247,6 @@ class NodesTableView extends React.Component {
}
}
NodesTableView.propTypes = {
availableProfiles: ImmutablePropTypes.list.isRequired,
children: PropTypes.node,
deleteNodes: PropTypes.func.isRequired,
formErrors: ImmutablePropTypes.list,
@ -270,7 +267,6 @@ NodesTableView.defaultProps = {
function mapStateToProps(state) {
return {
availableProfiles: getAvailableNodeProfiles(state),
nodes: getFilteredNodes(state),
nodesInProgress: state.nodes.get('nodesInProgress'),
nodesOperationInProgress: getNodesOperationInProgress(state),

View File

@ -16,27 +16,13 @@
import { defineMessages, FormattedMessage, injectIntl } from 'react-intl';
import Formsy from 'formsy-react';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { Link } from 'react-router-dom';
import PropTypes from 'prop-types';
import React from 'react';
import HorizontalSelect from '../../ui/forms/HorizontalSelect';
import HorizontalInput from '../../ui/forms/HorizontalInput';
import InlineNotification from '../../ui/InlineNotification';
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: {
id: 'TagNodesForm.confirm',
defaultMessage: 'Tag Nodes'
@ -119,7 +105,7 @@ class TagNodesForm extends React.Component {
}
render() {
const { intl: { formatMessage }, onCancel, roles } = this.props;
const { intl: { formatMessage }, onCancel } = this.props;
return (
<Formsy
ref="tagNodesForm"
@ -130,20 +116,6 @@ class TagNodesForm extends React.Component {
onInvalid={this.disableButton.bind(this)}
>
<div className="modal-body">
{roles.isEmpty() && (
<InlineNotification type="info">
<FormattedMessage
{...messages.noRolesInfo}
values={{
link: (
<Link to="/plans">
<FormattedMessage {...messages.activateDeploymentPlan} />
</Link>
)
}}
/>
</InlineNotification>
)}
<fieldset>
<HorizontalSelect
name="profile"
@ -197,7 +169,6 @@ TagNodesForm.propTypes = {
intl: PropTypes.object.isRequired,
onCancel: PropTypes.func.isRequired,
onSubmit: PropTypes.func.isRequired,
profiles: PropTypes.array.isRequired,
roles: ImmutablePropTypes.list.isRequired
profiles: PropTypes.array.isRequired
};
export default injectIntl(TagNodesForm);

View File

@ -21,11 +21,9 @@ import { ModalHeader, ModalTitle } from 'react-bootstrap';
import PropTypes from 'prop-types';
import React from 'react';
import { getAvailableNodeProfiles } from '../../../selectors/nodes';
import { getCurrentPlan } from '../../../selectors/plans';
import { getRoles } from '../../../selectors/roles';
import { getFlavorProfiles } from '../../../selectors/flavors';
import { CloseModalXButton, Modal } from '../../ui/Modals';
import RolesActions from '../../../actions/RolesActions';
import FlavorsActions from '../../../actions/FlavorsActions';
import TagNodesForm from './TagNodesForm';
const messages = defineMessages({
@ -37,18 +35,11 @@ const messages = defineMessages({
class TagNodesModal extends React.Component {
componentDidMount() {
const { currentPlan, fetchRoles } = this.props;
currentPlan && fetchRoles(currentPlan.name);
this.props.fetchFlavors();
}
render() {
const {
show,
onCancel,
onProfileSelected,
availableProfiles,
roles
} = this.props;
const { show, onCancel, onProfileSelected, profiles } = this.props;
return (
<Modal show={show} onHide={onCancel}>
<ModalHeader>
@ -61,31 +52,26 @@ class TagNodesModal extends React.Component {
<TagNodesForm
onCancel={onCancel}
onSubmit={onProfileSelected}
profiles={availableProfiles.toArray()}
roles={roles.toList()}
profiles={profiles.toArray()}
/>
</Modal>
);
}
}
TagNodesModal.propTypes = {
availableProfiles: ImmutablePropTypes.list.isRequired,
currentPlan: ImmutablePropTypes.record,
fetchRoles: PropTypes.func.isRequired,
fetchFlavors: PropTypes.func.isRequired,
onCancel: PropTypes.func.isRequired,
onProfileSelected: PropTypes.func.isRequired,
roles: ImmutablePropTypes.map.isRequired,
profiles: ImmutablePropTypes.map.isRequired,
show: PropTypes.bool.isRequired
};
const mapStateToProps = state => ({
availableProfiles: getAvailableNodeProfiles(state),
currentPlan: getCurrentPlan(state),
roles: getRoles(state)
profiles: getFlavorProfiles(state)
});
const mapDispatchToProps = dispatch => ({
fetchRoles: planName => dispatch(RolesActions.fetchRoles(planName))
fetchFlavors: () => dispatch(FlavorsActions.fetchFlavors())
});
export default connect(mapStateToProps, mapDispatchToProps)(TagNodesModal);

View File

@ -16,8 +16,11 @@
import { createSelector } from 'reselect';
const flavors = state => state.flavors;
export const getFlavors = state => state.flavors.flavors;
export const getFlavors = createSelector([flavors], flavors =>
flavors.flavors.sortBy(f => f.name)
export const getFlavorProfiles = createSelector([getFlavors], flavors =>
flavors
.map(flavor => flavor.getIn(['extra_specs', 'capabilities:profile']))
.filter(profile => profile !== undefined)
.sort()
);

View File

@ -15,10 +15,9 @@
*/
import { createSelector } from 'reselect';
import { List, Map, Set } from 'immutable';
import { List, Map } from 'immutable';
import { getFilterByName } from './filters';
import { getRoles } from './roles';
import { IntrospectionStatus } from '../immutableRecords/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
*/