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/10/22 10:30:09 UTC

[GitHub] garrensmith closed pull request #1144: [partitioned-dbs] Updates to doc editor

garrensmith closed pull request #1144: [partitioned-dbs] Updates to doc editor
URL: https://github.com/apache/couchdb-fauxton/pull/1144
 
 
   

This is a PR merged from a forked repository.
As GitHub hides the original diff on merge, it is displayed below for
the sake of provenance:

As this is a foreign pull request (from a fork), the diff is supplied
below (as it won't show otherwise due to GitHub magic):

diff --git a/app/addons/databases/__tests__/reducers.test.js b/app/addons/databases/__tests__/reducers.test.js
new file mode 100644
index 000000000..4f57bab32
--- /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 1935491c1..d8927af50 100644
--- a/app/addons/databases/actions.js
+++ b/app/addons/databases/actions.js
@@ -17,6 +17,7 @@ import DatabasesBase from '../databases/base';
 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 @@ export default {
     }).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 e8d3a534d..bf7bd5711 100644
--- a/app/addons/databases/actiontypes.js
+++ b/app/addons/databases/actiontypes.js
@@ -15,5 +15,7 @@ export default {
   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 000000000..79f980bb2
--- /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 c4218d4f0..2a082bbab 100644
--- a/app/addons/databases/base.js
+++ b/app/addons/databases/base.js
@@ -16,6 +16,7 @@ import { get } from "../../core/ajax";
 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 @@ function checkPartitionedDatabaseFeature () {
 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 74da7c4ce..a20a5f67a 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 @@ export default function databases(state = initialState, action) {
         ...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 92%
rename from app/addons/documents/__tests__/partition-key.js
rename to app/addons/documents/__tests__/partition-key.test.js
index 75005e912..7bce1f0a8 100644
--- a/app/addons/documents/__tests__/partition-key.js
+++ b/app/addons/documents/__tests__/partition-key.test.js
@@ -17,18 +17,6 @@ import PartitionKeySelector from '../partition-key/PartitionKeySelector';
 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 4505015d1..a06171c42 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('AllDocs', () => {
   });
 });
 
+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 c830b2cf1..09b4c9ca6 100644
--- a/app/addons/documents/__tests__/results-toolbar.test.js
+++ b/app/addons/documents/__tests__/results-toolbar.test.js
@@ -50,4 +50,9 @@ describe('Results Toolbar', () => {
     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 abaa037fa..c7dbccc2c 100644
--- a/app/addons/documents/components/results-toolbar.js
+++ b/app/addons/documents/components/results-toolbar.js
@@ -32,7 +32,8 @@ export class ResultsToolBar extends React.Component {
       hasSelectedItem,
       toggleSelectAll,
       isLoading,
-      databaseName
+      databaseName,
+      partitionKey
     } = this.props;
 
     // Determine if we need to display the bulk action selector
@@ -57,7 +58,7 @@ export class ResultsToolBar extends React.Component {
     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 @@ ResultsToolBar.propTypes = {
   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 35505f4fd..c0ab5c047 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 database = new Databases.Model({ id: 'a/special?db' });
 const defaultProps = {
   isLoading: true,
   isNewDoc: true,
+  isDbPartitioned: false,
   database: database,
   doc: new Documents.NewDoc(null, { database: database }),
   conflictCount: 0,
@@ -162,12 +166,55 @@ describe('DocEditorScreen', () => {
     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 @@ describe("Custom Extension Buttons", () => {
     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 12120513d..7977ab4cd 100644
--- a/app/addons/documents/doc-editor/actions.js
+++ b/app/addons/documents/doc-editor/actions.js
@@ -48,7 +48,7 @@ const initDocEditor = (params) => (dispatch) => {
   });
 };
 
-const saveDoc = (doc, isValidDoc, onSave) => {
+const saveDoc = (doc, isValidDoc, onSave, navigateToUrl) => {
   if (isValidDoc) {
     FauxtonAPI.addNotification({
       msg: 'Saving document.',
@@ -57,7 +57,11 @@ const saveDoc = (doc, isValidDoc, onSave) => {
 
     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 3d6776e68..7ae7968bb 100644
--- a/app/addons/documents/doc-editor/components/CloneDocModal.js
+++ b/app/addons/documents/doc-editor/components/CloneDocModal.js
@@ -43,7 +43,13 @@ export default class CloneDocModal extends React.Component {
     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 3946ca3f9..17eeba462 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 mapStateToProps = ({ docEditor }, ownProps) => {
 
 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 6518d04a2..f49aef718 100644
--- a/app/addons/documents/doc-editor/components/DocEditorScreen.js
+++ b/app/addons/documents/doc-editor/components/DocEditorScreen.js
@@ -31,8 +31,10 @@ export default class DocEditorScreen extends React.Component {
   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 @@ export default class DocEditorScreen extends React.Component {
       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 @@ export default class DocEditorScreen extends React.Component {
   }
 
   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 @@ export default class DocEditorScreen extends React.Component {
     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 @@ export default class DocEditorScreen extends React.Component {
   };
 
   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 a59d3a95b..42879ac0b 100644
--- a/app/addons/documents/helpers.js
+++ b/app/addons/documents/helpers.js
@@ -89,9 +89,13 @@ const truncateDoc = (docString, maxRows) => {
   };
 };
 
-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 4ba8da864..ca940ecc9 100644
--- a/app/addons/documents/index-results/containers/IndexResultsContainer.js
+++ b/app/addons/documents/index-results/containers/IndexResultsContainer.js
@@ -55,7 +55,8 @@ const mapStateToProps = ({indexResults}, ownProps) => {
     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 3efae93c4..fe6e48ed2 100644
--- a/app/addons/documents/layouts.js
+++ b/app/addons/documents/layouts.js
@@ -185,7 +185,8 @@ export const DocsTabsSidebarLayout = ({
     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 159559b28..a920b8e6a 100644
--- a/app/addons/documents/resources.js
+++ b/app/addons/documents/resources.js
@@ -71,17 +71,18 @@ Documents.DdocInfo = FauxtonAPI.Model.extend({
 
 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 f2509abc2..421bc14b2 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 @@ const DocEditorRouteObject = FauxtonAPI.RouteObject.extend({
     '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 @@ const DocEditorRouteObject = FauxtonAPI.RouteObject.extend({
     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 @@ const DocEditorRouteObject = FauxtonAPI.RouteObject.extend({
 
     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 c59bd2e49..ac5c0e0a3 100644
--- a/app/addons/documents/shared-resources.js
+++ b/app/addons/documents/shared-resources.js
@@ -51,6 +51,7 @@ Documents.Doc = FauxtonAPI.Model.extend({
     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 cb2c43d48..49e5e40c1 100644
--- a/app/core/router.js
+++ b/app/core/router.js
@@ -105,10 +105,10 @@ export default Backbone.Router.extend({
     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);


 

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