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/26 14:48:05 UTC

[GitHub] garrensmith closed pull request #1158: [partitioned dbs] Updates to index editor and index clone modal

garrensmith closed pull request #1158: [partitioned dbs] Updates to index editor and index clone modal
URL: https://github.com/apache/couchdb-fauxton/pull/1158
 
 
   

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/documents/assets/less/sidenav.less b/app/addons/documents/assets/less/sidenav.less
index df2ab1411..8ba51a53f 100644
--- a/app/addons/documents/assets/less/sidenav.less
+++ b/app/addons/documents/assets/less/sidenav.less
@@ -257,4 +257,8 @@
       }
     }
   }
+  .ddoc-selector-partitioned {
+    padding-top: 0px;
+    padding-bottom: 16px;
+  }
 }
diff --git a/app/addons/documents/assets/less/view-editor.less b/app/addons/documents/assets/less/view-editor.less
index 163c244d6..5c2314974 100644
--- a/app/addons/documents/assets/less/view-editor.less
+++ b/app/addons/documents/assets/less/view-editor.less
@@ -253,3 +253,16 @@ a.index-cancel-link {
     cursor: pointer;
   }
 }
+
+.ddoc-selector-partitioned {
+  clear: both;
+  padding-top: 16px;
+
+  label.check--disabled {
+    cursor: default
+  }
+}
+
+.reduce-editor-warning {
+  padding-bottom: 1rem;
+}
diff --git a/app/addons/documents/base.js b/app/addons/documents/base.js
index 48be8e3f0..5fa6613d5 100644
--- a/app/addons/documents/base.js
+++ b/app/addons/documents/base.js
@@ -135,8 +135,8 @@ FauxtonAPI.registerUrls('view', {
     return 'database/' + database + partitionUrlComponent(partitionKey) + '/_design/' + designDoc + '/_view/' + indexName + '/edit';
   },
 
-  showView: function (database, designDoc, viewName) {
-    return '/database/' + database + '/' + designDoc + '/_view/' + viewName;
+  showView: function (database, partitionKey, designDoc, viewName) {
+    return '/database/' + database + partitionUrlComponent(partitionKey) + '/' + designDoc + '/_view/' + viewName;
   },
 
   fragment: function (database, designDoc, viewName) {
diff --git a/app/addons/documents/helpers.js b/app/addons/documents/helpers.js
index 69347aa79..a5c592113 100644
--- a/app/addons/documents/helpers.js
+++ b/app/addons/documents/helpers.js
@@ -128,11 +128,22 @@ const isViewSelected = (selectedNavItem) => {
     && selectedNavItem.indexName);
 };
 
+const isDDocPartitioned = (ddoc, isDbPartitioned) => {
+  // By default a design doc is partitioned if the database is partitioned
+  let isDDocPartitioned = isDbPartitioned;
+  // Check if ddoc is explictly set to not partitioned
+  if (isDbPartitioned && ddoc.options && ddoc.options.partitioned === false) {
+    isDDocPartitioned = false;
+  }
+  return isDDocPartitioned;
+};
+
 export default {
   getSeqNum,
   getNewButtonLinks,
   getModifyDatabaseLinks,
   getNewDocUrl,
+  isDDocPartitioned,
   parseJSON,
   truncateDoc,
   selectedViewContainsReduceFunction,
diff --git a/app/addons/documents/index-editor/__tests__/components.test.js b/app/addons/documents/index-editor/__tests__/components.test.js
index fde338c18..a3d4c207c 100644
--- a/app/addons/documents/index-editor/__tests__/components.test.js
+++ b/app/addons/documents/index-editor/__tests__/components.test.js
@@ -11,7 +11,7 @@
 // the License.
 
 import React from 'react';
-import {mount} from 'enzyme';
+import {mount, shallow} from 'enzyme';
 import sinon from 'sinon';
 import FauxtonAPI from '../../../../core/api';
 import Views from '../components';
@@ -27,19 +27,20 @@ describe('ReduceEditor', () => {
       hasCustomReduce: false,
       reduce: null,
       reduceSelectedOption: 'NONE',
+      customReducerSupported: true,
       updateReduceCode: () => {},
       selectReduceChanged: () => {}
     };
 
     it('returns null for none', () => {
-      const reduceEl = mount(<Views.ReduceEditor
+      const reduceEl = shallow(<Views.ReduceEditor
         {...defaultProps}
       />);
       expect(reduceEl.instance().getReduceValue()).toBeNull();
     });
 
     it('returns built in for built in reduce', () => {
-      const reduceEl = mount(<Views.ReduceEditor
+      const reduceEl = shallow(<Views.ReduceEditor
         {...defaultProps}
         reduce='_sum'
         hasReduce={true}
@@ -47,20 +48,38 @@ describe('ReduceEditor', () => {
       expect(reduceEl.instance().getReduceValue()).toBe('_sum');
     });
 
+    it('shows warning when custom reduce is not supported', () => {
+      const reduceEl = shallow(<Views.ReduceEditor
+        {...defaultProps}
+        reduce='function() {}'
+        hasReduce={true}
+        customReducerSupported={false}
+      />);
+      expect(reduceEl.find('div.reduce-editor-warning').exists()).toBe(true);
+    });
+
   });
 });
 
 describe('DesignDocSelector component', () => {
+  const defaultProps = {
+    designDocList: ['_design/test-doc', '_design/test-doc2'],
+    newDesignDocName: '',
+    selectedDesignDocPartitioned: false,
+    isDbPartitioned: false,
+    newDesignDocPartitioned: false,
+    onChangeNewDesignDocName: () => {},
+    onSelectDesignDoc: () => {}
+  };
   let selectorEl;
 
   it('calls onSelectDesignDoc on change', () => {
     const spy = sinon.spy();
     selectorEl = mount(
       <Views.DesignDocSelector
-        designDocList={['_design/test-doc', '_design/test-doc2']}
+        {...defaultProps}
         selectedDDocName={'new-doc'}
         onSelectDesignDoc={spy}
-        onChangeNewDesignDocName={() => {}}
       />);
 
     selectorEl.find('.styled-select select').first().simulate('change', {
@@ -74,10 +93,8 @@ describe('DesignDocSelector component', () => {
   it('shows new design doc field when set to new-doc', () => {
     selectorEl = mount(
       <Views.DesignDocSelector
-        designDocList={['_design/test-doc']}
+        {...defaultProps}
         selectedDesignDocName={'new-doc'}
-        onSelectDesignDoc={() => { }}
-        onChangeNewDesignDocName={() => {}}
       />);
 
     expect(selectorEl.find('#new-ddoc-section').length).toBe(1);
@@ -86,10 +103,8 @@ describe('DesignDocSelector component', () => {
   it('hides new design doc field when design doc selected', () => {
     selectorEl = mount(
       <Views.DesignDocSelector
-        designDocList={['_design/test-doc']}
+        {...defaultProps}
         selectedDesignDocName={'_design/test-doc'}
-        onSelectDesignDoc={() => { }}
-        onChangeNewDesignDocName={() => {}}
       />);
 
     expect(selectorEl.find('#new-ddoc-section').length).toBe(0);
@@ -98,10 +113,8 @@ describe('DesignDocSelector component', () => {
   it('always passes validation when design doc selected', () => {
     selectorEl = mount(
       <Views.DesignDocSelector
-        designDocList={['_design/test-doc']}
+        {...defaultProps}
         selectedDesignDocName={'_design/test-doc'}
-        onSelectDesignDoc={() => { }}
-        onChangeNewDesignDocName={() => {}}
       />);
 
     expect(selectorEl.instance().validate()).toBe(true);
@@ -110,11 +123,9 @@ describe('DesignDocSelector component', () => {
   it('fails validation if new doc name entered/not entered', () => {
     selectorEl = mount(
       <Views.DesignDocSelector
-        designDocList={['_design/test-doc']}
+        {...defaultProps}
         selectedDesignDocName={'new-doc'}
         newDesignDocName=''
-        onSelectDesignDoc={() => { }}
-        onChangeNewDesignDocName={() => {}}
       />);
 
     // it shouldn't validate at this point: no new design doc name has been entered
@@ -124,11 +135,9 @@ describe('DesignDocSelector component', () => {
   it('passes validation if new doc name entered/not entered', () => {
     selectorEl = mount(
       <Views.DesignDocSelector
-        designDocList={['_design/test-doc']}
+        {...defaultProps}
         selectedDesignDocName={'new-doc'}
         newDesignDocName='new-doc-name'
-        onSelectDesignDoc={() => { }}
-        onChangeNewDesignDocName={() => {}}
       />);
     expect(selectorEl.instance().validate()).toBe(true);
   });
@@ -137,10 +146,8 @@ describe('DesignDocSelector component', () => {
   it('omits doc URL when not supplied', () => {
     selectorEl = mount(
       <Views.DesignDocSelector
-        designDocList={['_design/test-doc']}
+        {...defaultProps}
         selectedDesignDocName={'new-doc'}
-        onSelectDesignDoc={() => { }}
-        onChangeNewDesignDocName={() => {}}
       />);
     expect(selectorEl.find('.help-link').length).toBe(0);
   });
@@ -149,15 +156,39 @@ describe('DesignDocSelector component', () => {
     const docLink = 'http://docs.com';
     selectorEl = mount(
       <Views.DesignDocSelector
-        designDocList={['_design/test-doc']}
+        {...defaultProps}
         selectedDesignDocName={'new-doc'}
-        onSelectDesignDoc={() => { }}
         docLink={docLink}
-        onChangeNewDesignDocName={() => {}}
       />);
     expect(selectorEl.find('.help-link').length).toBe(1);
     expect(selectorEl.find('.help-link').prop('href')).toBe(docLink);
   });
+
+  it('shows Partitioned checkbox only when db is partitioned', () => {
+    selectorEl = mount(
+      <Views.DesignDocSelector
+        {...defaultProps}
+        selectedDesignDocName={'new-doc'}
+      />);
+
+    expect(selectorEl.find('div.ddoc-selector-partitioned').exists()).toBe(false);
+
+    selectorEl.setProps({isDbPartitioned: true});
+    expect(selectorEl.find('div.ddoc-selector-partitioned').exists()).toBe(true);
+  });
+
+  it('calls onChangeNewDesignDocPartitioned when partitioned option changes', () => {
+    const spy = sinon.stub();
+    selectorEl = mount(
+      <Views.DesignDocSelector
+        {...defaultProps}
+        selectedDesignDocName={'new-doc'}
+        isDbPartitioned={true}
+        onChangeNewDesignDocPartitioned={spy}
+      />);
+    selectorEl.find('input#js-ddoc-selector-partitioned').simulate('change');
+    sinon.assert.called(spy);
+  });
 });
 
 describe('IndexEditor', () => {
@@ -165,12 +196,16 @@ describe('IndexEditor', () => {
     isLoading: false,
     isNewView: false,
     isNewDesignDoc: false,
+    isDbPartitioned: false,
     viewName: '',
-    database: {},
+    database: { safeID: () => 'test_db' },
+    newDesignDocName: '',
+    newDesignDocPartitioned: false,
     originalViewName: '',
     originalDesignDocName: '',
     designDoc: {},
     designDocId: '',
+    designDocPartitioned: false,
     designDocList: [],
     map: '',
     reduce: '',
@@ -179,6 +214,7 @@ describe('IndexEditor', () => {
     updateMapCode: () => {},
     selectDesignDoc: () => {},
     onChangeNewDesignDocName: () => {},
+    changeViewName: () => {},
     reduceOptions: [],
     reduceSelectedOption: 'NONE',
     hasReduce: false,
@@ -187,6 +223,10 @@ describe('IndexEditor', () => {
     selectReduceChanged: () => {}
   };
 
+  afterEach(() => {
+    sinon.restore();
+  });
+
   it('calls changeViewName on view name change', () => {
     const spy = sinon.spy();
     const editorEl = mount(<Views.IndexEditor
@@ -202,4 +242,23 @@ describe('IndexEditor', () => {
     });
     sinon.assert.calledWith(spy, 'newViewName');
   });
+
+  it('shows warning when trying to save a partitioned view with custom reduce', () => {
+    sinon.stub(FauxtonAPI, 'addNotification');
+    const editorEl = mount(<Views.IndexEditor
+      {...defaultProps}
+      viewName='new-name'
+      designDocId='new-doc'
+      newDesignDocName='test_ddoc'
+      newDesignDocPartitioned={true}
+      isDbPartitioned={true}
+      reduce='function() { /*custom reduce*/ }'
+    />);
+
+    editorEl.find('form.view-query-save').simulate('submit', {
+      preventDefault: () => {}
+    });
+    sinon.assert.calledWithMatch(FauxtonAPI.addNotification, { msg: 'Partitioned views do not support custom reduce functions.' });
+  });
+
 });
diff --git a/app/addons/documents/index-editor/__tests__/reducers.test.js b/app/addons/documents/index-editor/__tests__/reducers.test.js
index ee60b4ec3..c57901097 100644
--- a/app/addons/documents/index-editor/__tests__/reducers.test.js
+++ b/app/addons/documents/index-editor/__tests__/reducers.test.js
@@ -11,7 +11,8 @@
 // the License.
 
 import Documents from '../../../documents/resources';
-import reducer, { hasCustomReduce, getDesignDocIds } from '../reducers';
+import reducer, { hasCustomReduce, getDesignDocList, getSelectedDesignDocPartitioned,
+  getSaveDesignDoc } from '../reducers';
 import ActionTypes from '../actiontypes';
 import '../../base';
 
@@ -252,7 +253,7 @@ describe('IndexEditor Reducer', () => {
 
     it('only filters mango docs', () => {
       const newState = reducer(undefined, editAction);
-      const designDocs = getDesignDocIds(newState);
+      const designDocs = getDesignDocList(newState);
 
       expect(designDocs.length).toBe(1);
       expect(designDocs[0]).toBe('_design/test-doc');
@@ -296,4 +297,55 @@ describe('IndexEditor Reducer', () => {
       expect(newState.view.reduce).toBe('_sum');
     });
   });
+
+  describe('getSelectedDesignDocPartitioned', () => {
+    const designDocs = [
+      {id: '_design/docGlobal', get: () => { return {options: { partitioned: false }}; }},
+      {id: '_design/docPartitioned', get: () => { return {options: { partitioned: true }}; }},
+      {id: '_design/docNoOptions', get: () => { return {};}}
+    ];
+
+    it('returns true for ddocs without partitioned flag on partitioned dbs', () => {
+      const isDbPartitioned = true;
+      const state = { designDocs, designDocId: '_design/docNoOptions' };
+      expect(getSelectedDesignDocPartitioned(state, isDbPartitioned)).toBe(true);
+    });
+
+    it('returns false for ddocs where partitioned is false on partitioned dbs', () => {
+      const isDbPartitioned = true;
+      const state = { designDocs, designDocId: '_design/docGlobal' };
+      expect(getSelectedDesignDocPartitioned(state, isDbPartitioned)).toBe(false);
+    });
+
+    it('returns true for ddocs where partitioned is true on partitioned dbs', () => {
+      const isDbPartitioned = true;
+      const state = { designDocs, designDocId: '_design/docPartitioned' };
+      expect(getSelectedDesignDocPartitioned(state, isDbPartitioned)).toBe(true);
+    });
+
+    it('any ddoc is global on non-partitioned dbs', () => {
+      const isDbPartitioned = false;
+      const state = { designDocs, designDocId: '_design/docGlobal' };
+      expect(getSelectedDesignDocPartitioned(state, isDbPartitioned)).toBe(false);
+
+      const state2 = { designDocs, designDocId: '_design/docPartitioned' };
+      expect(getSelectedDesignDocPartitioned(state2, isDbPartitioned)).toBe(false);
+
+      const state3 = { designDocs, designDocId: '_design/docNoOptions' };
+      expect(getSelectedDesignDocPartitioned(state3, isDbPartitioned)).toBe(false);
+    });
+
+  });
+
+  describe('getSaveDesignDoc', () => {
+
+    it('only sets partitioned flag when db is partitioned', () => {
+      const state = { designDocId: 'new-doc', newDesignDocPartitioned: true, };
+      const ddoc = getSaveDesignDoc(state, true);
+      expect(ddoc.get('options')).toEqual({ partitioned: true });
+
+      const ddoc2 = getSaveDesignDoc(state, false);
+      expect(ddoc2.get('options')).toBeUndefined();
+    });
+  });
 });
diff --git a/app/addons/documents/index-editor/actions.js b/app/addons/documents/index-editor/actions.js
index ad261e0d2..a6cf7e75d 100644
--- a/app/addons/documents/index-editor/actions.js
+++ b/app/addons/documents/index-editor/actions.js
@@ -10,7 +10,6 @@
 // License for the specific language governing permissions and limitations under
 // the License.
 
-import app from '../../../app';
 import FauxtonAPI from '../../../core/api';
 import Documents from '../resources';
 import ActionTypes from './actiontypes';
@@ -65,7 +64,7 @@ const shouldRemoveDdocView = (viewInfo) => {
           viewInfo.originalViewName !== viewInfo.viewName;
 };
 
-const saveView = (viewInfo) => (dispatch) => {
+const saveView = (viewInfo, navigateToURL) => (dispatch) => {
   const designDoc = viewInfo.designDoc;
   designDoc.setDdocView(viewInfo.viewName, viewInfo.map, viewInfo.reduce);
 
@@ -103,8 +102,7 @@ const saveView = (viewInfo) => (dispatch) => {
     }
     SidebarActions.dispatchUpdateDesignDocs(viewInfo.designDocs);
     dispatch({ type: ActionTypes.VIEW_SAVED });
-    const fragment = FauxtonAPI.urls('view', 'showView', viewInfo.database.safeID(), designDoc.safeID(), app.utils.safeURLName(viewInfo.viewName));
-    FauxtonAPI.navigate(fragment, { trigger: true });
+    FauxtonAPI.navigate(navigateToURL, { trigger: true });
   }, (xhr) => {
     FauxtonAPI.addNotification({
       msg: 'Save failed. ' + (xhr.responseJSON ? `Reason: ${xhr.responseJSON.reason}` : ''),
@@ -146,7 +144,8 @@ const deleteView = (options) => {
 };
 
 const cloneView = (params) => {
-  const targetDesignDoc = getDesignDoc(params.designDocs, params.targetDesignDocName, params.newDesignDocName, params.database);
+  const targetDesignDoc = getDesignDoc(params.designDocs, params.targetDesignDocName, params.newDesignDocName,
+    params.newDesignDocPartitioned, params.database, params.isDbPartitioned);
   let indexes = targetDesignDoc.get('views');
   if (indexes && _.has(indexes, params.newIndexName)) {
     FauxtonAPI.addNotification({
@@ -223,6 +222,15 @@ const updateNewDesignDocName = (designDocName) => (dispatch) => {
   });
 };
 
+const updateNewDesignDocPartitioned = (isPartitioned) => (dispatch) => {
+  dispatch({
+    type: ActionTypes.DESIGN_DOC_NEW_PARTITIONED_UPDATED,
+    options: {
+      value: isPartitioned
+    }
+  });
+};
+
 // safely deletes an index of any type. It only deletes the actual design doc if there are no
 // other indexes of any type left in the doc
 const safeDeleteIndex = (designDoc, designDocs, indexPropName, indexName, options) => {
@@ -277,14 +285,18 @@ const findDesignDoc = (designDocs, designDocName) => {
   }).dDocModel();
 };
 
-const getDesignDoc = (designDocs, targetDesignDocName, newDesignDocName, database) => {
+const getDesignDoc = (designDocs, targetDesignDocName, newDesignDocName, newDesignDocPartitioned, database, isDbPartitioned) => {
   if (targetDesignDocName === 'new-doc') {
     const doc = {
       "_id": "_design/" + newDesignDocName,
       "views": {},
       "language": "javascript"
     };
-    return new Documents.Doc(doc, { database: database });
+    const dDoc = new Documents.Doc(doc, { database: database });
+    if (isDbPartitioned) {
+      dDoc.setDDocPartitionedOption(newDesignDocPartitioned);
+    }
+    return dDoc;
   }
 
   const foundDoc = designDocs.find(function (ddoc) {
@@ -314,5 +326,6 @@ export default {
   updateMapCode,
   updateReduceCode,
   selectDesignDoc,
-  updateNewDesignDocName
+  updateNewDesignDocName,
+  updateNewDesignDocPartitioned
 };
diff --git a/app/addons/documents/index-editor/actiontypes.js b/app/addons/documents/index-editor/actiontypes.js
index 920bd9f35..ef746dfc4 100644
--- a/app/addons/documents/index-editor/actiontypes.js
+++ b/app/addons/documents/index-editor/actiontypes.js
@@ -19,6 +19,7 @@ export default {
   VIEW_CREATED: 'VIEW_CREATED',
   DESIGN_DOC_CHANGE: 'DESIGN_DOC_CHANGE',
   DESIGN_DOC_NEW_NAME_UPDATED: 'DESIGN_DOC_NEW_NAME_UPDATED',
+  DESIGN_DOC_NEW_PARTITIONED_UPDATED: 'DESIGN_DOC_NEW_PARTITIONED_UPDATED',
   NEW_DESIGN_DOC: 'NEW_DESIGN_DOC',
   VIEW_NAME_CHANGE: 'VIEW_NAME_CHANGE',
   VIEW_ADD_DESIGN_DOC: 'VIEW_ADD_DESIGN_DOC',
diff --git a/app/addons/documents/index-editor/components/DesignDocSelector.js b/app/addons/documents/index-editor/components/DesignDocSelector.js
index 57c6bbcd8..a524c328e 100644
--- a/app/addons/documents/index-editor/components/DesignDocSelector.js
+++ b/app/addons/documents/index-editor/components/DesignDocSelector.js
@@ -21,6 +21,7 @@ export default class DesignDocSelector extends Component {
 
   constructor(props) {
     super(props);
+    this.onTogglePartitioned = this.onTogglePartitioned.bind(this);
   }
 
   validate() {
@@ -80,6 +81,39 @@ export default class DesignDocSelector extends Component {
     );
   }
 
+  onTogglePartitioned() {
+    this.props.onChangeNewDesignDocPartitioned(!this.props.newDesignDocPartitioned);
+  }
+
+  getPartitionedCheckbox() {
+    if (!this.props.isDbPartitioned) {
+      return null;
+    }
+    const isExistingDDoc = this.props.selectedDesignDocName !== 'new-doc';
+    const checked = isExistingDDoc ?
+      this.props.selectedDesignDocPartitioned :
+      this.props.newDesignDocPartitioned;
+    const labelClass = isExistingDDoc ? 'check--disabled' : '';
+    const inputTitle = isExistingDDoc ?
+      (this.props.selectedDesignDocPartitioned ? 'Design document is partitioned' : 'Design document is not partitioned') :
+      (this.props.newDesignDocPartitioned ? 'New document will be partitioned' : 'New document will not be partitioned');
+    return (
+      <div className="ddoc-selector-partitioned">
+        <label className={labelClass} title={inputTitle}>
+          <input
+            id="js-ddoc-selector-partitioned"
+            type="checkbox"
+            title={inputTitle}
+            checked={checked}
+            onChange={this.onTogglePartitioned}
+            style={{margin: '0px 10px 0px 0px'}}
+            disabled={isExistingDDoc}/>
+          Partitioned
+        </label>
+      </div>
+    );
+  }
+
   render() {
     const selectContent =
       <optgroup label="Select a document">
@@ -101,6 +135,7 @@ export default class DesignDocSelector extends Component {
           />
         </div>
         {this.getNewDDocField()}
+        {this.getPartitionedCheckbox()}
       </div>
     );
   }
@@ -117,7 +152,9 @@ DesignDocSelector.propTypes = {
   onSelectDesignDoc: PropTypes.func.isRequired,
   onChangeNewDesignDocName: PropTypes.func.isRequired,
   selectedDesignDocName: PropTypes.string.isRequired,
+  selectedDesignDocPartitioned: PropTypes.bool.isRequired,
   newDesignDocName: PropTypes.string.isRequired,
+  newDesignDocPartitioned: PropTypes.bool.isRequired,
   designDocLabel: PropTypes.string,
   docURL: PropTypes.string
 };
diff --git a/app/addons/documents/index-editor/components/IndexEditor.js b/app/addons/documents/index-editor/components/IndexEditor.js
index 332b74900..2d1a2cc46 100644
--- a/app/addons/documents/index-editor/components/IndexEditor.js
+++ b/app/addons/documents/index-editor/components/IndexEditor.js
@@ -25,6 +25,9 @@ export default class IndexEditor extends Component {
 
   constructor(props) {
     super(props);
+    this.saveView = this.saveView.bind(this);
+    this.viewChange = this.viewChange.bind(this);
+    this.updateMapCode = this.updateMapCode.bind(this);
   }
 
   // the code editor is a standalone component, so if the user goes from one edit view page to another, we need to
@@ -35,6 +38,21 @@ export default class IndexEditor extends Component {
     }
   }
 
+  isPartitionedView() {
+    if (this.props.designDocId === 'new-doc') {
+      return this.props.newDesignDocPartitioned;
+    }
+    return this.props.designDocPartitioned;
+  }
+
+  isCustomReduceSupported() {
+    if (this.props.isDbPartitioned && this.props.reduce && !this.props.reduce.startsWith('_')) {
+      const isDDocPartitioned = this.props.designDocId === 'new-doc' ? this.props.newDesignDocPartitioned : this.props.designDocPartitioned;
+      return isDDocPartitioned ? false : true;
+    }
+    return true;
+  }
+
   saveView(el) {
     el.preventDefault();
 
@@ -42,6 +60,18 @@ export default class IndexEditor extends Component {
       return;
     }
 
+    if (!this.isCustomReduceSupported()) {
+      FauxtonAPI.addNotification({
+        msg: 'Partitioned views do not support custom reduce functions.',
+        type: 'error',
+        clear: true
+      });
+      return;
+    }
+
+    const encodedPartKey = this.isPartitionedView() && this.props.partitionKey ? encodeURIComponent(this.props.partitionKey) : '';
+    const url = FauxtonAPI.urls('view', 'showView', this.props.database.safeID(), encodedPartKey,
+      this.props.saveDesignDoc.safeID(), encodeURIComponent(this.props.viewName));
     this.props.saveView({
       database: this.props.database,
       isNewView: this.props.isNewView,
@@ -54,7 +84,7 @@ export default class IndexEditor extends Component {
       map: this.mapEditor.getValue(),
       reduce: this.reduceEditor.getReduceValue(),
       designDocs: this.props.designDocs
-    });
+    }, url);
   }
 
   viewChange(el) {
@@ -65,6 +95,17 @@ export default class IndexEditor extends Component {
     this.props.updateMapCode(code);
   }
 
+  getCancelLink() {
+    const encodedDatabase = encodeURIComponent(this.props.database.id);
+    const encodedPartitionKey = this.props.partitionKey ? encodeURIComponent(this.props.partitionKey) : '';
+    const encodedDDoc = encodeURIComponent(this.props.designDocId);
+    const encodedView = encodeURIComponent(this.props.viewName);
+    if (this.props.designDocId === 'new-doc' || this.props.isNewView) {
+      return '#' + FauxtonAPI.urls('allDocs', 'app', encodedDatabase, encodedPartitionKey);
+    }
+    return '#' + FauxtonAPI.urls('view', 'showView', encodedDatabase, encodedPartitionKey, encodedDDoc, encodedView);
+  }
+
   render() {
     if (this.props.isLoading) {
       return (
@@ -76,20 +117,23 @@ export default class IndexEditor extends Component {
 
     const pageHeader = (this.props.isNewView) ? 'New View' : 'Edit View';
     const btnLabel = (this.props.isNewView) ? 'Create Document and then Build Index' : 'Save Document and then Build Index';
-    const cancelLink = '#' + FauxtonAPI.urls('view', 'showView', this.props.database.id, this.props.designDocId, this.props.viewName);
     return (
       <div className="define-view" >
-        <form className="form-horizontal view-query-save" onSubmit={this.saveView.bind(this)}>
+        <form className="form-horizontal view-query-save" onSubmit={this.saveView}>
           <h3 className="simple-header">{pageHeader}</h3>
 
           <div className="new-ddoc-section">
             <DesignDocSelector
               ref={(el) => { this.designDocSelector = el; }}
               designDocList={this.props.designDocList}
+              isDbPartitioned={this.props.isDbPartitioned}
               selectedDesignDocName={this.props.designDocId}
+              selectedDesignDocPartitioned={this.props.designDocPartitioned}
               newDesignDocName={this.props.newDesignDocName}
+              newDesignDocPartitioned={this.props.newDesignDocPartitioned}
               onSelectDesignDoc={this.props.selectDesignDoc}
               onChangeNewDesignDocName={this.props.updateNewDesignDocName}
+              onChangeNewDesignDocPartitioned={this.props.updateNewDesignDocPartitioned}
               docLink={getDocUrl('DESIGN_DOCS')} />
           </div>
 
@@ -109,7 +153,7 @@ export default class IndexEditor extends Component {
               type="text"
               id="index-name"
               value={this.props.viewName}
-              onChange={this.viewChange.bind(this)}
+              onChange={this.viewChange}
               placeholder="Index name" />
           </div>
           <CodeEditorPanel
@@ -117,14 +161,17 @@ export default class IndexEditor extends Component {
             ref={(el) => { this.mapEditor = el; }}
             title={"Map function"}
             docLink={getDocUrl('MAP_FUNCS')}
-            blur={this.updateMapCode.bind(this)}
+            blur={this.updateMapCode}
             allowZenMode={false}
             defaultCode={this.props.map} />
-          <ReduceEditor ref={(el) => { this.reduceEditor = el; }} {...this.props} />
+          <ReduceEditor
+            ref={(el) => { this.reduceEditor = el; }}
+            customReducerSupported={this.isCustomReduceSupported()}
+            {...this.props} />
           <div className="padded-box">
             <div className="control-group">
               <ConfirmButton id="save-view" text={btnLabel} />
-              <a href={cancelLink} className="index-cancel-link">Cancel</a>
+              <a href={this.getCancelLink()} className="index-cancel-link">Cancel</a>
             </div>
           </div>
         </form>
@@ -137,13 +184,16 @@ IndexEditor.propTypes = {
   isLoading:PropTypes.bool.isRequired,
   isNewView: PropTypes.bool.isRequired,
   database: PropTypes.object.isRequired,
+  isDbPartitioned: PropTypes.bool.isRequired,
   designDocId: PropTypes.string.isRequired,
+  newDesignDocName: PropTypes.string.isRequired,
   viewName: PropTypes.string.isRequired,
   isNewDesignDoc: PropTypes.bool.isRequired,
   originalViewName: PropTypes.string,
   originalDesignDocName: PropTypes.string,
   designDocs: PropTypes.object,
   saveDesignDoc: PropTypes.object,
+  partitionKey: PropTypes.string,
   updateNewDesignDocName: PropTypes.func.isRequired,
   changeViewName: PropTypes.func.isRequired,
   updateMapCode: PropTypes.func.isRequired
diff --git a/app/addons/documents/index-editor/components/IndexEditorContainer.js b/app/addons/documents/index-editor/components/IndexEditorContainer.js
index 445b02954..2e7a13636 100644
--- a/app/addons/documents/index-editor/components/IndexEditorContainer.js
+++ b/app/addons/documents/index-editor/components/IndexEditorContainer.js
@@ -13,35 +13,41 @@
 import { connect } from 'react-redux';
 import IndexEditor from './IndexEditor';
 import Actions from '../actions';
-import { getSaveDesignDoc, getDesignDocIds, reduceSelectedOption, hasCustomReduce } from '../reducers';
+import { getSaveDesignDoc, getDesignDocList, reduceSelectedOption, hasCustomReduce,
+  getSelectedDesignDocPartitioned } from '../reducers';
 
-const mapStateToProps = ({ indexEditor }) => {
+const mapStateToProps = ({ indexEditor, databases }, ownProps) => {
+  const isSelectedDDocPartitioned = getSelectedDesignDocPartitioned(indexEditor, databases.isDbPartitioned);
   return {
     database: indexEditor.database,
     isNewView: indexEditor.isNewView,
     viewName: indexEditor.viewName,
     designDocs: indexEditor.designDocs,
-    designDocList: getDesignDocIds(indexEditor),
+    designDocList: getDesignDocList(indexEditor),
     originalViewName: indexEditor.originalViewName,
     originalDesignDocName: indexEditor.originalDesignDocName,
     isNewDesignDoc: indexEditor.isNewDesignDoc,
     designDocId: indexEditor.designDocId,
+    designDocPartitioned: isSelectedDDocPartitioned,
     newDesignDocName: indexEditor.newDesignDocName,
-    saveDesignDoc: getSaveDesignDoc(indexEditor),
+    newDesignDocPartitioned: indexEditor.newDesignDocPartitioned,
+    saveDesignDoc: getSaveDesignDoc(indexEditor, databases.isDbPartitioned),
     map: indexEditor.view.map,
     isLoading: indexEditor.isLoading,
     reduce: indexEditor.view.reduce,
     reduceOptions: indexEditor.reduceOptions,
     reduceSelectedOption: reduceSelectedOption(indexEditor),
     hasCustomReduce: hasCustomReduce(indexEditor),
-    hasReduce: !!indexEditor.view.reduce
+    hasReduce: !!indexEditor.view.reduce,
+    isDbPartitioned: databases.isDbPartitioned,
+    partitionKey: ownProps.partitionKey
   };
 };
 
 const mapDispatchToProps = (dispatch) => {
   return {
-    saveView: (viewInfo) => {
-      dispatch(Actions.saveView(viewInfo));
+    saveView: (viewInfo, navigateToURL) => {
+      dispatch(Actions.saveView(viewInfo, navigateToURL));
     },
 
     changeViewName: (name) => {
@@ -60,6 +66,10 @@ const mapDispatchToProps = (dispatch) => {
       dispatch(Actions.updateNewDesignDocName(designDocName));
     },
 
+    updateNewDesignDocPartitioned: (isPartitioned) => {
+      dispatch(Actions.updateNewDesignDocPartitioned(isPartitioned));
+    },
+
     updateReduceCode: (code) => {
       dispatch(Actions.updateReduceCode(code));
     },
diff --git a/app/addons/documents/index-editor/components/ReduceEditor.js b/app/addons/documents/index-editor/components/ReduceEditor.js
index 7f1df5255..252057676 100644
--- a/app/addons/documents/index-editor/components/ReduceEditor.js
+++ b/app/addons/documents/index-editor/components/ReduceEditor.js
@@ -58,7 +58,7 @@ export default class ReduceEditor extends Component {
     const reduceOptions = this.getOptionsList();
     let customReduceSection;
 
-    if (this.props.hasCustomReduce) {
+    if (this.props.hasCustomReduce && this.props.customReducerSupported) {
       customReduceSection = <CodeEditorPanel
         ref={node => this.reduceEditor = node}
         id='reduce-function'
@@ -67,6 +67,12 @@ export default class ReduceEditor extends Component {
         allowZenMode={false}
         blur={this.updateReduceCode.bind(this)}
       />;
+    } else if (!this.props.customReducerSupported) {
+      customReduceSection = (
+        <div className="reduce-editor-warning">
+          <label>Partitioned views do not support custom reduce functions.</label>
+        </div>
+      );
     }
 
     return (
@@ -96,10 +102,15 @@ export default class ReduceEditor extends Component {
   }
 }
 
+ReduceEditor.defaultProps = {
+  customReducerSupported: true
+};
+
 ReduceEditor.propTypes = {
   reduceOptions: PropTypes.array.isRequired,
   hasReduce: PropTypes.bool.isRequired,
   hasCustomReduce: PropTypes.bool.isRequired,
+  customReducerSupported: PropTypes.bool,
   reduce: PropTypes.string,
   reduceSelectedOption: PropTypes.string.isRequired,
   updateReduceCode: PropTypes.func.isRequired,
diff --git a/app/addons/documents/index-editor/reducers.js b/app/addons/documents/index-editor/reducers.js
index 663818fca..51988f6eb 100644
--- a/app/addons/documents/index-editor/reducers.js
+++ b/app/addons/documents/index-editor/reducers.js
@@ -11,11 +11,13 @@
 // the License.
 
 import ActionTypes from './actiontypes';
-import Resources from "../resources";
+import Resources from '../resources';
+import Helpers from '../helpers';
 
 const defaultMap = 'function (doc) {\n  emit(doc._id, 1);\n}';
 const defaultReduce = 'function (keys, values, rereduce) {\n  if (rereduce) {\n    return sum(values);\n  } else {\n    return values.length;\n  }\n}';
-const builtInReducers = ['_sum', '_count', '_stats'];
+const builtInReducers = ['_sum', '_count', '_stats', '_approx_count_distinct'];
+const allReducers = builtInReducers.concat(['CUSTOM', 'NONE']);
 
 const initialState = {
   designDocs: new Backbone.Collection(),
@@ -25,11 +27,12 @@ const initialState = {
   designDocId: '',
   isNewDesignDoc: false,
   newDesignDocName: '',
+  newDesignDocPartitioned: true,
   isNewView: false,
   viewName: '',
   originalViewName: '',
   originalDesignDocName: '',
-  reduceOptions: builtInReducers.concat(['CUSTOM', 'NONE'])
+  reduceOptions: allReducers
 };
 
 function editIndex(state, options) {
@@ -61,6 +64,16 @@ function getView(state) {
   return designDoc.get('views')[state.viewName];
 }
 
+export function getSelectedDesignDocPartitioned(state, isDbPartitioned) {
+  const designDoc = state.designDocs.find(ddoc => {
+    return state.designDocId === ddoc.id;
+  });
+  if (designDoc) {
+    return Helpers.isDDocPartitioned(designDoc.get('doc'), isDbPartitioned);
+  }
+  return false;
+}
+
 export function reduceSelectedOption(state) {
   if (!state.view.reduce) {
     return 'NONE';
@@ -78,14 +91,18 @@ export function hasCustomReduce(state) {
   return false;
 }
 
-export function getSaveDesignDoc(state) {
+export function getSaveDesignDoc(state, isDbPartitioned) {
   if (state.designDocId === 'new-doc') {
     const doc = {
       _id: '_design/' + state.newDesignDocName,
       views: {},
       language: 'javascript'
     };
-    return new Resources.Doc(doc, { database: state.database });
+    const dDoc = new Resources.Doc(doc, { database: state.database });
+    if (isDbPartitioned) {
+      dDoc.setDDocPartitionedOption(state.newDesignDocPartitioned);
+    }
+    return dDoc;
   }
 
   if (!state.designDocs) {
@@ -100,7 +117,7 @@ export function getSaveDesignDoc(state) {
 }
 
 // returns a simple array of design doc IDs. Omits mango docs
-export function getDesignDocIds(state) {
+export function getDesignDocList(state) {
   if (!state.designDocs) {
     return [];
   }
@@ -190,6 +207,12 @@ export default function indexEditor(state = initialState, action) {
         newDesignDocName: options.value
       };
 
+    case ActionTypes.DESIGN_DOC_NEW_PARTITIONED_UPDATED:
+      return {
+        ...state,
+        newDesignDocPartitioned: options.value
+      };
+
     default:
       return state;
   }
diff --git a/app/addons/documents/layouts.js b/app/addons/documents/layouts.js
index 0d5932955..9c39f1364 100644
--- a/app/addons/documents/layouts.js
+++ b/app/addons/documents/layouts.js
@@ -249,7 +249,7 @@ export const ViewsTabsSidebarLayout = ({showEditView, database, docURL, endpoint
   dbName, dropDownLinks, selectedNavItem, designDocInfo, partitionKey }) => {
 
   const content = showEditView ?
-    <IndexEditorComponents.IndexEditorContainer /> :
+    <IndexEditorComponents.IndexEditorContainer partitionKey={partitionKey}/> :
     <DesignDocInfoContainer
       designDocInfo={designDocInfo}
       designDocName={selectedNavItem.designDocName}/>;
diff --git a/app/addons/documents/mango/components/MangoQueryEditor.js b/app/addons/documents/mango/components/MangoQueryEditor.js
index ecbfea083..a702955b6 100644
--- a/app/addons/documents/mango/components/MangoQueryEditor.js
+++ b/app/addons/documents/mango/components/MangoQueryEditor.js
@@ -145,8 +145,9 @@ export default class MangoQueryEditor extends Component {
 
     this.props.manageIndexes();
 
+    const encodedPartKey = this.props.partitionKey ? encodeURIComponent(this.props.partitionKey) : '';
     const manageIndexURL = '#' + FauxtonAPI.urls('mango', 'index-app',
-      encodeURIComponent(this.props.databaseName), encodeURIComponent(this.props.partitionKey));
+      encodeURIComponent(this.props.databaseName), encodedPartKey);
     FauxtonAPI.navigate(manageIndexURL);
   }
 
diff --git a/app/addons/documents/mango/mango.api.js b/app/addons/documents/mango/mango.api.js
index 965559294..59cbebf8b 100644
--- a/app/addons/documents/mango/mango.api.js
+++ b/app/addons/documents/mango/mango.api.js
@@ -16,7 +16,8 @@ import FauxtonAPI from "../../../core/api";
 import Constants from '../constants';
 
 export const fetchQueryExplain = (databaseName, partitionKey, queryCode) => {
-  const url = FauxtonAPI.urls('mango', 'explain-server', encodeURIComponent(databaseName), encodeURIComponent(partitionKey));
+  const encodedPartKey = partitionKey ? encodeURIComponent(partitionKey) : '';
+  const url = FauxtonAPI.urls('mango', 'explain-server', encodeURIComponent(databaseName), encodedPartKey);
 
   return post(url, queryCode, {rawBody: true}).then((json) => {
     if (json.error) {
diff --git a/app/addons/documents/mangolayout.js b/app/addons/documents/mangolayout.js
index 504b7df9a..70b3e93b7 100644
--- a/app/addons/documents/mangolayout.js
+++ b/app/addons/documents/mangolayout.js
@@ -165,7 +165,8 @@ class MangoLayout extends Component {
     let endpoint = this.props.endpoint;
 
     if (this.props.explainPlan) {
-      endpoint = FauxtonAPI.urls('mango', 'explain-apiurl', encodeURIComponent(database), encodeURIComponent(partitionKey));
+      const encodedPartKey = partitionKey ? encodeURIComponent(partitionKey) : '';
+      endpoint = FauxtonAPI.urls('mango', 'explain-apiurl', encodeURIComponent(database), encodedPartKey);
     }
     let queryFunction = (params) => { return MangoAPI.mangoQueryDocs(databaseName, partitionKey, queryFindCode, params); };
     let docType = Constants.INDEX_RESULTS_DOC_TYPE.MANGO_QUERY;
diff --git a/app/addons/documents/routes-mango.js b/app/addons/documents/routes-mango.js
index 4e555d6a4..b4981f0fb 100644
--- a/app/addons/documents/routes-mango.js
+++ b/app/addons/documents/routes-mango.js
@@ -57,7 +57,7 @@ const MangoIndexEditorAndQueryEditor = FauxtonAPI.RouteObject.extend({
       'allDocs', 'app', encodeURIComponent(this.databaseName), encodedPartitionKey
     );
 
-    const partKeyUrlComponent = partitionKey ? `/${encodeURIComponent(partitionKey)}` : '';
+    const partKeyUrlComponent = partitionKey ? `/${encodedPartitionKey}` : '';
     const fetchUrl = '/' + encodeURIComponent(this.databaseName) + partKeyUrlComponent + '/_find';
 
     const crumbs = [
diff --git a/app/addons/documents/shared-resources.js b/app/addons/documents/shared-resources.js
index ac5c0e0a3..d0ff69257 100644
--- a/app/addons/documents/shared-resources.js
+++ b/app/addons/documents/shared-resources.js
@@ -100,6 +100,20 @@ Documents.Doc = FauxtonAPI.Model.extend({
     return this.docType() === "design doc";
   },
 
+  setDDocPartitionedOption: function (isPartitioned) {
+    if (!this.isDdoc()) {
+      return false;
+    }
+    let options = this.get('options');
+    if (!options) {
+      options = {};
+    }
+    options.partitioned = isPartitioned;
+    this.set({ options });
+
+    return true;
+  },
+
   setDdocView: function (view, map, reduce) {
     if (!this.isDdoc()) {
       return false;
diff --git a/app/addons/documents/sidebar/SidebarControllerContainer.js b/app/addons/documents/sidebar/SidebarControllerContainer.js
index 550f3b7b0..9b85a26f3 100644
--- a/app/addons/documents/sidebar/SidebarControllerContainer.js
+++ b/app/addons/documents/sidebar/SidebarControllerContainer.js
@@ -13,7 +13,7 @@
 import { connect } from 'react-redux';
 import SidebarComponents from './sidebar';
 import Action from './actions';
-import { getDatabase } from './reducers';
+import { getDatabase, getDesignDocPartitioned } from './reducers';
 
 
 // returns a simple array of design doc IDs
@@ -69,7 +69,9 @@ const mapStateToProps = ({ sidebar, databases }, ownProps) => {
     cloneIndexModalVisible: sidebar.cloneIndexModalVisible,
     cloneIndexModalTitle: sidebar.cloneIndexModalTitle,
     cloneIndexModalSelectedDesignDoc: sidebar.cloneIndexModalSelectedDesignDoc,
+    cloneIndexModalSelectedDesignDocPartitioned: getDesignDocPartitioned(sidebar, databases.isDbPartitioned),
     cloneIndexModalNewDesignDocName: sidebar.cloneIndexModalNewDesignDocName,
+    cloneIndexModalNewDesignDocPartitioned: sidebar.cloneIndexModalNewDesignDocPartitioned,
     cloneIndexModalOnSubmit: sidebar.cloneIndexModalOnSubmit,
     cloneIndexDesignDocProp: sidebar.cloneIndexDesignDocProp,
     cloneIndexModalNewIndexName: sidebar.cloneIndexModalNewIndexName,
@@ -102,6 +104,9 @@ const mapDispatchToProps = (dispatch) => {
     updateNewDesignDocName: (designDocName) => {
       dispatch(Action.updateNewDesignDocName(designDocName));
     },
+    updateNewDesignDocPartitioned: (isPartitioned) => {
+      dispatch(Action.updateNewDesignDocPartitioned(isPartitioned));
+    },
     setNewCloneIndexName: (indexName) => {
       dispatch(Action.setNewCloneIndexName(indexName));
     }
diff --git a/app/addons/documents/sidebar/actions.js b/app/addons/documents/sidebar/actions.js
index 7564b3c36..d8ba4cd57 100644
--- a/app/addons/documents/sidebar/actions.js
+++ b/app/addons/documents/sidebar/actions.js
@@ -131,6 +131,15 @@ const updateNewDesignDocName = (designDocName) => (dispatch) => {
   });
 };
 
+const updateNewDesignDocPartitioned = (isPartitioned) => (dispatch) => {
+  dispatch({
+    type: ActionTypes.SIDEBAR_CLONE_MODAL_DESIGN_DOC_NEW_PARTITIONED_UPDATED,
+    options: {
+      value: isPartitioned
+    }
+  });
+};
+
 const selectDesignDoc = (designDoc) => (dispatch) => {
   dispatch({
     type: ActionTypes.SIDEBAR_CLONE_MODAL_DESIGN_DOC_CHANGE,
@@ -159,6 +168,7 @@ export default {
   showCloneIndexModal,
   hideCloneIndexModal,
   updateNewDesignDocName,
+  updateNewDesignDocPartitioned,
   selectDesignDoc,
   setNewCloneIndexName,
   dispatchExpandSelectedItem
diff --git a/app/addons/documents/sidebar/actiontypes.js b/app/addons/documents/sidebar/actiontypes.js
index 666b8a97b..28c84fc03 100644
--- a/app/addons/documents/sidebar/actiontypes.js
+++ b/app/addons/documents/sidebar/actiontypes.js
@@ -21,6 +21,7 @@ export default {
   SIDEBAR_HIDE_CLONE_INDEX_MODAL: 'SIDEBAR_HIDE_CLONE_INDEX_MODAL',
   SIDEBAR_CLONE_MODAL_DESIGN_DOC_CHANGE: 'SIDEBAR_CLONE_MODAL_DESIGN_DOC_CHANGE',
   SIDEBAR_CLONE_MODAL_DESIGN_DOC_NEW_NAME_UPDATED: 'SIDEBAR_CLONE_MODAL_DESIGN_DOC_NEW_NAME_UPDATED',
+  SIDEBAR_CLONE_MODAL_DESIGN_DOC_NEW_PARTITIONED_UPDATED: 'SIDEBAR_CLONE_MODAL_DESIGN_DOC_NEW_PARTITIONED_UPDATED',
   SIDEBAR_CLONE_MODAL_UPDATE_INDEX_NAME: 'SIDEBAR_CLONE_MODAL_UPDATE_INDEX_NAME',
   SIDEBAR_UPDATED_DESIGN_DOCS: 'SIDEBAR_UPDATED_DESIGN_DOCS'
 };
diff --git a/app/addons/documents/sidebar/components/CloneIndexModal.js b/app/addons/documents/sidebar/components/CloneIndexModal.js
index 220e8abc3..ca8356278 100644
--- a/app/addons/documents/sidebar/components/CloneIndexModal.js
+++ b/app/addons/documents/sidebar/components/CloneIndexModal.js
@@ -89,10 +89,14 @@ export default class CloneIndexModal extends React.Component {
               <DesignDocSelector
                 ref={node => this.designDocSelector = node}
                 designDocList={this.props.designDocArray}
+                isDbPartitioned={this.props.isDbPartitioned}
                 selectedDesignDocName={this.props.selectedDesignDoc}
+                selectedDesignDocPartitioned={this.props.selectedDesignDocPartitioned}
                 newDesignDocName={this.props.newDesignDocName}
+                newDesignDocPartitioned={this.props.newDesignDocPartitioned}
                 onSelectDesignDoc={this.props.selectDesignDoc}
-                onChangeNewDesignDocName={this.props.updateNewDesignDocName} />
+                onChangeNewDesignDocName={this.props.updateNewDesignDocName}
+                onChangeNewDesignDocPartitioned={this.props.updateNewDesignDocPartitioned} />
             </div>
 
             <div className="clone-index-name-row">
diff --git a/app/addons/documents/sidebar/components/DesignDocList.js b/app/addons/documents/sidebar/components/DesignDocList.js
index 21134702b..c52065953 100644
--- a/app/addons/documents/sidebar/components/DesignDocList.js
+++ b/app/addons/documents/sidebar/components/DesignDocList.js
@@ -12,8 +12,8 @@
 
 import PropTypes from 'prop-types';
 import React from 'react';
-import ReactDOM from 'react-dom';
 import FauxtonAPI from '../../../../core/api';
+import DocHelpers from '../../helpers';
 import DesignDoc from './DesignDoc';
 
 export default class DesignDocList extends React.Component {
@@ -43,12 +43,7 @@ export default class DesignDocList extends React.Component {
   designDocList = () => {
     return _.map(this.props.designDocs, (designDoc, key) => {
       const ddName = decodeURIComponent(designDoc.safeId);
-      // By default a design doc is partitioned if the database is partitioned
-      let isDDocPartitioned = this.props.isDbPartitioned;
-      // Check if it is explictly set to not partitioned
-      if (this.props.isDbPartitioned && designDoc.options && designDoc.options.partitioned === false) {
-        isDDocPartitioned = false;
-      }
+      const isDDocPartitioned = DocHelpers.isDDocPartitioned(designDoc, this.props.isDbPartitioned);
 
       // only pass down the selected nav info and toggle info if they're relevant for this particular design doc
       let expanded = false,
diff --git a/app/addons/documents/sidebar/components/SidebarController.js b/app/addons/documents/sidebar/components/SidebarController.js
index b37c8ac53..bda4bfa3f 100644
--- a/app/addons/documents/sidebar/components/SidebarController.js
+++ b/app/addons/documents/sidebar/components/SidebarController.js
@@ -12,7 +12,6 @@
 
 import PropTypes from 'prop-types';
 import React from 'react';
-import ReactDOM from 'react-dom';
 import ComponentsActions from "../../../components/actions";
 import Components from '../../../components/react-components';
 import ComponentsStore from '../../../components/stores';
@@ -91,6 +90,8 @@ export default class SidebarController extends React.Component {
       sourceDesignDocName: this.props.cloneIndexSourceDesignDocName,
       targetDesignDocName: this.props.cloneIndexModalSelectedDesignDoc,
       newDesignDocName: this.props.cloneIndexModalNewDesignDocName,
+      newDesignDocPartitioned: this.props.cloneIndexModalNewDesignDocPartitioned,
+      isDbPartitioned: this.props.isDbPartitioned,
       newIndexName: this.props.cloneIndexModalNewIndexName,
       designDocs: this.props.designDocs,
       database: this.props.database,
@@ -137,12 +138,16 @@ export default class SidebarController extends React.Component {
           submit={this.cloneIndex}
           designDocArray={this.props.availableDesignDocIds}
           selectedDesignDoc={this.props.cloneIndexModalSelectedDesignDoc}
+          selectedDesignDocPartitioned={this.props.cloneIndexModalSelectedDesignDocPartitioned}
           newDesignDocName={this.props.cloneIndexModalNewDesignDocName}
+          newDesignDocPartitioned={this.props.cloneIndexModalNewDesignDocPartitioned}
           newIndexName={this.props.cloneIndexModalNewIndexName}
           indexLabel={this.props.cloneIndexModalIndexLabel}
           selectDesignDoc={this.props.selectDesignDoc}
           updateNewDesignDocName={this.props.updateNewDesignDocName}
-          setNewCloneIndexName={this.props.setNewCloneIndexName} />
+          updateNewDesignDocPartitioned={this.props.updateNewDesignDocPartitioned}
+          setNewCloneIndexName={this.props.setNewCloneIndexName}
+          isDbPartitioned={this.props.isDbPartitioned} />
       </nav>
     );
   }
diff --git a/app/addons/documents/sidebar/reducers.js b/app/addons/documents/sidebar/reducers.js
index f1fa140ab..a5491d887 100644
--- a/app/addons/documents/sidebar/reducers.js
+++ b/app/addons/documents/sidebar/reducers.js
@@ -10,9 +10,10 @@
 // License for the specific language governing permissions and limitations under
 // the License.
 
-import React from "react";
-import app from "../../../app";
-import ActionTypes from "./actiontypes";
+import React from 'react';
+import app from '../../../app';
+import Helpers from '../helpers';
+import ActionTypes from './actiontypes';
 
 const initialState = {
   designDocs: new Backbone.Collection(),
@@ -37,6 +38,7 @@ const initialState = {
   cloneIndexModalTitle: '',
   cloneIndexModalSelectedDesignDoc: '',
   cloneIndexModalNewDesignDocName: '',
+  cloneIndexModalNewDesignDocPartitioned: true,
   cloneIndexModalNewIndexName: '',
   cloneIndexModalSourceIndexName: '',
   cloneIndexModalSourceDesignDocName: '',
@@ -136,6 +138,16 @@ function getDesignDocList (designDocs) {
   return ddocsList;
 }
 
+export function getDesignDocPartitioned(state, isDbPartitioned) {
+  const designDoc = state.designDocs.find(ddoc => {
+    return state.cloneIndexModalSelectedDesignDoc == ddoc.id;
+  });
+  if (designDoc) {
+    return Helpers.isDDocPartitioned(designDoc.get('doc'), isDbPartitioned);
+  }
+  return false;
+}
+
 export const getDatabase = (state) => {
   if (state.loading) {
     return {};
@@ -214,6 +226,12 @@ export default function sidebar(state = initialState, action) {
         cloneIndexModalNewDesignDocName: options.value
       };
 
+    case ActionTypes.SIDEBAR_CLONE_MODAL_DESIGN_DOC_NEW_PARTITIONED_UPDATED:
+      return {
+        ...state,
+        cloneIndexModalNewDesignDocPartitioned: options.value
+      };
+
     case ActionTypes.SIDEBAR_CLONE_MODAL_UPDATE_INDEX_NAME:
       return {
         ...state,
diff --git a/app/addons/documents/tests/nightwatch/viewCreateBadView.js b/app/addons/documents/tests/nightwatch/viewCreateBadView.js
index 66eb74a79..7405ed019 100644
--- a/app/addons/documents/tests/nightwatch/viewCreateBadView.js
+++ b/app/addons/documents/tests/nightwatch/viewCreateBadView.js
@@ -31,7 +31,7 @@ module.exports = {
       .clearValue('#index-name')
       .setValue('#index-name', 'hasenindex')
       .clickWhenVisible('#reduce-function-selector')
-      .keys(['\uE013', '\uE013', '\uE013', '\uE013', '\uE006'])
+      .keys(['\uE013', '\uE013', '\uE013', '\uE013', '\uE013', '\uE006'])
       .execute('\
         var editor = ace.edit("map-function");\
         editor.getSession().setValue("function (doc) { emit(\'boom\', doc._id); }");\
diff --git a/app/addons/documents/tests/nightwatch/viewEdit.js b/app/addons/documents/tests/nightwatch/viewEdit.js
index 6214a8e60..6a1dd413d 100644
--- a/app/addons/documents/tests/nightwatch/viewEdit.js
+++ b/app/addons/documents/tests/nightwatch/viewEdit.js
@@ -128,7 +128,7 @@ module.exports = {
       .clearValue('#index-name')
       .setValue('#index-name', 'view1')
       .clickWhenVisible('#reduce-function-selector')
-      .keys(['\uE013', '\uE013', '\uE013', '\uE013', '\uE006'])
+      .keys(['\uE013', '\uE013', '\uE013', '\uE013', '\uE013', '\uE006'])
       .execute('\
         var editor = ace.edit("map-function");\
         editor.getSession().setValue("function (doc) { emit(doc._id, 100); }");\


 

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