Antonio-Maranhao closed pull request #1133: update cluster module to redux
URL: https://github.com/apache/couchdb-fauxton/pull/1133
This is a PR merged from a forked repository.
As GitHub hides the original diff on merge, it is displayed below for
the sake of provenance:
As this is a foreign pull request (from a fork), the diff is supplied
below (as it won't show otherwise due to GitHub magic):
diff --git a/app/addons/auth/actions.js b/app/addons/auth/actions.js
index a58cd0915..1e094c9cf 100644
--- a/app/addons/auth/actions.js
+++ b/app/addons/auth/actions.js
@@ -11,7 +11,6 @@
// the License.
import FauxtonAPI from "../../core/api";
import app from "../../app";
-import ClusterStore from "../cluster/cluster.stores";
import ActionTypes from './actiontypes';
import Api from './api';
@@ -19,8 +18,6 @@ const {
AUTH_HIDE_PASSWORD_MODAL,
} = ActionTypes;
-const nodesStore = ClusterStore.nodesStore;
-
const errorHandler = ({ message }) => {
FauxtonAPI.addNotification({
msg: message,
@@ -69,11 +66,10 @@ export const login = (username, password, urlBack) => {
.catch(errorHandler);
};
-export const changePassword = (username, password, passwordConfirm) => () => {
+export const changePassword = (username, password, passwordConfirm, nodes) =>
() => {
if (!validatePasswords(password, passwordConfirm)) {
return errorHandler({message:
app.i18n.en_US['auth-passwords-not-matching']});
}
- var nodes = nodesStore.getNodes();
//To change an admin's password is the same as creating an admin. So we just
use the
//same api function call here.
Api.createAdmin({
@@ -90,8 +86,8 @@ export const changePassword = (username, password,
passwordConfirm) => () => {
);
};
-export const createAdmin = (username, password, loginAfter) => () => {
- const node = nodesStore.getNodes()[0].node;
+export const createAdmin = (username, password, loginAfter, nodes) => () => {
+ const node = nodes[0].node;
if (!validateUser(username, password)) {
return errorHandler({message: app.i18n.en_US['auth-missing-credentials']});
}
diff --git a/app/addons/auth/components/changepasswordform.js
b/app/addons/auth/components/changepasswordform.js
index 7ca0b8fdd..14943d770 100644
--- a/app/addons/auth/components/changepasswordform.js
+++ b/app/addons/auth/components/changepasswordform.js
@@ -43,7 +43,7 @@ export class ChangePasswordForm extends React.Component {
changePassword(e) {
e.preventDefault();
- this.props.changePassword(this.props.username, this.state.password,
this.state.passwordConfirm);
+ this.props.changePassword(this.props.username, this.state.password,
this.state.passwordConfirm, this.props.nodes);
}
render() {
@@ -87,11 +87,14 @@ export class ChangePasswordForm extends React.Component {
}
}
+const mapStateToProps = ({clusters}) => {
+ return {
+ nodes: clusters.nodes,
+ username: FauxtonAPI.session.user().name
+ };
+};
+
export default connect(
- () => {
- return {
- username: FauxtonAPI.session.user().name
- };
- },
+ mapStateToProps,
{changePassword}
)(ChangePasswordForm);
diff --git a/app/addons/auth/components/createadminform.js
b/app/addons/auth/components/createadminform.js
index f92049b76..333b4195e 100644
--- a/app/addons/auth/components/createadminform.js
+++ b/app/addons/auth/components/createadminform.js
@@ -13,7 +13,6 @@
import PropTypes from 'prop-types';
import React from "react";
-import ReactDOM from "react-dom";
import {
createAdmin
} from "./../actions";
@@ -45,7 +44,8 @@ export class CreateAdminForm extends React.Component {
this.props.createAdmin(
this.state.username,
this.state.password,
- this.props.loginAfter
+ this.props.loginAfter,
+ this.props.nodes
);
}
@@ -111,8 +111,14 @@ CreateAdminForm.defaultProps = {
loginAfter: false
};
+const mapStateToProps = ({clusters}) => {
+ return {
+ nodes: clusters.nodes
+ };
+};
+
export default connect(
- null,
+ mapStateToProps,
{createAdmin}
)(CreateAdminForm);
diff --git a/app/addons/auth/routes/auth.js b/app/addons/auth/routes/auth.js
index d2ebd64ee..d274c089e 100644
--- a/app/addons/auth/routes/auth.js
+++ b/app/addons/auth/routes/auth.js
@@ -12,7 +12,7 @@
import React from "react";
import FauxtonAPI from "../../../core/api";
-import ClusterActions from "../../cluster/cluster.actions";
+import ClusterActions from "../../cluster/actions";
import { AuthLayout } from "./../layout";
import app from "../../../app";
import Components from "./../components";
diff --git a/app/addons/auth/routes/user.js b/app/addons/auth/routes/user.js
index 1934b4415..36a8f531f 100644
--- a/app/addons/auth/routes/user.js
+++ b/app/addons/auth/routes/user.js
@@ -12,7 +12,7 @@
import React from "react";
import FauxtonAPI from "../../../core/api";
-import ClusterActions from "../../cluster/cluster.actions";
+import ClusterActions from "../../cluster/actions";
import { AdminLayout } from "./../layout";
export default FauxtonAPI.RouteObject.extend({
diff --git a/app/addons/cluster/__tests__/cluster.test.js
b/app/addons/cluster/__tests__/cluster.test.js
index dd4e313c2..d215e9991 100644
--- a/app/addons/cluster/__tests__/cluster.test.js
+++ b/app/addons/cluster/__tests__/cluster.test.js
@@ -9,13 +9,13 @@
// 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 ClusterComponent from "../cluster";
-import ClusterActions from "../cluster.actions";
-import ClusterStores from "../cluster.stores";
+import FauxtonAPI from "../../../core/api";
+import {DisabledConfigController} from "../cluster";
import utils from "../../../../test/mocha/testUtils";
import React from "react";
import ReactDOM from "react-dom";
import {mount} from 'enzyme';
+import sinon from 'sinon';
const assert = utils.assert;
@@ -23,6 +23,7 @@ describe('Cluster Controller', () => {
let controller;
beforeEach(() => {
+ FauxtonAPI.reduxDispatch = sinon.stub();
var nodeList = [
{'node': '[email protected]', 'isInCluster': true},
@@ -33,16 +34,11 @@ describe('Cluster Controller', () => {
{'node': '[email protected]', 'isInCluster': false}
];
- ClusterActions.updateNodes({nodes: nodeList});
controller = mount(
- <ClusterComponent.DisabledConfigController />
+ <DisabledConfigController nodes={nodeList} />
);
});
- afterEach(() => {
- ClusterStores.nodesStore.reset();
- });
-
it('renders the amount of nodes', () => {
assert.ok(/6 nodes/.test(controller.text()), 'finds 6 nodes');
});
diff --git a/app/addons/cluster/__tests__/resources.test.js
b/app/addons/cluster/__tests__/resources.test.js
deleted file mode 100644
index 4dd3774a0..000000000
--- a/app/addons/cluster/__tests__/resources.test.js
+++ /dev/null
@@ -1,56 +0,0 @@
-// 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 testUtils from "../../../../test/mocha/testUtils";
-import Resources from "../resources";
-var assert = testUtils.assert;
-
-
-describe('Membership Model', () => {
- const data = {
- 'all_nodes': ['[email protected]', '[email protected]', '[email protected]',
'notpartofclusternode'],
- 'cluster_nodes': ['[email protected]', '[email protected]', '[email protected]']
- };
-
- it('reorders the data', () => {
- const memberships = new Resources.ClusterNodes();
- const res = memberships.parse(data);
-
- assert.deepEqual([
- {node: '[email protected]', isInCluster: true},
- {node: '[email protected]', isInCluster: true},
- {node: '[email protected]', isInCluster: true},
- {node: 'notpartofclusternode', isInCluster: false}
- ],
- res.nodes_mapped);
- });
-
- it('keeps the exiting data', () => {
- const memberships = new Resources.ClusterNodes();
- const res = memberships.parse(data);
-
- assert.deepEqual([
- '[email protected]',
- '[email protected]',
- '[email protected]',
- 'notpartofclusternode'
- ],
- res.all_nodes);
-
- assert.deepEqual([
- '[email protected]',
- '[email protected]',
- '[email protected]'
- ],
- res.cluster_nodes);
- });
-});
diff --git a/app/addons/cluster/cluster.actions.js
b/app/addons/cluster/actions.js
similarity index 52%
rename from app/addons/cluster/cluster.actions.js
rename to app/addons/cluster/actions.js
index b0ca2a591..60ec0c92e 100644
--- a/app/addons/cluster/cluster.actions.js
+++ b/app/addons/cluster/actions.js
@@ -11,38 +11,48 @@
// the License.
import FauxtonAPI from "../../core/api";
-import ClusterResources from "./resources";
-import ActionTypes from "./cluster.actiontypes";
+import getNodes from "./api";
+import ActionTypes from "./actiontypes";
export default {
- fetchNodes: function () {
- var memberships = new ClusterResources.ClusterNodes();
-
- memberships.fetch().then(() => {
+ fetchNodes () {
+ getNodes().then((nodes) => {
this.updateNodes({
- nodes: memberships.get('nodes_mapped')
+ nodes: nodes.nodes_mapped
+ });
+ })
+ .catch(err => {
+ FauxtonAPI.addNotification({
+ type: 'error',
+ msg: err.message,
+ clear: true
+ });
});
- });
},
- updateNodes: function (options) {
- FauxtonAPI.dispatch({
+ updateNodes (options) {
+ FauxtonAPI.reduxDispatch({
type: ActionTypes.CLUSTER_FETCH_NODES,
options: options
});
},
- navigateToNodeBasedOnNodeCount: function (successtarget) {
- var memberships = new ClusterResources.ClusterNodes();
+ navigateToNodeBasedOnNodeCount (successtarget) {
+ getNodes().then((nodes) => {
+ const allNodes = nodes.all_nodes;
- memberships.fetch().then(function () {
- const nodes = memberships.get('all_nodes');
-
- if (nodes.length === 1) {
- return FauxtonAPI.navigate(successtarget + nodes[0]);
+ if (allNodes.length === 1) {
+ return FauxtonAPI.navigate(successtarget + allNodes[0]);
}
return FauxtonAPI.navigate('/cluster/disabled', {trigger: true});
- });
+ })
+ .catch(err => {
+ FauxtonAPI.addNotification({
+ type: 'error',
+ msg: err.message,
+ clear: true
+ });
+ });
}
};
diff --git a/app/addons/cluster/cluster.actiontypes.js
b/app/addons/cluster/actiontypes.js
similarity index 100%
rename from app/addons/cluster/cluster.actiontypes.js
rename to app/addons/cluster/actiontypes.js
diff --git a/app/addons/cluster/resources.js b/app/addons/cluster/api.js
similarity index 51%
rename from app/addons/cluster/resources.js
rename to app/addons/cluster/api.js
index 95b7ccb11..a6c05a180 100644
--- a/app/addons/cluster/resources.js
+++ b/app/addons/cluster/api.js
@@ -10,29 +10,25 @@
// License for the specific language governing permissions and limitations
under
// the License.
-import FauxtonAPI from "../../core/api";
import Helpers from "../../helpers";
-
-var Cluster = FauxtonAPI.addon();
-
-Cluster.ClusterNodes = Backbone.Model.extend({
- url: function () {
- return Helpers.getServerUrl('/_membership');
- },
-
- parse: function (res) {
- var list;
-
- list = res.all_nodes.reduce(function (acc, node) {
- var isInCluster = res.cluster_nodes.indexOf(node) !== -1;
-
- acc.push({node: node, isInCluster: isInCluster});
- return acc;
- }, []);
-
- res.nodes_mapped = list;
- return res;
- }
-});
-
-export default Cluster;
+import {get} from '../../core/ajax';
+
+export default () => {
+ return get(Helpers.getServerUrl('/_membership'))
+ .then(res => {
+ if (!res.all_nodes) {
+ const details = res.reason ? res.reason : '';
+ throw new Error('Failed to load list of nodes.' + details);
+ }
+
+ const list = res.all_nodes.reduce(function (acc, node) {
+ var isInCluster = res.cluster_nodes.indexOf(node) !== -1;
+
+ acc.push({node: node, isInCluster: isInCluster});
+ return acc;
+ }, []);
+
+ res.nodes_mapped = list;
+ return res;
+ });
+};
diff --git a/app/addons/cluster/base.js b/app/addons/cluster/base.js
index 338334dbb..d61ca990b 100644
--- a/app/addons/cluster/base.js
+++ b/app/addons/cluster/base.js
@@ -11,7 +11,13 @@
// the License.
import Cluster from "./routes";
+import FauxtonAPI from "../../core/api";
+import reducers from './reducers';
Cluster.initialize = function () {};
+FauxtonAPI.addReducers({
+ clusters: reducers
+});
+
export default Cluster;
diff --git a/app/addons/cluster/cluster.js b/app/addons/cluster/cluster.js
index 625e86678..573e37a6d 100644
--- a/app/addons/cluster/cluster.js
+++ b/app/addons/cluster/cluster.js
@@ -11,32 +11,9 @@
// the License.
import React from "react";
-import ClusterStore from "./cluster.stores";
-
-var nodesStore = ClusterStore.nodesStore;
-
-
-class DisabledConfigController extends React.Component {
- getStoreState = () => {
- return {
- nodes: nodesStore.getNodes()
- };
- };
-
- componentDidMount() {
- nodesStore.on('change', this.onChange, this);
- }
-
- componentWillUnmount() {
- nodesStore.off('change', this.onChange);
- }
-
- onChange = () => {
- this.setState(this.getStoreState());
- };
-
- state = this.getStoreState();
+import {connect} from 'react-redux';
+export class DisabledConfigController extends React.Component {
render() {
return (
<div className="config-warning-cluster-wrapper">
@@ -45,7 +22,7 @@ class DisabledConfigController extends React.Component {
<div className="config-warning-icon-container pull-left">
<i className="fonticon-attention-circled"></i>
</div>
- It seems that you are running a cluster with
{this.state.nodes.length} nodes. For CouchDB 2.0
+ It seems that you are running a cluster with
{this.props.nodes.length} nodes. For CouchDB 2.0
we recommend using a configuration management tools like Chef,
Ansible,
Puppet or Salt (in no particular order) to configure your nodes in
a cluster.
<br/>
@@ -61,8 +38,13 @@ class DisabledConfigController extends React.Component {
}
}
-var Views = {
- DisabledConfigController: DisabledConfigController
+const mapStateToProps = ({clusters}) => {
+ return {
+ nodes: clusters.nodes
+ };
};
-export default Views;
+export default connect(
+ mapStateToProps,
+ null,
+)(DisabledConfigController);
diff --git a/app/addons/cluster/cluster.stores.js
b/app/addons/cluster/cluster.stores.js
deleted file mode 100644
index 27d4557e4..000000000
--- a/app/addons/cluster/cluster.stores.js
+++ /dev/null
@@ -1,57 +0,0 @@
-// 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 FauxtonAPI from "../../core/api";
-import ActionTypes from "./cluster.actiontypes";
-
-var NodesStore = FauxtonAPI.Store.extend({
-
- initialize: function () {
- this.reset();
- },
-
- reset: function () {
- this._nodes = [];
- },
-
- setNodes: function (options) {
- this._nodes = options.nodes;
- },
-
- getNodes: function () {
- return this._nodes;
- },
-
- dispatch: function (action) {
-
- switch (action.type) {
- case ActionTypes.CLUSTER_FETCH_NODES:
- this.setNodes(action.options);
- break;
-
- default:
- return;
- }
-
- this.triggerChange();
- }
-
-});
-
-
-var nodesStore = new NodesStore();
-
-nodesStore.dispatchToken =
FauxtonAPI.dispatcher.register(nodesStore.dispatch.bind(nodesStore));
-
-export default {
- nodesStore: nodesStore
-};
diff --git a/app/addons/cluster/reducers.js b/app/addons/cluster/reducers.js
new file mode 100644
index 000000000..8a48c7a94
--- /dev/null
+++ b/app/addons/cluster/reducers.js
@@ -0,0 +1,29 @@
+// 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 ActionTypes from "./actiontypes";
+
+const initialState = {
+ nodes: []
+};
+
+export default (state = initialState, {type, options}) => {
+ switch (type) {
+ case ActionTypes.CLUSTER_FETCH_NODES:
+ return {
+ ...state,
+ nodes: options.nodes
+ };
+ }
+
+ return state;
+};
diff --git a/app/addons/cluster/routes.js b/app/addons/cluster/routes.js
index 339360f7d..37ed665c4 100644
--- a/app/addons/cluster/routes.js
+++ b/app/addons/cluster/routes.js
@@ -12,9 +12,9 @@
import React from 'react';
import FauxtonAPI from "../../core/api";
-import Cluster from "./resources";
-import ClusterComponents from "./cluster";
-import ClusterActions from "./cluster.actions";
+import Helpers from "../../helpers";
+import DisabledConfigController from "./cluster";
+import ClusterActions from "./actions";
import {OnePaneSimpleLayout} from '../components/layouts';
@@ -25,17 +25,12 @@ var ConfigDisabledRouteObject =
FauxtonAPI.RouteObject.extend({
'cluster/disabled': 'showDisabledFeatureScreen'
},
- apiUrl: function () {
- return [this.memberships.url('apiurl'), this.memberships.documentation];
- },
-
showDisabledFeatureScreen: function () {
- const memberships = new Cluster.ClusterNodes();
ClusterActions.fetchNodes();
return <OnePaneSimpleLayout
- component={<ClusterComponents.DisabledConfigController/>}
- endpoint={memberships.url('apiurl')}
- docURL={memberships.documentation}
+ component={<DisabledConfigController/>}
+ endpoint={Helpers.getServerUrl('/_membership')}
+ docURL={FauxtonAPI.constants.DOC_URLS.MEMBERSHIP}
crumbs={[
{ name: 'Config disabled' }
]}
@@ -43,6 +38,6 @@ var ConfigDisabledRouteObject =
FauxtonAPI.RouteObject.extend({
}
});
-Cluster.RouteObjects = [ConfigDisabledRouteObject];
-
-export default Cluster;
+export default {
+ RouteObjects: [ConfigDisabledRouteObject]
+};
diff --git a/app/addons/config/routes.js b/app/addons/config/routes.js
index 4b924d435..4a21749cf 100644
--- a/app/addons/config/routes.js
+++ b/app/addons/config/routes.js
@@ -13,7 +13,7 @@
import React from 'react';
import FauxtonAPI from "../../core/api";
import Config from "./resources";
-import ClusterActions from "../cluster/cluster.actions";
+import ClusterActions from "../cluster/actions";
import ConfigActions from "./actions";
import Layout from './layout';
diff --git a/app/addons/setup/route.js b/app/addons/setup/route.js
index 945f3e4a2..38752a13c 100644
--- a/app/addons/setup/route.js
+++ b/app/addons/setup/route.js
@@ -13,7 +13,7 @@
import React from 'react';
import app from "../../app";
import FauxtonAPI from "../../core/api";
-import ClusterActions from "../cluster/cluster.actions";
+import ClusterActions from "../cluster/actions";
import {OnePaneSimpleLayout} from '../components/layouts';
import ConfiguredScreenContainer from './container/ConfiguredSceenContainer';
diff --git a/app/constants.js b/app/constants.js
index 66fdc2e96..7f0264065 100644
--- a/app/constants.js
+++ b/app/constants.js
@@ -46,6 +46,7 @@ export default {
MANGO_INDEX:'./docs/intro/api.html#documents',
MANGO_SEARCH:'./docs/intro/api.html#documents',
CHANGES:'./docs/api/database/changes.html?highlight=changes#post--db-_changes',
- SETUP: './docs/cluster/setup.html#the-cluster-setup-wizard'
+ SETUP: './docs/cluster/setup.html#the-cluster-setup-wizard',
+ MEMBERSHIP: './docs/cluster/nodes.html'
}
};
----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on GitHub and use the
URL above to go to the specific comment.
For queries about this service, please contact Infrastructure at:
[email protected]
With regards,
Apache Git Services