You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@couchdb.apache.org by GitBox <gi...@apache.org> on 2018/09/24 17:24:42 UTC

[GitHub] Antonio-Maranhao closed pull request #1125: [partitioned-dbs] Support create and list databases

Antonio-Maranhao closed pull request #1125: [partitioned-dbs] Support create and list databases
URL: https://github.com/apache/couchdb-fauxton/pull/1125
 
 
   

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/databases/__tests__/components.test.js b/app/addons/databases/__tests__/components.test.js
index 1c0bc5bd7..e2e3a5e83 100644
--- a/app/addons/databases/__tests__/components.test.js
+++ b/app/addons/databases/__tests__/components.test.js
@@ -10,7 +10,6 @@
 // License for the specific language governing permissions and limitations under
 // the License.
 import FauxtonAPI from "../../../core/api";
-import Views from "../components";
 import Actions from "../actions";
 import Stores from "../stores";
 import utils from "../../../../test/mocha/testUtils";
@@ -18,37 +17,35 @@ import React from "react";
 import ReactDOM from "react-dom";
 import { mount } from 'enzyme';
 import sinon from 'sinon';
+import Views from "../components";
 
 const assert = utils.assert;
 
 const store = Stores.databasesStore;
 
 describe('AddDatabaseWidget', () => {
-  let oldCreateNewDatabase;
-  let createCalled, passedDbName;
 
   beforeEach(() => {
-    oldCreateNewDatabase = Actions.createNewDatabase;
-    Actions.createNewDatabase = function (dbName) {
-      createCalled = true;
-      passedDbName = dbName;
-    };
+    sinon.stub(Actions, 'createNewDatabase');
   });
 
   afterEach(() => {
-    Actions.createNewDatabase = oldCreateNewDatabase;
+    Actions.createNewDatabase.restore();
   });
 
-  it("Creates a database with given name", () => {
-    createCalled = false;
-    passedDbName = null;
+  it("creates a database with given name", () => {
     const el = mount(<Views.AddDatabaseWidget />);
     el.setState({databaseName: 'my-db'});
     el.instance().onAddDatabase();
-    assert.equal(true, createCalled);
-    assert.equal("my-db", passedDbName);
+    sinon.assert.calledWith(Actions.createNewDatabase, 'my-db');
   });
 
+  it('creates a partitioned database', () => {
+    const el = mount(<Views.AddDatabaseWidget showPartitionedOption={true}/>);
+    el.setState({databaseName: 'my-db', partitionedSelected: true});
+    el.instance().onAddDatabase();
+    sinon.assert.calledWith(Actions.createNewDatabase, 'my-db', true);
+  });
 });
 
 
@@ -123,7 +120,7 @@ describe('DatabaseTable', () => {
     FauxtonAPI.registerExtension('DatabaseTable:head', ColHeader3);
 
     var table = mount(
-      <Views.DatabaseTable showDeleteDatabaseModal={{showModal: false}} loading={false} dbList={[]} />
+      <Views.DatabaseTable showDeleteDatabaseModal={{showModal: false}} loading={false} dbList={[]} showPartitionedColumn={false}/>
     );
     var cols = table.find('th');
 
@@ -150,7 +147,7 @@ describe('DatabaseTable', () => {
     const list = store.getDbList();
 
     var databaseRow = mount(
-      <Views.DatabaseTable showDeleteDatabaseModal={{showModal: false}} dbList={list} loading={false}/>
+      <Views.DatabaseTable showDeleteDatabaseModal={{showModal: false}} dbList={list} loading={false} showPartitionedColumn={false}/>
     );
     var links = databaseRow.find('td');
 
@@ -173,7 +170,7 @@ describe('DatabaseTable', () => {
     const list = store.getDbList();
 
     var databaseRow = mount(
-      <Views.DatabaseTable showDeleteDatabaseModal={{showModal: false}} dbList={list} loading={false} />
+      <Views.DatabaseTable showDeleteDatabaseModal={{showModal: false}} dbList={list} loading={false} showPartitionedColumn={false}/>
     );
     assert.equal(databaseRow.find('.database-load-fail').length, 1);
   });
@@ -189,9 +186,55 @@ describe('DatabaseTable', () => {
     const list = store.getDbList();
 
     var databaseRow = mount(
-      <Views.DatabaseTable showDeleteDatabaseModal={{showModal: false}} dbList={list} loading={false} />
+      <Views.DatabaseTable showDeleteDatabaseModal={{showModal: false}} dbList={list} loading={false} showPartitionedColumn={false}/>
     );
 
     assert.equal(databaseRow.find('.database-load-fail').length, 0);
   });
+
+  it('shows Partitioned column only when prop is set to true', () => {
+    Actions.updateDatabases({
+      dbList: ['db1'],
+      databaseDetails: [{db_name: 'db1', doc_count: 0, doc_del_count: 0, props: {partitioned: true}}],
+      failedDbs: [],
+      fullDbList: ['db1']
+    });
+
+    const list = store.getDbList();
+
+    const withPartColumn = mount(
+      <Views.DatabaseTable showDeleteDatabaseModal={{showModal: false}} dbList={list} loading={false} showPartitionedColumn={true}/>
+    );
+    const colHeaders = withPartColumn.find('th');
+    assert.equal(colHeaders.length, 5);
+    assert.equal(colHeaders.get(3).props.children, 'Partitioned');
+
+    const withoutPartColumn = mount(
+      <Views.DatabaseTable showDeleteDatabaseModal={{showModal: false}} dbList={list} loading={false} showPartitionedColumn={false}/>
+    );
+    assert.equal(withoutPartColumn.find('th').length, 4);
+  });
+
+  it('shows correct values in the Partitioned column', () => {
+    Actions.updateDatabases({
+      dbList: ['db1', 'db2'],
+      databaseDetails: [
+        {db_name: 'db1', doc_count: 1, doc_del_count: 0, props: {partitioned: true}},
+        {db_name: 'db2', doc_count: 2, doc_del_count: 0, props: {partitioned: false}}
+      ],
+      failedDbs: [],
+      fullDbList: ['db1', 'db2']
+    });
+
+    const list = store.getDbList();
+
+    const dbTable = mount(
+      <Views.DatabaseTable showDeleteDatabaseModal={{showModal: false}} dbList={list} loading={false} showPartitionedColumn={true}/>
+    );
+    const colCells = dbTable.find('td');
+    // 2 rows with 5 cells each
+    assert.equal(colCells.length, 10);
+    assert.equal(colCells.get(3).props.children, 'Yes');
+    assert.equal(colCells.get(8).props.children, 'No');
+  });
 });
diff --git a/app/addons/databases/__tests__/databasepagination.test.js b/app/addons/databases/__tests__/databasepagination.test.js
index 6ec9f59ae..27776803a 100644
--- a/app/addons/databases/__tests__/databasepagination.test.js
+++ b/app/addons/databases/__tests__/databasepagination.test.js
@@ -13,10 +13,10 @@
 import Stores from "../stores";
 import React from 'react';
 import ReactDOM from 'react-dom';
-import DatabaseComponents from "../components";
 import "../../documents/base";
 import DatabaseActions from "../actions";
 import {mount} from 'enzyme';
+import DatabaseComponents from "../components";
 
 const store = Stores.databasesStore;
 
diff --git a/app/addons/databases/actions.js b/app/addons/databases/actions.js
index 5e2178178..6b1c57040 100644
--- a/app/addons/databases/actions.js
+++ b/app/addons/databases/actions.js
@@ -13,6 +13,7 @@ import app from "../../app";
 import Helpers from "../../helpers";
 import FauxtonAPI from "../../core/api";
 import { get } from "../../core/ajax";
+import DatabasesBase from '../databases/base';
 import Stores from "./stores";
 import ActionTypes from "./actiontypes";
 import Resources from "./resources";
@@ -126,7 +127,7 @@ export default {
     });
   },
 
-  createNewDatabase: function (databaseName) {
+  createNewDatabase: function (databaseName, partitioned) {
     if (_.isNull(databaseName) || databaseName.trim().length === 0) {
       FauxtonAPI.addNotification({
         msg: 'Please enter a valid database name',
@@ -144,7 +145,7 @@ export default {
       }
     });
 
-    var db = Stores.databasesStore.obtainNewDatabaseModel(databaseName);
+    const db = Stores.databasesStore.obtainNewDatabaseModel(databaseName, partitioned);
     FauxtonAPI.addNotification({ msg: 'Creating database.' });
     db.save().done(function () {
       FauxtonAPI.addNotification({
@@ -152,11 +153,11 @@ export default {
         type: 'success',
         clear: true
       });
-      var route = FauxtonAPI.urls('allDocs', 'app', app.utils.safeURLName(databaseName), '?limit=' + Resources.DocLimit);
+      const route = FauxtonAPI.urls('allDocs', 'app', app.utils.safeURLName(databaseName), '?limit=' + Resources.DocLimit);
       app.router.navigate(route, { trigger: true });
     }
     ).fail(function (xhr) {
-      var responseText = JSON.parse(xhr.responseText).reason;
+      const responseText = JSON.parse(xhr.responseText).reason;
       FauxtonAPI.addNotification({
         msg: 'Create database failed: ' + responseText,
         type: 'error',
@@ -195,5 +196,27 @@ export default {
       });
       callback(null, { options: options });
     });
+  },
+
+  setPartitionedDatabasesAvailable(available) {
+    FauxtonAPI.dispatch({
+      type: ActionTypes.DATABASES_PARTITIONED_DB_AVAILABLE,
+      options: {
+        available
+      }
+    });
+  },
+
+  checkPartitionedQueriesIsAvailable() {
+    const exts = FauxtonAPI.getExtensions(DatabasesBase.PARTITONED_DB_CHECK_EXTENSION);
+    let promises = exts.map(checkFunction => {
+      return checkFunction();
+    });
+    FauxtonAPI.Promise.all(promises).then(results => {
+      const isAvailable = results.every(check => check === true);
+      this.setPartitionedDatabasesAvailable(isAvailable);
+    }).catch(() => {
+      // ignore as the default is false
+    });
   }
 };
diff --git a/app/addons/databases/actiontypes.js b/app/addons/databases/actiontypes.js
index e9665c01f..e8d3a534d 100644
--- a/app/addons/databases/actiontypes.js
+++ b/app/addons/databases/actiontypes.js
@@ -14,6 +14,6 @@ export default {
   DATABASES_SET_PROMPT_VISIBLE: 'DATABASES_SET_PROMPT_VISIBLE',
   DATABASES_STARTLOADING: 'DATABASES_STARTLOADING',
   DATABASES_LOADCOMPLETE: 'DATABASES_LOADCOMPLETE',
-
-  DATABASES_UPDATE: 'DATABASES_UPDATE'
+  DATABASES_UPDATE: 'DATABASES_UPDATE',
+  DATABASES_PARTITIONED_DB_AVAILABLE: 'DATABASES_PARTITIONED_DB_AVAILABLE'
 };
diff --git a/app/addons/databases/base.js b/app/addons/databases/base.js
index 72d50e371..92f2e75a5 100644
--- a/app/addons/databases/base.js
+++ b/app/addons/databases/base.js
@@ -12,8 +12,10 @@
 
 import app from "../../app";
 import Helpers from "../../helpers";
+import { get } from "../../core/ajax";
 import FauxtonAPI from "../../core/api";
 import Databases from "./routes";
+import Actions from "./actions";
 import "./assets/less/databases.less";
 
 Databases.initialize = function () {
@@ -23,8 +25,26 @@ Databases.initialize = function () {
     icon: "fonticon-database",
     className: 'databases'
   });
+  Actions.checkPartitionedQueriesIsAvailable();
 };
 
+function checkPartitionedDatabaseFeature () {
+  // Checks if the CouchDB server supports Partitioned Databases
+  return get(Helpers.getServerUrl("/")).then((couchdb) => {
+    //TODO: needs to be updated with the correct feature name
+    return couchdb.features && couchdb.features.includes('partitioned-dbs');
+  }).catch(() => {
+    return false;
+  });
+}
+
+// This extension can be used by addons to add extra checks when
+// deciding if the partitioned database feature should be enabled.
+// The registered element should be a function that returns a
+// Promise resolving to either true or false.
+Databases.PARTITONED_DB_CHECK_EXTENSION = 'Databases:PartitionedDbCheck';
+FauxtonAPI.registerExtension(Databases.PARTITONED_DB_CHECK_EXTENSION, checkPartitionedDatabaseFeature);
+
 // Utility functions
 Databases.databaseUrl = function (database) {
   var name = _.isObject(database) ? database.id : database,
diff --git a/app/addons/databases/components.js b/app/addons/databases/components.js
index b3a69ed2a..203ea494c 100644
--- a/app/addons/databases/components.js
+++ b/app/addons/databases/components.js
@@ -14,7 +14,7 @@ import FauxtonAPI from "../../core/api";
 
 import PropTypes from 'prop-types';
 
-import React from "react";
+import React, { Fragment } from "react";
 import ReactDOM from "react-dom";
 import Components from "../components/react-components";
 import ComponentsStore from "../components/stores";
@@ -36,7 +36,8 @@ class DatabasesController extends React.Component {
     return {
       dbList: databasesStore.getDbList(),
       loading: databasesStore.isLoading(),
-      showDeleteDatabaseModal: deleteDbModalStore.getShowDeleteDatabaseModal()
+      showDeleteDatabaseModal: deleteDbModalStore.getShowDeleteDatabaseModal(),
+      showPartitionedColumn: databasesStore.isPartitionedDatabasesAvailable()
     };
   };
 
@@ -63,7 +64,8 @@ class DatabasesController extends React.Component {
       <DatabaseTable
         showDeleteDatabaseModal={this.state.showDeleteDatabaseModal}
         dbList={dbList}
-        loading={loading} />
+        loading={loading}
+        showPartitionedColumn={this.state.showPartitionedColumn} />
     );
   }
 }
@@ -73,12 +75,13 @@ class DatabaseTable extends React.Component {
     dbList: PropTypes.array.isRequired,
     showDeleteDatabaseModal: PropTypes.object.isRequired,
     loading: PropTypes.bool.isRequired,
+    showPartitionedColumn: PropTypes.bool.isRequired
   };
 
   createRows = (dbList) => {
     return dbList.map((item, k) => {
       return (
-        <DatabaseRow item={item} key={k} />
+        <DatabaseRow item={item} key={k} showPartitionedColumn={this.props.showPartitionedColumn}/>
       );
     });
   };
@@ -117,6 +120,7 @@ class DatabaseTable extends React.Component {
               <th>Name</th>
               <th>Size</th>
               <th># of Docs</th>
+              {this.props.showPartitionedColumn ? (<th>Partitioned</th>) : null}
               {this.getExtensionColumns()}
               <th>Actions</th>
             </tr>
@@ -132,7 +136,18 @@ class DatabaseTable extends React.Component {
 
 class DatabaseRow extends React.Component {
   static propTypes = {
-    row: PropTypes.object
+    item: PropTypes.shape({
+      id: PropTypes.string.isRequired,
+      encodedId: PropTypes.string.isRequired,
+      url: PropTypes.string.isRequired,
+      failed: PropTypes.bool.isRequired,
+      dataSize: PropTypes.string,
+      docCount: PropTypes.number,
+      docDelCount: PropTypes.number,
+      isPartitioned: PropTypes.bool,
+      showTombstoneWarning: PropTypes.bool
+    }).isRequired,
+    showPartitionedColumn: PropTypes.bool.isRequired
   };
 
   getExtensionColumns = (row) => {
@@ -151,7 +166,7 @@ class DatabaseRow extends React.Component {
       item
     } = this.props;
 
-    const {encodedId, id, url, dataSize, docCount, docDelCount, showTombstoneWarning, failed } = item;
+    const {encodedId, id, url, dataSize, docCount, docDelCount, showTombstoneWarning, failed, isPartitioned } = item;
     const tombStoneWarning = showTombstoneWarning ?
       (<GraveyardInfo docCount={docCount} docDelCount={docDelCount} />) : null;
 
@@ -164,7 +179,9 @@ class DatabaseRow extends React.Component {
         </tr>
       );
     }
-
+    const partitionedCol = this.props.showPartitionedColumn ?
+      (<td>{isPartitioned ? 'Yes' : 'No'}</td>) :
+      null;
     return (
       <tr>
         <td>
@@ -172,6 +189,7 @@ class DatabaseRow extends React.Component {
         </td>
         <td>{dataSize}</td>
         <td>{docCount} {tombStoneWarning}</td>
+        {partitionedCol}
         {this.getExtensionColumns(item)}
 
         <td className="database-actions">
@@ -202,46 +220,123 @@ const GraveyardInfo = ({docCount, docDelCount}) => {
   );
 };
 
-const RightDatabasesHeader = () => {
-  return (
-    <div className="header-right right-db-header flex-layout flex-row">
-      <JumpToDatabaseWidget loadOptions={Actions.fetchAllDbsWithKey} />
-      <AddDatabaseWidget />
-    </div>
-  );
-};
+class RightDatabasesHeader extends React.Component {
+
+  constructor(props) {
+    super(props);
+    this.state = this.getStoreState();
+  }
+
+  getStoreState () {
+    return {
+      showPartitionedOption: databasesStore.isPartitionedDatabasesAvailable()
+    };
+  }
+
+  componentDidMount() {
+    databasesStore.on('change', this.onChange, this);
+  }
+
+  componentWillUnmount() {
+    databasesStore.off('change', this.onChange, this);
+  }
+
+  onChange () {
+    this.setState(this.getStoreState());
+  }
+
+  render() {
+    return (
+      <div className="header-right right-db-header flex-layout flex-row">
+        <JumpToDatabaseWidget loadOptions={Actions.fetchAllDbsWithKey} />
+        <AddDatabaseWidget showPartitionedOption={this.state.showPartitionedOption}/>
+      </div>
+    );
+  }
+}
 
 class AddDatabaseWidget extends React.Component {
-  state = {
-    isPromptVisible: false,
-    databaseName: ''
+  static defaultProps = {
+    showPartitionedOption: false
   };
 
-  onTrayToggle = () => {
-    this.setState({isPromptVisible: !this.state.isPromptVisible});
+  static propTypes = {
+    showPartitionedOption: PropTypes.bool.isRequired
   };
 
-  closeTray = () => {
+  constructor(props) {
+    super(props);
+    this.state = {
+      isPromptVisible: false,
+      databaseName: '',
+      partitionedSelected: false
+    };
+
+    this.onTrayToggle = this.onTrayToggle.bind(this);
+    this.closeTray = this.closeTray.bind(this);
+    this.focusInput = this.focusInput.bind(this);
+    this.onKeyUpInInput = this.onKeyUpInInput.bind(this);
+    this.onChange = this.onChange.bind(this);
+    this.onAddDatabase = this.onAddDatabase.bind(this);
+    this.onTogglePartitioned = this.onTogglePartitioned.bind(this);
+  }
+
+  onTrayToggle () {
+    this.setState({isPromptVisible: !this.state.isPromptVisible});
+  }
+
+  closeTray () {
     this.setState({isPromptVisible: false});
-  };
+  }
 
-  focusInput = () => {
+  focusInput () {
     this.newDbName.focus();
-  };
+  }
 
-  onKeyUpInInput = (e) => {
+  onKeyUpInInput (e) {
     if (e.which === 13) {
       this.onAddDatabase();
     }
-  };
+  }
 
-  onChange = (e) => {
+  onChange (e) {
     this.setState({databaseName: e.target.value});
-  };
+  }
 
-  onAddDatabase = () => {
-    Actions.createNewDatabase(this.state.databaseName);
-  };
+  onAddDatabase () {
+    const partitioned = this.props.showPartitionedOption ?
+      this.state.partitionedSelected :
+      undefined;
+
+    Actions.createNewDatabase(
+      this.state.databaseName,
+      partitioned
+    );
+  }
+
+  onTogglePartitioned() {
+    this.setState({ partitionedSelected: !this.state.partitionedSelected });
+  }
+
+  partitionedCheckobx() {
+    if (!this.props.showPartitionedOption) {
+      return null;
+    }
+    return (
+      <Fragment>
+        <br/>
+        <label style={{margin: '10px 10px 0px 0px'}}>
+          <input
+            id="js-partitioned-db"
+            type="checkbox"
+            checked={this.state.partitionedSelected}
+            onChange={this.onTogglePartitioned}
+            style={{margin: '0px 10px 0px 0px'}} />
+          Partitioned
+        </label>
+      </Fragment>
+    );
+  }
 
   render() {
     return (
@@ -265,6 +360,7 @@ class AddDatabaseWidget extends React.Component {
             placeholder="Name of database"
           />
           <a className="btn" id="js-create-database" onClick={this.onAddDatabase}>Create</a>
+          { this.partitionedCheckobx() }
         </TrayContents>
       </div>
     );
diff --git a/app/addons/databases/resources.js b/app/addons/databases/resources.js
index ae379c44d..468532297 100644
--- a/app/addons/databases/resources.js
+++ b/app/addons/databases/resources.js
@@ -20,6 +20,12 @@ Databases.DocLimit = 100;
 
 Databases.Model = FauxtonAPI.Model.extend({
 
+  partitioned: false,
+
+  setPartitioned: function (partitioned) {
+    this.partitioned = partitioned;
+  },
+
   documentation: function () {
     return FauxtonAPI.constants.DOC_URLS.ALL_DBS;
   },
@@ -56,6 +62,9 @@ Databases.Model = FauxtonAPI.Model.extend({
     } else if (context === "app") {
       return "/database/" + this.safeID();
     }
+    if (this.partitioned) {
+      return Helpers.getServerUrl("/" + this.safeID()) + '?partitioned=true';
+    }
     return Helpers.getServerUrl("/" + this.safeID());
 
   },
diff --git a/app/addons/databases/stores.js b/app/addons/databases/stores.js
index 68846699a..00befb09b 100644
--- a/app/addons/databases/stores.js
+++ b/app/addons/databases/stores.js
@@ -32,6 +32,8 @@ const DatabasesStoreConstructor = FauxtonAPI.Store.extend({
     this._databaseDetails = [];
     this._failedDbs = [];
     this._fullDbList = [];
+
+    this._partitionedDatabasesAvailable = false;
   },
 
   getPage: function () {
@@ -54,11 +56,13 @@ const DatabasesStoreConstructor = FauxtonAPI.Store.extend({
     this._promptVisible = promptVisible;
   },
 
-  obtainNewDatabaseModel: function (databaseName) {
-    return new Database({
+  obtainNewDatabaseModel: function (databaseName, partitioned) {
+    const dbModel = new Database({
       id: databaseName,
       name: databaseName
     });
+    dbModel.setPartitioned(partitioned);
+    return dbModel;
   },
 
   doesDatabaseExist: function (databaseName) {
@@ -95,10 +99,15 @@ const DatabasesStoreConstructor = FauxtonAPI.Store.extend({
       dataSize: Helpers.formatSize(dataSize),
       docCount: details.doc_count,
       docDelCount: details.doc_del_count,
-      showTombstoneWarning: details.doc_del_count > details.doc_count
+      showTombstoneWarning: details.doc_del_count > details.doc_count,
+      isPartitioned: details.props && details.props.partitioned === true
     };
   },
 
+  isPartitionedDatabasesAvailable: function () {
+    return this._partitionedDatabasesAvailable;
+  },
+
   dispatch: function (action) {
     switch (action.type) {
       case ActionTypes.DATABASES_SETPAGE:
@@ -125,6 +134,10 @@ const DatabasesStoreConstructor = FauxtonAPI.Store.extend({
         this.setLoading(false);
         break;
 
+      case ActionTypes.DATABASES_PARTITIONED_DB_AVAILABLE:
+        this._partitionedDatabasesAvailable = action.options.available;
+        break;
+
       default:
         return;
     }


 

----------------------------------------------------------------
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:
users@infra.apache.org


With regards,
Apache Git Services