[partitioned-dbs] Updates to doc editor (#1144)

* action to fetch db metadata

* Adds partition key to auto generated ID for new docs

* Adds partition key to auto generated ID for cloned docs

* Fix fetchDatabaseInfo

* Add tests

* Address comments

* Address comments again
diff --git a/app/addons/databases/__tests__/reducers.test.js b/app/addons/databases/__tests__/reducers.test.js
new file mode 100644
index 0000000..4f57bab
--- /dev/null
+++ b/app/addons/databases/__tests__/reducers.test.js
@@ -0,0 +1,61 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+import databases from '../reducers';
+import ActionTypes from '../actiontypes';
+
+describe('Databases Reducer', () => {
+
+  it('sets if partioned database feature is available', () => {
+    const action = {
+      type: ActionTypes.DATABASES_PARTITIONED_DB_AVAILABLE,
+      options: { available: true }
+    };
+    let newState = databases(undefined, { type: 'DO_NOTHIN'});
+    expect(newState.partitionedDatabasesAvailable).toBe(false);
+
+    newState = databases(newState, action);
+    expect(newState.partitionedDatabasesAvailable).toBe(true);
+  });
+
+  it('sets isPartitioned to false when props is not present', () => {
+    const action = {
+      type: ActionTypes.DATABASES_FETCH_SELECTED_DB_METADATA_SUCCESS,
+      options: {
+        metadata: { name: 'dummy_db' }
+      }
+    };
+    const newState = databases(undefined, action);
+    expect(newState.isDbPartitioned).toBe(false);
+    expect(newState.dbInfo).toBeDefined();
+    expect(newState.dbInfo.name).toBe('dummy_db');
+  });
+
+  it('sets isPartitioned based on db metadata', () => {
+    const action = {
+      type: ActionTypes.DATABASES_FETCH_SELECTED_DB_METADATA_SUCCESS,
+      options: {
+        metadata: {
+          name: 'dummy_db',
+          props: { partitioned: true }
+        }
+      }
+    };
+    const newState = databases(undefined, action);
+    expect(newState.isDbPartitioned).toBe(true);
+
+    action.options.metadata.props.partitioned = false;
+    const newState2 = databases(undefined, action);
+    expect(newState2.isDbPartitioned).toBe(false);
+  });
+
+});
diff --git a/app/addons/databases/actions.js b/app/addons/databases/actions.js
index 1935491..d8927af 100644
--- a/app/addons/databases/actions.js
+++ b/app/addons/databases/actions.js
@@ -17,6 +17,7 @@
 import Stores from "./stores";
 import ActionTypes from "./actiontypes";
 import Resources from "./resources";
+import * as API from './api';
 
 function getDatabaseDetails (dbList, fullDbList) {
   const databaseDetails = [];
@@ -220,5 +221,31 @@
     }).catch(() => {
       // ignore as the default is false
     });
+  },
+
+  // Fetches and sets metadata info for the selected database, which is defined by the current URL
+  // This function is intended to be called by the routers if needed by the components in the page.
+  fetchSelectedDatabaseInfo(databaseName) {
+    FauxtonAPI.reduxDispatch({
+      type: ActionTypes.DATABASES_FETCH_SELECTED_DB_METADATA
+    });
+    API.fetchDatabaseInfo(databaseName).then(res => {
+      if (!res.db_name) {
+        const details = res.reason ? res.reason : '';
+        throw new Error('Failed to fetch database info. ' + details);
+      }
+      FauxtonAPI.reduxDispatch({
+        type: ActionTypes.DATABASES_FETCH_SELECTED_DB_METADATA_SUCCESS,
+        options: {
+          metadata: res
+        }
+      });
+    }).catch(err => {
+      FauxtonAPI.addNotification({
+        msg: err.message,
+        type: 'error',
+        clear: true
+      });
+    });
   }
 };
