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/11/15 17:50:32 UTC

[GitHub] Antonio-Maranhao closed pull request #1155: [partitioned dbs] Support partitioned mango queries and creating partitioned indexes

Antonio-Maranhao closed pull request #1155: [partitioned dbs] Support partitioned mango queries and creating partitioned indexes
URL: https://github.com/apache/couchdb-fauxton/pull/1155
 
 
   

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/reducers.js b/app/addons/databases/reducers.js
index a20a5f67a..2c0c2607b 100644
--- a/app/addons/databases/reducers.js
+++ b/app/addons/databases/reducers.js
@@ -13,7 +13,7 @@
 import ActionTypes from './actiontypes';
 const initialState = {
   partitionedDatabasesAvailable: false,
-  isLoadingDbInfo: false,
+  isLoadingDbInfo: true,
   dbInfo: undefined,
   isDbPartitioned: false
 };
diff --git a/app/addons/documents/base.js b/app/addons/documents/base.js
index 8ccf98995..48be8e3f0 100644
--- a/app/addons/documents/base.js
+++ b/app/addons/documents/base.js
@@ -222,40 +222,40 @@ FauxtonAPI.registerUrls('mango', {
     return Helpers.getServerUrl('/' + db + '/_index' + query);
   },
 
-  'index-apiurl': function (db, query) {
+  'index-apiurl': function (db, partitionKey, query) {
     if (!query) {
       query = '';
     }
 
-    return Helpers.getApiUrl('/' + db + '/_index' + query);
+    return Helpers.getApiUrl('/' + db + partitionUrlComponent(partitionKey) + '/_index' + query);
   },
 
-  'index-app': function (db, query) {
+  'index-app': function (db, partitionKey, query) {
     if (!query) {
       query = '';
     }
 
-    return 'database/' + db + '/_index' + query;
+    return 'database/' + db + partitionUrlComponent(partitionKey) + '/_index' + query;
   },
 
   'index-server-bulk-delete': function (db) {
     return Helpers.getServerUrl('/' + db + '/_index/_bulk_delete');
   },
 
-  'query-server': function (db, query) {
+  'query-server': function (db, partitionKey, query) {
     if (!query) {
       query = '';
     }
 
-    return Helpers.getServerUrl('/' + db + '/_find' + query);
+    return Helpers.getServerUrl('/' + db + partitionUrlComponent(partitionKey) + '/_find' + query);
   },
 
-  'query-apiurl': function (db, query) {
+  'query-apiurl': function (db, partitionKey, query) {
     if (!query) {
       query = '';
     }
 
-    return Helpers.getApiUrl('/' + db + '/_find' + query);
+    return Helpers.getApiUrl('/' + db + partitionUrlComponent(partitionKey) + '/_find' + query);
   },
 
   'query-app': function (db, partitionKey, query) {
@@ -266,12 +266,12 @@ FauxtonAPI.registerUrls('mango', {
     return 'database/' + db + partitionUrlComponent(partitionKey) + '/_find' + query;
   },
 
-  'explain-server': function (db) {
-    return Helpers.getServerUrl('/' + db + '/_explain');
+  'explain-server': function (db, partitionKey) {
+    return Helpers.getServerUrl('/' + db + partitionUrlComponent(partitionKey) + '/_explain');
   },
 
-  'explain-apiurl': function (db) {
-    return Helpers.getApiUrl('/' + db + '/_explain');
+  'explain-apiurl': function (db, partitionKey) {
+    return Helpers.getApiUrl('/' + db + partitionUrlComponent(partitionKey) + '/_explain');
   }
 });
 
diff --git a/app/addons/documents/header/header.js b/app/addons/documents/header/header.js
index 52093e522..3f1171c3d 100644
--- a/app/addons/documents/header/header.js
+++ b/app/addons/documents/header/header.js
@@ -29,22 +29,23 @@ export default class BulkDocumentHeaderController extends React.Component {
     } = this.props;
 
     let metadata, json, table;
-    if ((docType === Constants.INDEX_RESULTS_DOC_TYPE.VIEW)) {
+    if (docType === Constants.INDEX_RESULTS_DOC_TYPE.VIEW) {
       metadata = <Button
         className={selectedLayout === Constants.LAYOUT_ORIENTATION.METADATA ? 'active' : ''}
         onClick={this.toggleLayout.bind(this, Constants.LAYOUT_ORIENTATION.METADATA)}
       >
           Metadata
       </Button>;
-    } else if ((docType === Constants.INDEX_RESULTS_DOC_TYPE.MANGO_INDEX)) {
+    } else if (docType === Constants.INDEX_RESULTS_DOC_TYPE.MANGO_INDEX) {
       return null;
     }
 
-    // Reduce doesn't allow for include_docs=true, so we'll prevent JSON and table
-    // views since they force include_docs=true when reduce is checked in the query options panel.
-    // Partitioned queries don't supprt include_docs=true either.
+    // Reduce doesn't allow for include_docs=true, so we'll hide the JSON and table modes
+    // since they force 'include_docs=true' when reduce is checked in the query options panel.
+    // Partitioned views don't support 'include_docs=true' either.
     const isAllDocsQuery = fetchUrl && fetchUrl.includes('/_all_docs');
-    if (isAllDocsQuery || (!queryOptionsParams.reduce && !partitionKey)) {
+    const isMangoQuery = docType === Constants.INDEX_RESULTS_DOC_TYPE.MANGO_QUERY;
+    if (isAllDocsQuery || isMangoQuery || (!queryOptionsParams.reduce && !partitionKey)) {
       table = <Button
         className={selectedLayout === Constants.LAYOUT_ORIENTATION.TABLE ? 'active' : ''}
         onClick={this.toggleLayout.bind(this, Constants.LAYOUT_ORIENTATION.TABLE)}
diff --git a/app/addons/documents/mango/__tests__/mango.api.test.js b/app/addons/documents/mango/__tests__/mango.api.test.js
index 08366334c..2942662d0 100644
--- a/app/addons/documents/mango/__tests__/mango.api.test.js
+++ b/app/addons/documents/mango/__tests__/mango.api.test.js
@@ -10,34 +10,52 @@
 // License for the specific language governing permissions and limitations under
 // the License.
 
-import sinon from "sinon";
-import utils from "../../../../../test/mocha/testUtils";
-import FauxtonAPI from "../../../../core/api";
-import * as MangoAPI from '../mango.api';
+import fetchMock from 'fetch-mock';
 import Constants from '../../constants';
-
-const fetchMock = require('fetch-mock');
-const assert = utils.assert;
-const restore = utils.restore;
+import * as MangoAPI from '../mango.api';
+import '../../base';
 
 describe('Mango API', () => {
 
   const paginationLimit = 6;
 
-  beforeEach(() => {
-    sinon.stub(FauxtonAPI, 'urls').returns('mock-url');
-  });
-
   afterEach(() => {
-    restore(FauxtonAPI.urls);
+    fetchMock.restore();
   });
 
   describe('mangoQueryDocs', () => {
-    it('returns document type INDEX_RESULTS_DOC_TYPE.MANGO_QUERY', (done) => {
+    it('returns document type INDEX_RESULTS_DOC_TYPE.MANGO_QUERY', () => {
       fetchMock.mock("*", { times: 2 });
-      MangoAPI.mangoQueryDocs('myDB', {}, {}).then((res) => {
-        assert.equal(res.docType, Constants.INDEX_RESULTS_DOC_TYPE.MANGO_QUERY);
-        done();
+      return MangoAPI.mangoQueryDocs('myDB', '', {}, {}).then((res) => {
+        expect(res.docType).toBe(Constants.INDEX_RESULTS_DOC_TYPE.MANGO_QUERY);
+      });
+    });
+  });
+
+  describe('mangoQuery', () => {
+    it('adds partition key to the query URL when one is set', () => {
+      fetchMock.postOnce('./myDB/_partition/part1/_find', {
+        status: 200,
+        body: { ok: true }
+      }).catch({
+        status: 500,
+        body: { ok: false }
+      });
+      return MangoAPI.mangoQuery('myDB', 'part1', {}, {}).then(() => {
+        expect(fetchMock.done()).toBe(true);
+      });
+    });
+
+    it('does not add partition key to the query URL when one is set', () => {
+      fetchMock.postOnce('./myDB/_find', {
+        status: 200,
+        body: { ok: true }
+      }).catch({
+        status: 500,
+        body: { ok: false }
+      });
+      return MangoAPI.mangoQuery('myDB', '', {}, {}).then(() => {
+        expect(fetchMock.done()).toBe(true);
       });
     });
   });
@@ -46,76 +64,75 @@ describe('Mango API', () => {
     it('adjusts the query "skip" field based on pagination fetch params', () => {
       // 1st page and query's skip is zero
       let mergedParams = MangoAPI.mergeFetchParams({skip: 0}, {skip: 0, limit: paginationLimit});
-      assert.equal(mergedParams.skip, 0);
+      expect(mergedParams.skip).toBe(0);
 
       // 1st page and query's skip is non-zero
       mergedParams = MangoAPI.mergeFetchParams({skip: 3}, {skip: 0, limit: paginationLimit});
-      assert.equal(mergedParams.skip, 3);
+      expect(mergedParams.skip).toBe(3);
 
       // non-1st page and query's skip is zero
       mergedParams = MangoAPI.mergeFetchParams({skip: 0}, {skip: 5, limit: paginationLimit});
-      assert.equal(mergedParams.skip, 5);
+      expect(mergedParams.skip).toBe(5);
 
       // non-1st page and query's skip is non-zero
       mergedParams = MangoAPI.mergeFetchParams({skip: 3}, {skip: 5, limit: paginationLimit});
-      assert.equal(mergedParams.skip, 8);
+      expect(mergedParams.skip).toBe(8);
 
     });
 
     it('uses ZERO when query limit is ZERO', () => {
       const mergedParams = MangoAPI.mergeFetchParams({limit: 0}, {skip: 0, limit: paginationLimit});
-      assert.equal(mergedParams.limit, 0);
+      expect(mergedParams.limit).toBe(0);
     });
 
     it('uses pagination limit when query limit is not provided', () => {
       const mergedParams = MangoAPI.mergeFetchParams({}, {skip: 5, limit: paginationLimit});
-      assert.equal(mergedParams.limit, paginationLimit);
+      expect(mergedParams.limit).toBe(paginationLimit);
     });
 
     it('uses pagination limit if query limit has not been reached', () => {
       let mergedParams = MangoAPI.mergeFetchParams({limit: 50}, {skip: 5, limit: paginationLimit});
-      assert.equal(mergedParams.limit, paginationLimit);
+      expect(mergedParams.limit).toBe(paginationLimit);
 
       mergedParams = MangoAPI.mergeFetchParams({limit: 50}, {skip: 15, limit: paginationLimit});
-      assert.equal(mergedParams.limit, paginationLimit);
+      expect(mergedParams.limit).toBe(paginationLimit);
     });
 
     it('respects query limit when value is lower than docs per page', () => {
       const mergedParams = MangoAPI.mergeFetchParams({limit: 3}, {skip: 0, limit: paginationLimit});
-      assert.equal(mergedParams.limit, 3);
+      expect(mergedParams.limit).toBe(3);
     });
 
     it('respects query limit when value is greater than docs per page', () => {
       // Simulates loading of 3rd page
       const mergedParams = MangoAPI.mergeFetchParams({limit: 17}, {skip: 15, limit: paginationLimit});
-      assert.equal(mergedParams.limit, 2);
+      expect(mergedParams.limit).toBe(2);
     });
 
     it('respects query limit when value is a multipe of docs per page', () => {
       // 1st page
       let mergedParams = MangoAPI.mergeFetchParams({limit: 5}, {skip: 0, limit: paginationLimit});
-      assert.equal(mergedParams.limit, 5);
+      expect(mergedParams.limit).toBe(5);
 
       // non-1st page
       mergedParams = MangoAPI.mergeFetchParams({limit: 15}, {skip: 10, limit: paginationLimit});
-      assert.equal(mergedParams.limit, 5);
+      expect(mergedParams.limit).toBe(5);
     });
 
     it('works correctly with both skip and limit query params', () => {
       // Simulates loading of 3rd page
       const mergedParams = MangoAPI.mergeFetchParams({skip:10, limit: 17}, {skip: 15, limit: paginationLimit});
-      assert.equal(mergedParams.limit, 2);
-      assert.equal(mergedParams.skip, 25);
+      expect(mergedParams.limit).toBe(2);
+      expect(mergedParams.skip).toBe(25);
     });
 
   });
 
   describe('fetchIndexes', () => {
-    it('returns document type INDEX_RESULTS_DOC_TYPE.MANGO_INDEX', (done) => {
+    it('returns document type INDEX_RESULTS_DOC_TYPE.MANGO_INDEX', () => {
       fetchMock.once("*", {});
-      MangoAPI.fetchIndexes('myDB', {}).then((res) => {
-        assert.equal(res.docType, Constants.INDEX_RESULTS_DOC_TYPE.MANGO_INDEX);
-        done();
+      return MangoAPI.fetchIndexes('myDB', {}).then((res) => {
+        expect(res.docType).toBe(Constants.INDEX_RESULTS_DOC_TYPE.MANGO_INDEX);
       });
     });
   });
diff --git a/app/addons/documents/mango/__tests__/mango.components.test.js b/app/addons/documents/mango/__tests__/mango.components.test.js
index c83b95408..738add6b6 100644
--- a/app/addons/documents/mango/__tests__/mango.components.test.js
+++ b/app/addons/documents/mango/__tests__/mango.components.test.js
@@ -10,31 +10,34 @@
 // License for the specific language governing permissions and limitations under
 // the License.
 
-import FauxtonAPI from "../../../../core/api";
-import Views from "../mango.components";
-import MangoQueryEditor from "../components/MangoQueryEditor";
-import MangoIndexEditor from "../components/MangoIndexEditor";
-import utils from "../../../../../test/mocha/testUtils";
-import React from "react";
-import ReactDOM from "react-dom";
-import sinon from "sinon";
-import { mount } from 'enzyme';
-
-import thunk from 'redux-thunk';
+import { mount, shallow } from 'enzyme';
+import React from 'react';
 import { Provider } from 'react-redux';
 import { createStore, applyMiddleware, combineReducers } from 'redux';
-import mangoReducer from '../mango.reducers';
+import thunk from 'redux-thunk';
+import sinon from 'sinon';
+import FauxtonAPI from '../../../../core/api';
+import utils from '../../../../../test/mocha/testUtils';
+import databasesReducer from '../../../databases/reducers';
 import indexResultsReducer from '../../index-results/reducers';
+import Views from '../mango.components';
+import MangoQueryEditor from '../components/MangoQueryEditor';
+import MangoIndexEditor from '../components/MangoIndexEditor';
+import mangoReducer from '../mango.reducers';
+import '../../base';
 
-const assert = utils.assert;
 const restore = utils.restore;
 const databaseName = 'testdb';
 
-describe('Mango IndexEditor', function () {
+describe('MangoIndexEditorContainer', function () {
 
   const middlewares = [thunk];
   const store = createStore(
-    combineReducers({ mangoQuery: mangoReducer, indexResults: indexResultsReducer }),
+    combineReducers({
+      mangoQuery: mangoReducer,
+      indexResults: indexResultsReducer,
+      databases: databasesReducer
+    }),
     applyMiddleware(...middlewares)
   );
 
@@ -56,16 +59,111 @@ describe('Mango IndexEditor', function () {
     );
 
     const indexEditor = wrapper.find(MangoIndexEditor);
-    assert.ok(indexEditor.exists());
+    expect(indexEditor.exists()).toBe(true);
     if (indexEditor.exists()) {
       const json = JSON.parse(indexEditor.props().queryIndexCode);
-      assert.equal(json.index.fields[0], 'foo');
+      expect(json.index.fields[0]).toBe('foo');
     }
   });
 
+  it('shows partitioned option only for partitioned databases', function () {
+    const wrapper = mount(
+      <Provider store={store}>
+        <Views.MangoIndexEditorContainer
+          description="foo"
+          databaseName={databaseName} />
+      </Provider>
+    );
+    const indexEditor = wrapper.find(MangoIndexEditor);
+    expect(indexEditor.exists()).toBe(true);
+    if (indexEditor.exists()) {
+      const json = JSON.parse(indexEditor.props().queryIndexCode);
+      expect(json.index.fields[0]).toBe('foo');
+    }
+  });
+
+});
+
+describe('MangoIndexEditor', function () {
+  const defaultProps = {
+    isLoading: false,
+    databaseName: 'db1',
+    isDbPartitioned: false,
+    saveIndex: () => {},
+    queryIndexCode: '{ "selector": {} }',
+    partitionKey: '',
+    loadIndexTemplates: () => {},
+    clearResults: () => {},
+    loadIndexList: () => {}
+  };
+
+  it('shows partitioned option only for partitioned databases', function () {
+    const wrapperNotPartitioned = shallow(
+      <MangoIndexEditor
+        {...defaultProps}
+      />
+    );
+    expect(wrapperNotPartitioned.find('#js-partitioned-index').exists()).toBe(false);
+
+    const wrapperPartitioned = shallow(
+      <MangoIndexEditor
+        {...defaultProps}
+        isDbPartitioned={true}
+      />
+    );
+    expect(wrapperPartitioned.find('#js-partitioned-index').exists()).toBe(true);
+  });
+
+  it('does not add "partitioned" field for non-partitioned dbs', function () {
+    const saveIndexStub = sinon.stub();
+    const wrapper = mount(
+      <MangoIndexEditor
+        {...defaultProps}
+        saveIndex={saveIndexStub}
+      />
+    );
+    wrapper.find('form.form-horizontal').simulate('submit', { preventDefault: () => {} });
+    sinon.assert.called(saveIndexStub);
+    const { indexCode } = saveIndexStub.firstCall.args[0];
+    expect(indexCode.length).toBeGreaterThan(0);
+    expect(indexCode).not.toMatch('"partitioned":');
+  });
+
+  it('adds "partitioned: true" field when creating a partitioned index', function () {
+    const saveIndexStub = sinon.stub();
+    const wrapper = mount(
+      <MangoIndexEditor
+        {...defaultProps}
+        isDbPartitioned={true}
+        saveIndex={saveIndexStub}
+      />
+    );
+    wrapper.find('form.form-horizontal').simulate('submit', { preventDefault: () => {} });
+    sinon.assert.called(saveIndexStub);
+    const { indexCode } = saveIndexStub.firstCall.args[0];
+    expect(indexCode.length).toBeGreaterThan(0);
+    expect(indexCode).toMatch('"partitioned":true');
+  });
+
+  it('adds "partitioned: false" field when creating a global index', function () {
+    const saveIndexStub = sinon.stub();
+    const wrapper = mount(
+      <MangoIndexEditor
+        {...defaultProps}
+        isDbPartitioned={true}
+        saveIndex={saveIndexStub}
+      />
+    );
+    wrapper.find('#js-partitioned-index').simulate('change');
+    wrapper.find('form.form-horizontal').simulate('submit', { preventDefault: () => {} });
+    sinon.assert.called(saveIndexStub);
+    const { indexCode } = saveIndexStub.firstCall.args[0];
+    expect(indexCode.length).toBeGreaterThan(0);
+    expect(indexCode).toMatch('"partitioned":false');
+  });
 });
 
-describe('Mango QueryEditor', function () {
+describe('MangoQueryEditorContainer', function () {
 
   const middlewares = [thunk];
   const store = createStore(
@@ -91,10 +189,58 @@ describe('Mango QueryEditor', function () {
       </Provider>
     );
     const queryEditor = wrapper.find(MangoQueryEditor);
-    assert.ok(queryEditor.exists());
+    expect(queryEditor.exists()).toBe(true);
     if (queryEditor.exists()) {
       const query = JSON.parse(queryEditor.props().queryFindCode);
-      assert.property(query.selector, '_id');
+      expect(query.selector).toHaveProperty('_id');
     }
   });
 });
+
+describe('MangoQueryEditor', function () {
+  const defaultProps = {
+    description: 'desc',
+    editorTitle: 'title',
+    queryFindCode: '{}',
+    queryFindCodeChanged: false,
+    databaseName: 'db1',
+    partitionKey: '',
+    runExplainQuery: () => {},
+    runQuery: () => {},
+    manageIndexes: () => {},
+    loadQueryHistory: () => {},
+    clearResults: () => {}
+  };
+
+  it('runs explain query with partition when one is set', function () {
+    const runExplainQueryStub = sinon.stub();
+    const wrapper = mount(
+      <MangoQueryEditor
+        {...defaultProps}
+        partitionKey='part1'
+        runExplainQuery={runExplainQueryStub}
+      />
+    );
+
+    wrapper.find('#explain-btn').simulate('click', { preventDefault: () => {} });
+    sinon.assert.called(runExplainQueryStub);
+    const { partitionKey } = runExplainQueryStub.firstCall.args[0];
+    expect(partitionKey).toBe('part1');
+  });
+
+  it('runs explain query with partition when one is set', function () {
+    const runQueryStub = sinon.stub();
+    const wrapper = mount(
+      <MangoQueryEditor
+        {...defaultProps}
+        partitionKey='part1'
+        runQuery={runQueryStub}
+      />
+    );
+
+    wrapper.find('form.form-horizontal').simulate('submit', { preventDefault: () => {} });
+    sinon.assert.called(runQueryStub);
+    const { partitionKey } = runQueryStub.firstCall.args[0];
+    expect(partitionKey).toBe('part1');
+  });
+});
diff --git a/app/addons/documents/mango/components/MangoIndexEditor.js b/app/addons/documents/mango/components/MangoIndexEditor.js
index dc3006370..28ba57d0b 100644
--- a/app/addons/documents/mango/components/MangoIndexEditor.js
+++ b/app/addons/documents/mango/components/MangoIndexEditor.js
@@ -11,23 +11,29 @@
 // the License.
 
 import PropTypes from 'prop-types';
-
-import React, { Component } from "react";
-import ReactSelect from "react-select";
-import "../../../../../assets/js/plugins/prettify";
-import app from "../../../../app";
-import FauxtonAPI from "../../../../core/api";
-import ReactComponents from "../../../components/react-components";
+import React, { Component } from 'react';
+import ReactSelect from 'react-select';
+import '../../../../../assets/js/plugins/prettify';
+import app from '../../../../app';
+import FauxtonAPI from '../../../../core/api';
+import ReactComponents from '../../../components/react-components';
 
 const PaddedBorderedBox = ReactComponents.PaddedBorderedBox;
 const CodeEditorPanel = ReactComponents.CodeEditorPanel;
 const ConfirmButton = ReactComponents.ConfirmButton;
+const LoadLines = ReactComponents.LoadLines;
 const getDocUrl = app.helpers.getDocUrl;
 
 export default class MangoIndexEditor extends Component {
 
   constructor(props) {
     super(props);
+    this.state = {
+      partitionedSelected: true
+    };
+    this.onTemplateSelected = this.onTemplateSelected.bind(this);
+    this.onTogglePartitioned = this.onTogglePartitioned.bind(this);
+    this.saveIndex = this.saveIndex.bind(this);
   }
 
   componentDidMount() {
@@ -48,7 +54,9 @@ export default class MangoIndexEditor extends Component {
   }
 
   setEditorValue(newValue = '') {
-    return this.codeEditor.getEditor().setValue(newValue);
+    if (this.codeEditor) {
+      return this.codeEditor.getEditor().setValue(newValue);
+    }
   }
 
   getEditorValue() {
@@ -63,11 +71,33 @@ export default class MangoIndexEditor extends Component {
     this.setEditorValue(selectedItem.value);
   }
 
+  onTogglePartitioned() {
+    this.setState({partitionedSelected: !this.state.partitionedSelected});
+  }
+
+  partitionedCheckobx() {
+    if (!this.props.isDbPartitioned) {
+      return null;
+    }
+    return (
+      <label>
+        <input
+          id="js-partitioned-index"
+          type="checkbox"
+          checked={this.state.partitionedSelected}
+          onChange={this.onTogglePartitioned}
+          style={{margin: '0px 10px 0px 0px'}} />
+        Partitioned
+      </label>
+    );
+  }
+
   editor() {
-    const editQueryURL = '#' + FauxtonAPI.urls('mango', 'query-app', encodeURIComponent(this.props.databaseName), '');
+    const encodedPartKey = this.props.partitionKey ? encodeURIComponent(this.props.partitionKey) : '';
+    const editQueryURL = '#' + FauxtonAPI.urls('mango', 'query-app', encodeURIComponent(this.props.databaseName), encodedPartKey);
     return (
       <div className="mango-editor-wrapper">
-        <form className="form-horizontal" onSubmit={(ev) => { this.saveIndex(ev); }}>
+        <form className="form-horizontal" onSubmit={this.saveIndex}>
           <div className="padded-box">
             <ReactSelect
               className="mango-select"
@@ -77,7 +107,7 @@ export default class MangoIndexEditor extends Component {
               searchable={false}
               clearable={false}
               autosize={false}
-              onChange={(item) => { this.onTemplateSelected(item); }}
+              onChange={this.onTemplateSelected}
             />
           </div>
           <PaddedBorderedBox>
@@ -87,6 +117,7 @@ export default class MangoIndexEditor extends Component {
               title="Index"
               docLink={getDocUrl('MANGO_INDEX')}
               defaultCode={this.props.queryIndexCode} />
+            {this.partitionedCheckobx()}
           </PaddedBorderedBox>
           <div className="padded-box">
             <div className="control-group">
@@ -100,31 +131,62 @@ export default class MangoIndexEditor extends Component {
   }
 
   render() {
+    if (this.props.isLoading) {
+      return <LoadLines />;
+    }
     return this.editor();
   }
 
   saveIndex(event) {
     event.preventDefault();
 
-    if (this.editorHasErrors()) {
+    const showInvalidCodeMsg = () => {
       FauxtonAPI.addNotification({
         msg: 'Please fix the Javascript errors and try again.',
         type: 'error',
         clear: true
       });
+    };
+    if (this.editorHasErrors()) {
+      showInvalidCodeMsg();
       return;
     }
 
+    let indexCode = this.getEditorValue();
+    if (this.props.isDbPartitioned) {
+      // Set the partitioned property if not yet set
+      try {
+        const json = JSON.parse(indexCode);
+        if (json.partitioned !== true && json.partitioned !== false) {
+          json.partitioned = this.state.partitionedSelected;
+        }
+        indexCode = JSON.stringify(json);
+      } catch (err) {
+        showInvalidCodeMsg();
+      }
+    }
+
     this.props.saveIndex({
       databaseName: this.props.databaseName,
-      indexCode: this.getEditorValue(),
+      indexCode: indexCode,
       fetchParams: this.props.fetchParams
     });
   }
 }
 
 MangoIndexEditor.propTypes = {
+  isLoading: PropTypes.bool.isRequired,
   databaseName: PropTypes.string.isRequired,
+  isDbPartitioned: PropTypes.bool.isRequired,
   saveIndex: PropTypes.func.isRequired,
-  queryIndexCode: PropTypes.string.isRequired
+  queryIndexCode: PropTypes.string.isRequired,
+  partitionKey: PropTypes.string,
+  loadIndexTemplates: PropTypes.func.isRequired,
+  clearResults: PropTypes.func.isRequired,
+  loadIndexList: PropTypes.func.isRequired
+};
+
+MangoIndexEditor.defaultProps = {
+  isLoading: true,
+  isDbPartitioned: false
 };
diff --git a/app/addons/documents/mango/components/MangoIndexEditorContainer.js b/app/addons/documents/mango/components/MangoIndexEditorContainer.js
index 93e84adfb..79ca5874a 100644
--- a/app/addons/documents/mango/components/MangoIndexEditorContainer.js
+++ b/app/addons/documents/mango/components/MangoIndexEditorContainer.js
@@ -18,13 +18,16 @@ import Helpers from '../mango.helper';
 import Actions from '../mango.actions';
 import * as MangoAPI from '../mango.api';
 
-const mapStateToProps = ({ mangoQuery, indexResults }, ownProps) => {
+const mapStateToProps = ({ mangoQuery, indexResults, databases }, ownProps) => {
   return {
     description: ownProps.description,
     databaseName: ownProps.databaseName,
     queryIndexCode: Helpers.formatCode(mangoQuery.queryIndexCode),
     templates: mangoQuery.queryIndexTemplates,
     fetchParams: indexResults.fetchParams,
+    partitionKey: ownProps.partitionKey,
+    isLoading: databases.isLoadingDbInfo,
+    isDbPartitioned: databases.isDbPartitioned
   };
 };
 
diff --git a/app/addons/documents/mango/components/MangoQueryEditor.js b/app/addons/documents/mango/components/MangoQueryEditor.js
index 15ef83a6e..ecbfea083 100644
--- a/app/addons/documents/mango/components/MangoQueryEditor.js
+++ b/app/addons/documents/mango/components/MangoQueryEditor.js
@@ -145,7 +145,8 @@ export default class MangoQueryEditor extends Component {
 
     this.props.manageIndexes();
 
-    const manageIndexURL = '#' + FauxtonAPI.urls('mango', 'index-app', encodeURIComponent(this.props.databaseName));
+    const manageIndexURL = '#' + FauxtonAPI.urls('mango', 'index-app',
+      encodeURIComponent(this.props.databaseName), encodeURIComponent(this.props.partitionKey));
     FauxtonAPI.navigate(manageIndexURL);
   }
 
@@ -158,6 +159,7 @@ export default class MangoQueryEditor extends Component {
 
     this.props.runExplainQuery({
       databaseName: this.props.databaseName,
+      partitionKey: this.props.partitionKey,
       queryCode: this.getEditorValue()
     });
   }
@@ -171,6 +173,7 @@ export default class MangoQueryEditor extends Component {
     this.props.clearResults();
     this.props.runQuery({
       databaseName: this.props.databaseName,
+      partitionKey: this.props.partitionKey,
       queryCode: JSON.parse(this.getEditorValue()),
       fetchParams: {...this.props.fetchParams, skip: 0}
     });
@@ -183,6 +186,10 @@ MangoQueryEditor.propTypes = {
   queryFindCode: PropTypes.string.isRequired,
   queryFindCodeChanged: PropTypes.bool,
   databaseName: PropTypes.string.isRequired,
+  partitionKey: PropTypes.string,
   runExplainQuery: PropTypes.func.isRequired,
+  runQuery: PropTypes.func.isRequired,
   manageIndexes: PropTypes.func.isRequired,
+  loadQueryHistory: PropTypes.func.isRequired,
+  clearResults: PropTypes.func.isRequired
 };
diff --git a/app/addons/documents/mango/components/MangoQueryEditorContainer.js b/app/addons/documents/mango/components/MangoQueryEditorContainer.js
index 7bbb3107e..ede0e5698 100644
--- a/app/addons/documents/mango/components/MangoQueryEditorContainer.js
+++ b/app/addons/documents/mango/components/MangoQueryEditorContainer.js
@@ -59,7 +59,8 @@ const mapStateToProps = (state, ownProps) => {
     additionalIndexesText: ownProps.additionalIndexesText,
     fetchParams: indexResults.fetchParams,
     executionStats: indexResults.executionStats,
-    warning: indexResults.warning
+    warning: indexResults.warning,
+    partitionKey: ownProps.partitionKey
   };
 };
 
@@ -74,7 +75,9 @@ const mapDispatchToProps = (dispatch/*, ownProps*/) => {
     },
 
     runQuery: (options) => {
-      const queryDocs = (params) => { return MangoAPI.mangoQueryDocs(options.databaseName, options.queryCode, params); };
+      const queryDocs = (params) => {
+        return MangoAPI.mangoQueryDocs(options.databaseName, options.partitionKey, options.queryCode, params);
+      };
 
       dispatch(Actions.hideQueryExplain());
       dispatch(Actions.newQueryFindCode(options));
diff --git a/app/addons/documents/mango/mango.actions.js b/app/addons/documents/mango/mango.actions.js
index 67f31baef..be79bd4a4 100644
--- a/app/addons/documents/mango/mango.actions.js
+++ b/app/addons/documents/mango/mango.actions.js
@@ -105,9 +105,9 @@ export default {
     return 'Reason: ' + ((error && error.message) || 'n/a');
   },
 
-  runExplainQuery: function ({ databaseName, queryCode }) {
+  runExplainQuery: function ({ databaseName, partitionKey, queryCode }) {
     return (dispatch) => {
-      return MangoAPI.fetchQueryExplain(databaseName, queryCode)
+      return MangoAPI.fetchQueryExplain(databaseName, partitionKey, queryCode)
         .then((explainPlan) => {
           dispatch(this.showQueryExplain({ explainPlan }));
         }).catch(() => {
diff --git a/app/addons/documents/mango/mango.api.js b/app/addons/documents/mango/mango.api.js
index 21e5ecd20..965559294 100644
--- a/app/addons/documents/mango/mango.api.js
+++ b/app/addons/documents/mango/mango.api.js
@@ -15,8 +15,8 @@ import {post, get} from '../../../core/ajax';
 import FauxtonAPI from "../../../core/api";
 import Constants from '../constants';
 
-export const fetchQueryExplain = (databaseName, queryCode) => {
-  const url = FauxtonAPI.urls('mango', 'explain-server', encodeURIComponent(databaseName));
+export const fetchQueryExplain = (databaseName, partitionKey, queryCode) => {
+  const url = FauxtonAPI.urls('mango', 'explain-server', encodeURIComponent(databaseName), encodeURIComponent(partitionKey));
 
   return post(url, queryCode, {rawBody: true}).then((json) => {
     if (json.error) {
@@ -61,7 +61,7 @@ let supportsExecutionStatsCache = null;
 const supportsExecutionStats = (databaseName) => {
   if (supportsExecutionStatsCache === null) {
     return new FauxtonAPI.Promise((resolve) => {
-      mangoQuery(databaseName, {
+      mangoQuery(databaseName, '', {
         selector: {
           "_id": {"$gt": "a" }
         },
@@ -96,21 +96,21 @@ export const mergeFetchParams = (queryCode, fetchParams) => {
   };
 };
 
-export const mangoQuery = (databaseName, queryCode, fetchParams) => {
-  const url = FauxtonAPI.urls('mango', 'query-server', encodeURIComponent(databaseName));
+export const mangoQuery = (databaseName, partitionKey, queryCode, fetchParams) => {
+  const encodedPartKey = partitionKey ? encodeURIComponent(partitionKey) : '';
+  const url = FauxtonAPI.urls('mango', 'query-server', encodeURIComponent(databaseName), encodedPartKey);
   const modifiedQuery = mergeFetchParams(queryCode, fetchParams);
-
   return post(url, modifiedQuery, {raw: true});
 };
 
-export const mangoQueryDocs = (databaseName, queryCode, fetchParams) => {
+export const mangoQueryDocs = (databaseName, partitionKey, queryCode, fetchParams) => {
   // we can only add the execution_stats field if it is supported by the server
   // otherwise Couch throws an error
   return supportsExecutionStats(databaseName).then((shouldFetchExecutionStats) => {
     if (shouldFetchExecutionStats) {
       queryCode.execution_stats = true;
     }
-    return mangoQuery(databaseName, queryCode, fetchParams)
+    return mangoQuery(databaseName, partitionKey, queryCode, fetchParams)
       .then((res) => res.json())
       .then((json) => {
         if (json.error) {
diff --git a/app/addons/documents/mangolayout.js b/app/addons/documents/mangolayout.js
index 918cd011c..504b7df9a 100644
--- a/app/addons/documents/mangolayout.js
+++ b/app/addons/documents/mangolayout.js
@@ -20,13 +20,36 @@ import * as MangoAPI from "./mango/mango.api";
 import IndexResultsContainer from './index-results/containers/IndexResultsContainer';
 import PaginationContainer from './index-results/containers/PaginationContainer';
 import ApiBarContainer from './index-results/containers/ApiBarContainer';
+import PartitionKeySelectorContainer from './partition-key/container';
 import FauxtonAPI from "../../core/api";
 import Constants from './constants';
 
-export const RightHeader = ({ docURL, endpoint }) => {
+export const RightHeader = ({
+  docURL,
+  endpoint,
+  databaseName,
+  showPartitionKeySelector,
+  partitionKey,
+  onPartitionKeySelected,
+  onGlobalModeSelected,
+  globalMode
+}) => {
   const apiBar = <ApiBarContainer docURL={docURL} endpoint={endpoint} includeQueryOptionsParams={false}/>;
+  let partKeySelector = null;
+  if (showPartitionKeySelector) {
+    partKeySelector = (<PartitionKeySelectorContainer
+      databaseName={databaseName}
+      partitionKey={partitionKey}
+      onPartitionKeySelected={onPartitionKeySelected}
+      onGlobalModeSelected={onGlobalModeSelected}
+      globalMode={globalMode}/>
+    );
+  }
   return (
     <div className="right-header-wrapper flex-layout flex-row flex-body">
+      <div style={{flex:1, padding: '18px 6px 12px 12px'}}>
+        {partKeySelector}
+      </div>
       <div id="right-header" className="flex-body">
       </div>
       {apiBar}
@@ -48,7 +71,17 @@ export const MangoFooter = ({databaseName, fetchUrl, queryDocs}) => {
   );
 };
 
-export const MangoHeader = ({ crumbs, docURL, endpoint }) => {
+export const MangoHeader = ({
+  crumbs,
+  docURL,
+  endpoint,
+  databaseName,
+  partitionKey,
+  showPartitionKeySelector,
+  onPartitionKeySelected,
+  onGlobalModeSelected,
+  globalMode
+}) => {
   return (
     <div className="header-wrapper flex-layout flex-row">
       <div className='flex-body faux__breadcrumbs-mango-header'>
@@ -57,6 +90,12 @@ export const MangoHeader = ({ crumbs, docURL, endpoint }) => {
       <RightHeader
         docURL={docURL}
         endpoint={endpoint}
+        databaseName={databaseName}
+        showPartitionKeySelector={showPartitionKeySelector}
+        partitionKey={partitionKey}
+        onPartitionKeySelected={onPartitionKeySelected}
+        onGlobalModeSelected={onGlobalModeSelected}
+        globalMode={globalMode}
       />
     </div>
   );
@@ -66,17 +105,19 @@ MangoHeader.defaultProps = {
   crumbs: []
 };
 
-export const MangoContent = ({ edit, designDocs, explainPlan, databaseName, fetchUrl, queryDocs, docType }) => {
+export const MangoContent = ({ edit, designDocs, explainPlan, databaseName, fetchUrl, queryDocs, docType, partitionKey }) => {
   const leftContent = edit ?
     <MangoComponents.MangoIndexEditorContainer
       description={app.i18n.en_US['mango-descripton-index-editor']}
       databaseName={databaseName}
+      partitionKey={partitionKey}
     /> :
     <MangoComponents.MangoQueryEditorContainer
       description={app.i18n.en_US['mango-descripton']}
       editorTitle={app.i18n.en_US['mango-title-editor']}
       additionalIndexesText={app.i18n.en_US['mango-additional-indexes-heading']}
       databaseName={databaseName}
+      partitionKey={partitionKey}
     />;
 
   let resultsPage = <IndexResultsContainer
@@ -86,7 +127,8 @@ export const MangoContent = ({ edit, designDocs, explainPlan, databaseName, fetc
     databaseName={databaseName}
     fetchAtStartup={false}
     queryDocs={queryDocs}
-    docType={docType} />;
+    docType={docType}
+    partitionKey={partitionKey} />;
 
   let mangoFooter = <MangoFooter
     databaseName={databaseName}
@@ -119,13 +161,13 @@ class MangoLayout extends Component {
   }
 
   render() {
-    const { database, edit, docURL, crumbs, designDocs, fetchUrl, databaseName, queryFindCode } = this.props;
+    const { database, edit, docURL, crumbs, designDocs, fetchUrl, databaseName, queryFindCode, partitionKey, onPartitionKeySelected, onGlobalModeSelected, globalMode } = this.props;
     let endpoint = this.props.endpoint;
 
     if (this.props.explainPlan) {
-      endpoint = FauxtonAPI.urls('mango', 'explain-apiurl', encodeURIComponent(database));
+      endpoint = FauxtonAPI.urls('mango', 'explain-apiurl', encodeURIComponent(database), encodeURIComponent(partitionKey));
     }
-    let queryFunction = (params) => { return MangoAPI.mangoQueryDocs(databaseName, queryFindCode, params); };
+    let queryFunction = (params) => { return MangoAPI.mangoQueryDocs(databaseName, partitionKey, queryFindCode, params); };
     let docType = Constants.INDEX_RESULTS_DOC_TYPE.MANGO_QUERY;
     if (edit) {
       queryFunction = (params) => { return MangoAPI.fetchIndexes(databaseName, params); };
@@ -137,6 +179,12 @@ class MangoLayout extends Component {
           docURL={docURL}
           endpoint={endpoint}
           crumbs={crumbs}
+          databaseName={databaseName}
+          partitionKey={partitionKey}
+          showPartitionKeySelector={!edit}
+          onPartitionKeySelected={onPartitionKeySelected}
+          onGlobalModeSelected={onGlobalModeSelected}
+          globalMode={globalMode}
         />
         <MangoContent
           edit={edit}
@@ -146,6 +194,7 @@ class MangoLayout extends Component {
           fetchUrl={fetchUrl}
           queryDocs={queryFunction}
           docType={docType}
+          partitionKey={partitionKey}
         />
       </div>
     );
@@ -156,7 +205,11 @@ const mapStateToProps = ({ mangoQuery }, ownProps) => {
   return {
     explainPlan: mangoQuery.explainPlan,
     queryFindCode: mangoQuery.queryFindCode,
-    partitionKey: ownProps.partitionKey
+    partitionKey: ownProps.partitionKey,
+    databaseName: ownProps.databaseName,
+    onPartitionKeySelected: ownProps.onPartitionKeySelected,
+    onGlobalModeSelected: ownProps.onGlobalModeSelected,
+    globalMode: ownProps.globalMode
   };
 };
 
diff --git a/app/addons/documents/routes-documents.js b/app/addons/documents/routes-documents.js
index b86cb6b60..b8e9765ab 100644
--- a/app/addons/documents/routes-documents.js
+++ b/app/addons/documents/routes-documents.js
@@ -71,7 +71,7 @@ var DocumentsRouteObject = BaseRoute.extend({
       designDocName: ddoc,
       designDocSection: 'metadata'
     });
-
+    DatabaseActions.fetchSelectedDatabaseInfo(database);
     const dropDownLinks = this.getCrumbs(this.database);
     return <ViewsTabsSidebarLayout
       showEditView={false}
@@ -147,8 +147,9 @@ var DocumentsRouteObject = BaseRoute.extend({
     />;
   },
 
-  changes: function (_, partitionKey) {
+  changes: function (databaseName, partitionKey) {
     const selectedNavItem = new SidebarItemSelection('changes');
+    DatabaseActions.fetchSelectedDatabaseInfo(databaseName);
 
     return <ChangesSidebarLayout
       endpoint={FauxtonAPI.urls('changes', 'apiurl', this.database.id, '')}
diff --git a/app/addons/documents/routes-index-editor.js b/app/addons/documents/routes-index-editor.js
index 121afb512..321bb2f35 100644
--- a/app/addons/documents/routes-index-editor.js
+++ b/app/addons/documents/routes-index-editor.js
@@ -14,6 +14,7 @@ import React from 'react';
 import FauxtonAPI from "../../core/api";
 import BaseRoute from "./shared-routes";
 import ActionsIndexEditor from "./index-editor/actions";
+import DatabaseActions from '../databases/actions';
 import Databases from "../databases/base";
 import SidebarActions from './sidebar/actions';
 import {SidebarItemSelection} from './sidebar/helpers';
@@ -82,6 +83,7 @@ const IndexEditorAndResults = BaseRoute.extend({
       designDocs: this.designDocs,
       designDocId: '_design/' + ddoc
     });
+    DatabaseActions.fetchSelectedDatabaseInfo(databaseName);
 
     const selectedNavItem = new SidebarItemSelection('designDoc', {
       designDocName: ddoc,
@@ -144,6 +146,7 @@ const IndexEditorAndResults = BaseRoute.extend({
       designDocId: designDoc,
       isNewDesignDoc: isNewDesignDoc
     });
+    DatabaseActions.fetchSelectedDatabaseInfo(database);
 
     const selectedNavItem = new SidebarItemSelection('');
     const dropDownLinks = this.getCrumbs(this.database);
@@ -171,6 +174,7 @@ const IndexEditorAndResults = BaseRoute.extend({
       designDocs: this.designDocs,
       designDocId: '_design/' + ddocName
     });
+    DatabaseActions.fetchSelectedDatabaseInfo(databaseName);
 
     const selectedNavItem = new SidebarItemSelection('designDoc', {
       designDocName: ddocName,
diff --git a/app/addons/documents/routes-mango.js b/app/addons/documents/routes-mango.js
index 03495eeb4..4e555d6a4 100644
--- a/app/addons/documents/routes-mango.js
+++ b/app/addons/documents/routes-mango.js
@@ -11,10 +11,11 @@
 // the License.
 
 import React from 'react';
-import app from "../../app";
-import FauxtonAPI from "../../core/api";
-import Databases from "../databases/resources";
-import Documents from "./shared-resources";
+import app from '../../app';
+import FauxtonAPI from '../../core/api';
+import Databases from '../databases/resources';
+import DatabaseActions from '../databases/actions';
+import Documents from './shared-resources';
 import {MangoLayoutContainer} from './mangolayout';
 
 const MangoIndexEditorAndQueryEditor = FauxtonAPI.RouteObject.extend({
@@ -56,14 +57,25 @@ const MangoIndexEditorAndQueryEditor = FauxtonAPI.RouteObject.extend({
       'allDocs', 'app', encodeURIComponent(this.databaseName), encodedPartitionKey
     );
 
-    const fetchUrl = '/' + encodeURIComponent(this.databaseName) + '/_find';
+    const partKeyUrlComponent = partitionKey ? `/${encodeURIComponent(partitionKey)}` : '';
+    const fetchUrl = '/' + encodeURIComponent(this.databaseName) + partKeyUrlComponent + '/_find';
 
     const crumbs = [
       {name: database, link: url},
       {name: app.i18n.en_US['mango-title-editor']}
     ];
 
-    const endpoint = FauxtonAPI.urls('mango', 'query-apiurl', encodeURIComponent(this.databaseName));
+    const endpoint = FauxtonAPI.urls('mango', 'query-apiurl', encodeURIComponent(this.databaseName), encodedPartitionKey);
+
+    const navigateToPartitionedView = (partKey) => {
+      const baseUrl = FauxtonAPI.urls('mango', 'query-app', encodeURIComponent(database),
+        encodeURIComponent(partKey));
+      FauxtonAPI.navigate('#/' + baseUrl);
+    };
+    const navigateToGlobalView = () => {
+      const baseUrl = FauxtonAPI.urls('mango', 'query-app', encodeURIComponent(database));
+      FauxtonAPI.navigate('#/' + baseUrl);
+    };
 
     return <MangoLayoutContainer
       database={database}
@@ -71,9 +83,12 @@ const MangoIndexEditorAndQueryEditor = FauxtonAPI.RouteObject.extend({
       docURL={FauxtonAPI.constants.DOC_URLS.MANGO_SEARCH}
       endpoint={endpoint}
       edit={false}
-      partitionKey={partitionKey}
       databaseName={this.databaseName}
       fetchUrl={fetchUrl}
+      partitionKey={partitionKey}
+      onPartitionKeySelected={navigateToPartitionedView}
+      onGlobalModeSelected={navigateToGlobalView}
+      globalMode={partitionKey === ''}
     />;
   },
 
@@ -99,13 +114,15 @@ const MangoIndexEditorAndQueryEditor = FauxtonAPI.RouteObject.extend({
     const url = FauxtonAPI.urls(
       'allDocs', 'app', encodeURIComponent(this.databaseName), encodedPartitionKey
     );
-    const endpoint = FauxtonAPI.urls('mango', 'index-apiurl', encodeURIComponent(this.databaseName));
+    const endpoint = FauxtonAPI.urls('mango', 'index-apiurl', encodeURIComponent(this.databaseName), encodedPartitionKey);
 
     const crumbs = [
       {name: database, link: url},
       {name: app.i18n.en_US['mango-indexeditor-title']}
     ];
 
+    DatabaseActions.fetchSelectedDatabaseInfo(database);
+
     return <MangoLayoutContainer
       showIncludeAllDocs={false}
       crumbs={crumbs}
@@ -113,8 +130,8 @@ const MangoIndexEditorAndQueryEditor = FauxtonAPI.RouteObject.extend({
       endpoint={endpoint}
       edit={true}
       designDocs={designDocs}
-
       databaseName={this.databaseName}
+      partitionKey={partitionKey}
     />;
   }
 });


 

----------------------------------------------------------------
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