You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@couchdb.apache.org by am...@apache.org on 2018/10/10 14:39:04 UTC

[couchdb-fauxton] branch master updated: update cluster module to redux (#1133)

This is an automated email from the ASF dual-hosted git repository.

amaranhao pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/couchdb-fauxton.git


The following commit(s) were added to refs/heads/master by this push:
     new 05dc83a  update cluster module to redux (#1133)
05dc83a is described below

commit 05dc83a0c08fefad650d0fa7da36fdff529fa74f
Author: garren smith <ga...@gmail.com>
AuthorDate: Wed Oct 10 16:38:58 2018 +0200

    update cluster module to redux (#1133)
    
    * update cluster module to redux
    
    * add error messages
---
 app/addons/auth/actions.js                         | 10 ++--
 app/addons/auth/components/changepasswordform.js   | 15 +++---
 app/addons/auth/components/createadminform.js      | 12 +++--
 app/addons/auth/routes/auth.js                     |  2 +-
 app/addons/auth/routes/user.js                     |  2 +-
 app/addons/cluster/__tests__/cluster.test.js       | 14 ++----
 app/addons/cluster/__tests__/resources.test.js     | 56 ---------------------
 .../cluster/{cluster.actions.js => actions.js}     | 46 ++++++++++-------
 .../{cluster.actiontypes.js => actiontypes.js}     |  0
 app/addons/cluster/{resources.js => api.js}        | 46 ++++++++---------
 app/addons/cluster/base.js                         |  6 +++
 app/addons/cluster/cluster.js                      | 40 +++++----------
 app/addons/cluster/cluster.stores.js               | 57 ----------------------
 .../{cluster.actiontypes.js => reducers.js}        | 18 ++++++-
 app/addons/cluster/routes.js                       | 23 ++++-----
 app/addons/config/routes.js                        |  2 +-
 app/addons/setup/route.js                          |  2 +-
 app/constants.js                                   |  3 +-
 18 files changed, 123 insertions(+), 231 deletions(-)

diff --git a/app/addons/auth/actions.js b/app/addons/auth/actions.js
index a58cd09..1e094c9 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 7ca0b8f..14943d7 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 f92049b..333b419 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 d2ebd64..d274c08 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 1934b44..36a8f53 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 dd4e313..d215e99 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': 'node1@127.0.0.1', 'isInCluster': true},
@@ -33,16 +34,11 @@ describe('Cluster Controller', () => {
       {'node': 'node3@127.0.0.1', '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 4dd3774..0000000
--- 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': ['node1@127.0.0.1', 'node2@127.0.0.1', 'node3@127.0.0.1', 'notpartofclusternode'],
-    'cluster_nodes': ['node1@127.0.0.1', 'node2@127.0.0.1', 'node3@127.0.0.1']
-  };
-
-  it('reorders the data', () => {
-    const memberships = new Resources.ClusterNodes();
-    const res = memberships.parse(data);
-
-    assert.deepEqual([
-      {node: 'node1@127.0.0.1', isInCluster: true},
-      {node: 'node2@127.0.0.1', isInCluster: true},
-      {node: 'node3@127.0.0.1', 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([
-      'node1@127.0.0.1',
-      'node2@127.0.0.1',
-      'node3@127.0.0.1',
-      'notpartofclusternode'
-    ],
-    res.all_nodes);
-
-    assert.deepEqual([
-      'node1@127.0.0.1',
-      'node2@127.0.0.1',
-      'node3@127.0.0.1'
-    ],
-    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 b0ca2a5..60ec0c9 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%
copy from app/addons/cluster/cluster.actiontypes.js
copy 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 95b7ccb..a6c05a1 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 338334d..d61ca99 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 625e866..573e37a 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 27d4557..0000000
--- 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/cluster.actiontypes.js b/app/addons/cluster/reducers.js
similarity index 65%
rename from app/addons/cluster/cluster.actiontypes.js
rename to app/addons/cluster/reducers.js
index 525e7f6..8a48c7a 100644
--- a/app/addons/cluster/cluster.actiontypes.js
+++ b/app/addons/cluster/reducers.js
@@ -10,6 +10,20 @@
 // License for the specific language governing permissions and limitations under
 // the License.
 
-export default {
-  CLUSTER_FETCH_NODES: 'CLUSTER_FETCH_NODES'
+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 339360f..37ed665 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 4b924d4..4a21749 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 945f3e4..38752a1 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 66fdc2e..7f02640 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'
   }
 };