diff --git a/app/addons/databases/actiontypes.js b/app/addons/databases/actiontypes.js
index e8d3a53..bf7bd57 100644
--- a/app/addons/databases/actiontypes.js
+++ b/app/addons/databases/actiontypes.js
@@ -15,5 +15,7 @@
   DATABASES_STARTLOADING: 'DATABASES_STARTLOADING',
   DATABASES_LOADCOMPLETE: 'DATABASES_LOADCOMPLETE',
   DATABASES_UPDATE: 'DATABASES_UPDATE',
-  DATABASES_PARTITIONED_DB_AVAILABLE: 'DATABASES_PARTITIONED_DB_AVAILABLE'
+  DATABASES_PARTITIONED_DB_AVAILABLE: 'DATABASES_PARTITIONED_DB_AVAILABLE',
+  DATABASES_FETCH_SELECTED_DB_METADATA: 'DATABASES_FETCH_SELECTED_DB_METADATA',
+  DATABASES_FETCH_SELECTED_DB_METADATA_SUCCESS: 'DATABASES_FETCH_SELECTED_DB_METADATA_SUCCESS'
 };
diff --git a/app/addons/databases/api.js b/app/addons/databases/api.js
new file mode 100644
index 0000000..79f980b
--- /dev/null
+++ b/app/addons/databases/api.js
@@ -0,0 +1,21 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+import FauxtonAPI from '../../core/api';
+import { get } from '../../core/ajax';
+import Helpers from '../../helpers';
+
+export const fetchDatabaseInfo = (databaseName) => {
+  const base = FauxtonAPI.urls('databaseBaseURL', 'server', databaseName);
+  const url = Helpers.getServerUrl(base);
+  return get(url);
+};
diff --git a/app/addons/databases/base.js b/app/addons/databases/base.js
index c4218d4..2a082bb 100644
--- a/app/addons/databases/base.js
+++ b/app/addons/databases/base.js
@@ -16,6 +16,7 @@
 import FauxtonAPI from "../../core/api";
 import Databases from "./routes";
 import Actions from "./actions";
