[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);