Use Nova profiles in node tagging form
Change-Id: I722fa9e3d7e90fa6f8d40a5667f0854a51d165ef
Partial-Bug: 1750821
(cherry picked from commit ac0bb63abe
)
This commit is contained in:
parent
57e3d96237
commit
77f9ba2483
5
releasenotes/notes/nova-profiles-5457ad7c39d19426.yaml
Normal file
5
releasenotes/notes/nova-profiles-5457ad7c39d19426.yaml
Normal 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
|
52
src/__tests__/selectors/flavors.tests.js
Normal file
52
src/__tests__/selectors/flavors.tests.js
Normal 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');
|
||||
});
|
||||
});
|
@ -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),
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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()
|
||||
);
|
||||
|
@ -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
|
||||
*/
|
||||
|
Loading…
Reference in New Issue
Block a user