+import reducers from './reducers';
 import "./assets/less/databases.less";
 
 Databases.initialize = function () {
@@ -45,6 +46,10 @@
 Databases.PARTITONED_DB_CHECK_EXTENSION = 'Databases:PartitionedDbCheck';
 FauxtonAPI.registerExtension(Databases.PARTITONED_DB_CHECK_EXTENSION, checkPartitionedDatabaseFeature);
 
+FauxtonAPI.addReducers({
+  databases: reducers
+});
+
 // Utility functions
 Databases.databaseUrl = function (database) {
   var name = _.isObject(database) ? database.id : database,
diff --git a/app/addons/databases/reducers.js b/app/addons/databases/reducers.js
index 74da7c4..a20a5f6 100644
--- a/app/addons/databases/reducers.js
+++ b/app/addons/databases/reducers.js
@@ -12,8 +12,19 @@
 
 import ActionTypes from './actiontypes';
 const initialState = {
-  partitionedDatabasesAvailable: false
+  partitionedDatabasesAvailable: false,
+  isLoadingDbInfo: false,
+  dbInfo: undefined,
+  isDbPartitioned: false
 };
+
+export function isPartitioned(metadata) {
+  if (metadata && metadata.props && metadata.props.partitioned === true) {
+    return true;
+  }
+  return false;
+}
+
 export default function databases(state = initialState, action) {
   switch (action.type) {
     case ActionTypes.DATABASES_PARTITIONED_DB_AVAILABLE:
@@ -21,6 +32,20 @@
         ...state,
         partitionedDatabasesAvailable: action.options.available
       };
+    case ActionTypes.DATABASES_FETCH_SELECTED_DB_METADATA:
+      return {
+        ...state,
+        isLoadingDbInfo: true,
+        dbInfo: undefined,
+        isDbPartitioned: false
+      };
+    case ActionTypes.DATABASES_FETCH_SELECTED_DB_METADATA_SUCCESS:
+      return {
+        ...state,
+        isLoadingDbInfo: false,
+        dbInfo: action.options.metadata,
+        isDbPartitioned: isPartitioned(action.options.metadata)
+      };
     default:
       return state;
   }
diff --git a/app/addons/documents/__tests__/partition-key.js b/app/addons/documents/__tests__/partition-key.test.js
similarity index 91%
rename from app/addons/documents/__tests__/partition-key.js
rename to app/addons/documents/__tests__/partition-key.test.js
index 75005e9..7bce1f0 100644
--- a/app/addons/documents/__tests__/partition-key.js
+++ b/app/addons/documents/__tests__/partition-key.test.js
@@ -17,18 +17,6 @@
 import sinon from 'sinon';
 
 describe('PartitionKeySelector', () => {
-  // const props = {
-  //   includeDocs: false,
-  //   queryOptionsToggleIncludeDocs: () => {},
-  //   reduce: false,
-  //   contentVisible: true,
-  //   perPage: 10,
-  //   queryOptionsToggleStable: () => {},
-  //   queryOptionsChangeUpdate: () => {},
-  //   stable: false,
-  //   update: 'true'
-  // };
-
   const defaultProps = {
     selectorVisible: true,
     partitionKey: '',
diff --git a/app/addons/documents/__tests__/resources.test.js b/app/addons/documents/__tests__/resources.test.js
index 4505015..a06171c 100644
--- a/app/addons/documents/__tests__/resources.test.js
+++ b/app/addons/documents/__tests__/resources.test.js
@@ -11,6 +11,7 @@
 // the License.
 
 import FauxtonAPI from "../../../core/api";
+import Helpers from '../../../helpers';
 import Models from "../resources";
 import testUtils from "../../../../test/mocha/testUtils";
 import "../base";
@@ -82,6 +83,25 @@
   });
 });
 
+describe('NewDoc', () => {
+
+  let getUUID;
+  beforeEach(() => {
+    getUUID = sinon.stub(Helpers, 'getUUID').resolves({ uuids: ['abc9876'] });
+  });
+
+  afterEach(() => {
+    getUUID.restore();
+  });
+
+  it('adds partition key to auto-generated ID', () => {
+    const newDoc = new Models.NewDoc(null, { database: {}, partitionKey: 'part_key' });
+    return newDoc.fetch().then(() => {
+      expect(newDoc.get('_id')).toMatch(/part_key:abc9876/);
+    });
+  });
+});
+
 describe('QueryParams', () => {
   describe('parse', () => {
     it('should not parse arbitrary parameters', () => {
diff --git a/app/addons/documents/__tests__/results-toolbar.test.js b/app/addons/documents/__tests__/results-toolbar.test.js
index c830b2c..09b4c9c 100644
--- a/app/addons/documents/__tests__/results-toolbar.test.js
+++ b/app/addons/documents/__tests__/results-toolbar.test.js
@@ -50,4 +50,9 @@
     expect(wrapper.find('div.two-sides-toggle-button').length).toBe(1);
     expect(wrapper.find('.document-result-screen__toolbar-create-btn').length).toBe(1);
   });
+
+  it('includes default partition key when one is selected', () => {
+    const wrapper = mount(<ResultsToolBar hasResults={true} isListDeletable={false} {...restProps} partitionKey={'partKey1'}/>);
+    expect(wrapper.find('a').prop('href')).toMatch(/\?partitionKey=partKey1$/);
+  });
 });
diff --git a/app/addons/documents/components/results-toolbar.js b/app/addons/documents/components/results-toolbar.js
index abaa037..c7dbccc 100644
--- a/app/addons/documents/components/results-toolbar.js
+++ b/app/addons/documents/components/results-toolbar.js
@@ -32,7 +32,8 @@
       hasSelectedItem,
       toggleSelectAll,
       isLoading,
-      databaseName
+      databaseName,
+      partitionKey
     } = this.props;
 
     // Determine if we need to display the bulk action selector
@@ -57,7 +58,7 @@
     if (databaseName) {
       createDocumentLink = (
         <div className="document-result-screen__toolbar-flex-container">
-          <a href={Helpers.getNewDocUrl(databaseName)} className="btn save document-result-screen__toolbar-create-btn btn-primary">
+          <a href={Helpers.getNewDocUrl(databaseName, partitionKey)} className="btn save document-result-screen__toolbar-create-btn btn-primary">
             Create Document
           </a>
         </div>
@@ -81,5 +82,6 @@
   toggleSelectAll: PropTypes.func.isRequired,
   isLoading: PropTypes.bool.isRequired,
   hasResults: PropTypes.bool.isRequired,
-  isListDeletable: PropTypes.bool
+  isListDeletable: PropTypes.bool,
+  partitionKey: PropTypes.string
 };
diff --git a/app/addons/documents/doc-editor/__tests__/doc-editor.components.test.js b/app/addons/documents/doc-editor/__tests__/doc-editor.components.test.js
index 35505f4..c0ab5c0 100644
--- a/app/addons/documents/doc-editor/__tests__/doc-editor.components.test.js
+++ b/app/addons/documents/doc-editor/__tests__/doc-editor.components.test.js
@@ -11,13 +11,16 @@
 // the License.
 
 import FauxtonAPI from '../../../../core/api';
+import Helpers from '../../../../helpers';
 import React from 'react';
-import ReactDOM from 'react-dom';
+import sinon from 'sinon';
 import Documents from '../../resources';
 import AttachmentsPanelButton from '../components/AttachmentsPanelButton';
+import CloneDocModal from '../components/CloneDocModal';
 import DocEditorScreen from '../components/DocEditorScreen';
 import DocEditorContainer from '../components/DocEditorContainer';
 import Databases from '../../../databases/base';
+import databasesReducer from '../../../databases/reducers';
 import utils from '../../../../../test/mocha/testUtils';
 import { mount } from 'enzyme';
 import thunk from 'redux-thunk';
@@ -62,6 +65,7 @@
 const defaultProps = {
   isLoading: true,
   isNewDoc: true,
+  isDbPartitioned: false,
   database: database,
   doc: new Documents.NewDoc(null, { database: database }),
   conflictCount: 0,
@@ -162,12 +166,55 @@
     assert.equal(attachmentURLactual, './a%2Fspecial%3Fdb/_design%2Ftest%23doc/one%252F.png');
   });
 
+  it('auto-generated ID for new docs starts with colon for partitioned databases', () => {
+    const doc = { database: database, attributes: { _id: 'new_doc_id'} };
+    const el = mount(<DocEditorScreen
+      {...defaultProps}
+      isLoading={false}
+      isNewDoc={true}
+      isDbPartitioned={true}
+      database={database}
+      doc={doc} />);
+
+    const editor = el.find('CodeEditor');
+    expect(editor.prop('defaultCode')).toMatch(/":new_doc_id"/);
+  });
+
+  it('cancel button navigates to all docs by default', () => {
+    const doc = new Documents.Doc(docWithAttachmentsJSON, { database: database });
+    const el = mount(<DocEditorScreen
+      {...defaultProps}
+      isLoading={false}
+      isNewDoc={true}
+      isDbPartitioned={true}
+      database={database}
+      doc={doc} />);
+
+    const linkUrl = el.find('a.cancel-button').prop('href');
+    expect(linkUrl).toBe('#/database/' + encodeURIComponent(database.id) + '/_all_docs');
+  });
+
+  it('cancel button navigates to previousUrl', () => {
+    const doc = new Documents.Doc(docWithAttachmentsJSON, { database: database });
+    const el = mount(<DocEditorScreen
+      {...defaultProps}
+      isLoading={false}
+      isNewDoc={true}
+      isDbPartitioned={true}
+      database={database}
+      previousUrl='something/else'
+      doc={doc} />);
+
+    const linkUrl = el.find('a.cancel-button').prop('href');
+    expect(linkUrl).toBe('#/something/else');
+  });
+
 });
 
 describe('DocEditorContainer', () => {
   const middlewares = [thunk];
   const store = createStore(
-    combineReducers({ docEditor: docEditorReducer }),
+    combineReducers({ docEditor: docEditorReducer, databases: databasesReducer }),
     applyMiddleware(...middlewares)
   );
 
@@ -257,3 +304,51 @@
     assert.equal(el.find("#testDatabaseName").text(), database.id);
   });
 });
+
+describe("CloneDocModal", () => {
+  const defaultProps = {
+    visible: false,
+    doc: { attributes: {_id: 'my_doc_id', hey: 'there'} },
+    database: {},
+    onSubmit: () => {},
+    hideCloneDocModal: () => {},
+    cloneDoc: () => {}
+  };
+
+  let getUUID;
+
+  afterEach(() => {
+    if (getUUID) {
+      getUUID.restore();
+    }
+  });
+
+  it('sets random UUID by default', () => {
+    const promise = FauxtonAPI.Promise.resolve({ uuids: ['abc9876'] });
+    getUUID = sinon.stub(Helpers, 'getUUID').returns(promise);
+    const el = mount(
+      <CloneDocModal {...defaultProps} />
+    );
+    el.setProps({visible: true});
+    return promise.then(() => {
+      expect(el.state().uuid).toBe('abc9876');
+    });
+  });
+
+  it('adds partition key from original doc to the auto-generated ID when it exists', () => {
+    const promise = FauxtonAPI.Promise.resolve({ uuids: ['abc9876'] });
+    getUUID = sinon.stub(Helpers, 'getUUID').returns(promise);
+    const el = mount(
+      <CloneDocModal
+        {...defaultProps}
+        doc={{ attributes: {_id: 'part1:my_doc_id', hey: 'there'} }}/>
+    );
+    el.setProps({visible: true});
+    return promise.then(() => {
+      expect(el.state().uuid).toBe('part1:abc9876');
+    });
+  });
+
+
+});
+
diff --git a/app/addons/documents/doc-editor/actions.js b/app/addons/documents/doc-editor/actions.js
index 1212051..7977ab4 100644
--- a/app/addons/documents/doc-editor/actions.js
+++ b/app/addons/documents/doc-editor/actions.js
@@ -48,7 +48,7 @@
   });
 };
 
-const saveDoc = (doc, isValidDoc, onSave) => {
+const saveDoc = (doc, isValidDoc, onSave, navigateToUrl) => {
   if (isValidDoc) {
     FauxtonAPI.addNotification({
       msg: 'Saving document.',
@@ -57,7 +57,11 @@
 
     doc.save().then(function () {
       onSave(doc.prettyJSON());
-      FauxtonAPI.navigate('#/' + FauxtonAPI.urls('allDocs', 'app',  FauxtonAPI.url.encode(doc.database.id)), {trigger: true});
+      if (navigateToUrl) {
+        FauxtonAPI.navigate(navigateToUrl, {trigger: true});
+      } else {
+        FauxtonAPI.navigate('#/' + FauxtonAPI.urls('allDocs', 'app',  FauxtonAPI.url.encode(doc.database.id)), {trigger: true});
+      }
     }).fail(function (xhr) {
       FauxtonAPI.addNotification({
         msg: 'Save failed: ' + JSON.parse(xhr.responseText).reason,
diff --git a/app/addons/documents/doc-editor/components/CloneDocModal.js b/app/addons/documents/doc-editor/components/CloneDocModal.js
index 3d6776e..7ae7968 100644
--- a/app/addons/documents/doc-editor/components/CloneDocModal.js
+++ b/app/addons/documents/doc-editor/components/CloneDocModal.js
@@ -43,7 +43,13 @@
     if (this.state.uuid === null) {
       Helpers.getUUID().then((res) => {
         if (res.uuids) {
-          this.setState({ uuid: res.uuids[0] });
+          const newState = { uuid: res.uuids[0] };
+          const idx = this.props.doc ? this.props.doc.attributes._id.indexOf(':') : -1;
+          if (idx >= 0) {
+            const partitionKey = this.props.doc.attributes._id.substring(0, idx);
+            newState.uuid = partitionKey + ':' + newState.uuid;
+          }
+          this.setState(newState);
         }
       }).catch(() => {});
     }
diff --git a/app/addons/documents/doc-editor/components/DocEditorContainer.js b/app/addons/documents/doc-editor/components/DocEditorContainer.js
index 3946ca3..17eeba4 100644
--- a/app/addons/documents/doc-editor/components/DocEditorContainer.js
+++ b/app/addons/documents/doc-editor/components/DocEditorContainer.js
@@ -1,15 +1,28 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
 import { connect } from 'react-redux';
 import Actions from '../actions';
 import DocEditorScreen from './DocEditorScreen';
 
-const mapStateToProps = ({ docEditor }, ownProps) => {
+const mapStateToProps = ({ docEditor, databases }, ownProps) => {
   return {
-    isLoading: docEditor.isLoading,
+    isLoading: docEditor.isLoading || databases.isLoadingDbInfo,
     isNewDoc: ownProps.isNewDoc,
+    isDbPartitioned: databases.isDbPartitioned,
     doc: docEditor.doc,
     database: ownProps.database,
     conflictCount: docEditor.docConflictCount,
-
+    previousUrl: ownProps.previousUrl,
     isCloneDocModalVisible: docEditor.cloneDocModalVisible,
 
     isDeleteDocModalVisible: docEditor.deleteDocModalVisible,
@@ -24,8 +37,8 @@
 
 const mapDispatchToProps = (dispatch) => {
   return {
-    saveDoc: (doc, isValidDoc, onSave) => {
-      Actions.saveDoc(doc, isValidDoc, onSave);
+    saveDoc: (doc, isValidDoc, onSave, navigateToUrl) => {
+      Actions.saveDoc(doc, isValidDoc, onSave, navigateToUrl);
     },
 
     showCloneDocModal: () => {
diff --git a/app/addons/documents/doc-editor/components/DocEditorScreen.js b/app/addons/documents/doc-editor/components/DocEditorScreen.js
index 6518d04..f49aef7 100644
--- a/app/addons/documents/doc-editor/components/DocEditorScreen.js
+++ b/app/addons/documents/doc-editor/components/DocEditorScreen.js
@@ -31,8 +31,10 @@
   static propTypes = {
     isLoading: PropTypes.bool.isRequired,
     isNewDoc: PropTypes.bool.isRequired,
+    isDbPartitioned: PropTypes.bool.isRequired,
     doc: PropTypes.object,
     conflictCount: PropTypes.number.isRequired,
+    previousUrl: PropTypes.string,
     saveDoc: PropTypes.func.isRequired,
 
     isCloneDocModalVisible: PropTypes.bool.isRequired,
@@ -63,8 +65,14 @@
       return (<GeneralComponents.LoadLines />);
     }
 
-    var code = JSON.stringify(this.props.doc.attributes, null, '  ');
-    var editorCommands = [{
+    const docContent = this.props.doc.attributes;
+    if (this.props.isDbPartitioned) {
+      if (!docContent._id.includes(':')) {
+        docContent._id = ':' + docContent._id;
+      }
+    }
+    const code = JSON.stringify(docContent, null, '  ');
+    const editorCommands = [{
       name: 'save',
       bindKey: { win: 'Ctrl-S', mac: 'Ctrl-S' },
       exec: this.saveDoc
@@ -94,7 +102,7 @@
   }
 
   saveDoc = () => {
-    this.props.saveDoc(this.props.doc, this.checkDocIsValid(), this.onSaveComplete);
+    this.props.saveDoc(this.props.doc, this.checkDocIsValid(), this.onSaveComplete, this.props.previousUrl);
   };
 
   onSaveComplete = () => {
@@ -118,7 +126,7 @@
     if (this.getEditor().hasErrors()) {
       return false;
     }
-    var json = JSON.parse(this.getEditor().getValue());
+    const json = JSON.parse(this.getEditor().getValue());
     this.props.doc.clear().set(json, { validate: true });
 
     return !this.props.doc.validationError;
@@ -158,8 +166,10 @@
   };
 
   render() {
-    var saveButtonLabel = (this.props.isNewDoc) ? 'Create Document' : 'Save Changes';
-    let endpoint = FauxtonAPI.urls('allDocs', 'app', FauxtonAPI.url.encode(this.props.database.id));
+    const saveButtonLabel = (this.props.isNewDoc) ? 'Create Document' : 'Save Changes';
+    const endpoint = this.props.previousUrl ?
+      this.props.previousUrl :
+      FauxtonAPI.urls('allDocs', 'app', FauxtonAPI.url.encode(this.props.database.id));
     return (
       <div>
         <div id="doc-editor-actions-panel">
diff --git a/app/addons/documents/helpers.js b/app/addons/documents/helpers.js
index a59d3a9..42879ac 100644
--- a/app/addons/documents/helpers.js
+++ b/app/addons/documents/helpers.js
@@ -89,9 +89,13 @@
   };
 };
 
-const getNewDocUrl = (databaseName) => {
+const getNewDocUrl = (databaseName, partitionKey) => {
   const safeDatabaseName = encodeURIComponent(databaseName);
-  return FauxtonAPI.urls('new', 'newDocument', safeDatabaseName);
+  let url = FauxtonAPI.urls('new', 'newDocument', safeDatabaseName);
+  if (partitionKey) {
+    url = url + '?partitionKey=' + encodeURIComponent(partitionKey);
+  }
+  return url;
 };
 
 const selectedViewContainsReduceFunction = (designDocs, selectedNavItem) => {
diff --git a/app/addons/documents/index-results/containers/IndexResultsContainer.js b/app/addons/documents/index-results/containers/IndexResultsContainer.js
index 4ba8da8..ca940ec 100644
--- a/app/addons/documents/index-results/containers/IndexResultsContainer.js
+++ b/app/addons/documents/index-results/containers/IndexResultsContainer.js
@@ -55,7 +55,8 @@
     textEmptyIndex: getTextEmptyIndex(indexResults),
     docType: getDocType(indexResults),
     fetchParams: getFetchParams(indexResults),
-    queryOptionsParams: getQueryOptionsParams(indexResults)
+    queryOptionsParams: getQueryOptionsParams(indexResults),
+    partitionKey: ownProps.partitionKey
   };
 };
 
diff --git a/app/addons/documents/layouts.js b/app/addons/documents/layouts.js
index 3efae93..fe6e48e 100644
--- a/app/addons/documents/layouts.js
+++ b/app/addons/documents/layouts.js
@@ -185,7 +185,8 @@
     fetchAtStartup={true}
     queryDocs={queryDocs}
     docType={Constants.INDEX_RESULTS_DOC_TYPE.VIEW}
-    deleteEnabled={deleteEnabled} />;
+    deleteEnabled={deleteEnabled}
+    partitionKey={partitionKey} />;
 
   return (
     <div id="dashboard" className="with-sidebar">
diff --git a/app/addons/documents/resources.js b/app/addons/documents/resources.js
index 159559b..a920b8e 100644
--- a/app/addons/documents/resources.js
+++ b/app/addons/documents/resources.js
@@ -71,17 +71,18 @@
 
 Documents.NewDoc = Documents.Doc.extend({
   fetch: function () {
+    const prefix = this.partitionKey ? (this.partitionKey + ':') : '';
     return Helpers.getUUID().then((res) => {
       if (res.uuids) {
-        this.set("_id", res.uuids[0]);
+        this.set("_id", prefix + res.uuids[0]);
       } else {
-        this.set("_id", 'enter_document_id');
+        this.set("_id", prefix + 'enter_document_id');
       }
       return res;
     }).catch(() => {
       // Don't throw error so the user is still able
       // to edit the new doc
-      this.set("_id", 'enter_document_id');
+      this.set("_id", prefix + 'enter_document_id');
     });
   }
 });
diff --git a/app/addons/documents/routes-doc-editor.js b/app/addons/documents/routes-doc-editor.js
index f2509ab..421bc14 100644
--- a/app/addons/documents/routes-doc-editor.js
+++ b/app/addons/documents/routes-doc-editor.js
@@ -11,9 +11,11 @@
 // the License.
 
 import React from 'react';
+import app from '../../app';
 import FauxtonAPI from "../../core/api";
 import Documents from "./resources";
 import Databases from "../databases/base";
+import DatabaseActions from '../databases/actions';
 import Actions from "./doc-editor/actions";
 import DocEditorContainer from "./doc-editor/components/DocEditorContainer";
 import RevBrowserContainer from './rev-browser/container';
@@ -39,7 +41,7 @@
     'database/:database/_design/:ddoc': 'showDesignDoc',
     'database/:database/_local/:doc': 'showLocalDoc',
     'database/:database/:doc': 'codeEditor',
-    'database/:database/new': 'codeEditor'
+    'database/:database/new(:extra)': 'codeEditor'
   },
 
   revisionBrowser: function (databaseName, docId) {
@@ -63,7 +65,8 @@
     return this.revisionBrowser(databaseName, '_design/' + ddoc);
   },
 
-  codeEditor: function (databaseName, docId) {
+  codeEditor: function (databaseName, docId, options) {
+    const urlParams = app.getParams(options);
     const backLink = FauxtonAPI.urls('allDocs', 'app', FauxtonAPI.url.encode(databaseName));
 
     const crumbs =  [
@@ -75,17 +78,31 @@
 
     if (docId) {
       this.doc = new Documents.Doc({ _id: docId }, { database: this.database, fetchConflicts: true });
+    } else {
+      const partitionKey = urlParams ? urlParams.partitionKey : undefined;
+      this.doc = new Documents.NewDoc(null, { database: this.database, partitionKey });
     }
-
+    DatabaseActions.fetchSelectedDatabaseInfo(databaseName);
     Actions.dispatchInitDocEditor({ doc: this.doc, database: this.database });
 
+    let previousUrl = undefined;
+    const previousValidUrls = FauxtonAPI.router.lastPages.filter(url => {
+      // make sure it doesn't redirect back to the code editor when cloning docs
+      return url.includes('/_all_docs') || url.match(/_design\/(\S)*\/_/) || url.includes('/_find');
+    });
+    if (previousValidUrls.length > 0) {
+      previousUrl = previousValidUrls[previousValidUrls.length - 1];
+    }
+
     return <DocEditorLayout
       crumbs={crumbs}
       endpoint={this.doc.url('apiurl')}
       docURL={this.doc.documentation()}
+      partitionKey={urlParams.partitionKey}
       component={<DocEditorContainer
         database={this.database}
         isNewDoc={docId ? false : true}
+        previousUrl={previousUrl}
       />}
     />;
   },
diff --git a/app/addons/documents/shared-resources.js b/app/addons/documents/shared-resources.js
index c59bd2e..ac5c0e0 100644
--- a/app/addons/documents/shared-resources.js
+++ b/app/addons/documents/shared-resources.js
@@ -51,6 +51,7 @@
     if (options.fetchConflicts) {
       this.fetchConflicts = true;
     }
+    this.partitionKey = options.partitionKey;
   },
 
   // HACK: the doc needs to know about the database, but it may be
diff --git a/app/core/router.js b/app/core/router.js
index cb2c43d..49e5e40 100644
--- a/app/core/router.js
+++ b/app/core/router.js
@@ -105,10 +105,10 @@
     this.setModuleRoutes(addons);
 
     this.lastPages = [];
-    //keep last pages visited in Fauxton
+    //keep last few pages visited in Fauxton
     Backbone.history.on('route', function () {
       this.lastPages.push(Backbone.history.fragment);
-      if (this.lastPages.length > 2) {
+      if (this.lastPages.length > 5) {
         this.lastPages.shift();
       }
     }, this);