Repository: couchdb-fauxton Updated Branches: refs/heads/master 4a40e3f9e -> d7641f16e
Mango: creation and listing of indexes Part 1/2 for Mango: Creation of Mango indexes and listing them. Disabled pagination and bulk-deletion for now, see: https://issues.apache.org/jira/browse/COUCHDB-2651 https://issues.apache.org/jira/browse/COUCHDB-2652 Use the direct urls to access the features: http://localhost:8000/#database/$YOUR_DATABASE/_index http://localhost:8000/#database/$YOUR_DATABASE/_indexlist Additionally prepares the app for i18n. Additionally removes the listing of Mango created indexes which are not editable from the sidebar COUCHDB-2627 PR: #343 PR-URL: https://github.com/apache/couchdb-fauxton/pull/343 Reviewed-By: garren smith <[email protected]> Project: http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/repo Commit: http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/commit/d7641f16 Tree: http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/tree/d7641f16 Diff: http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/diff/d7641f16 Branch: refs/heads/master Commit: d7641f16e7a12faff896136826b43f7affb0a393 Parents: 4a40e3f Author: Robert Kowalski <[email protected]> Authored: Fri Mar 27 14:16:43 2015 +0100 Committer: Robert Kowalski <[email protected]> Committed: Tue Mar 31 17:20:15 2015 +0200 ---------------------------------------------------------------------- Gruntfile.js | 7 + app/addons/components/assets/less/docs.less | 6 + .../components/react-components.react.jsx | 43 +++-- app/addons/components/tests/docSpec.react.jsx | 19 ++- app/addons/documents/base.js | 28 ++++ app/addons/documents/index-editor/stores.js | 4 +- app/addons/documents/index-results/actions.js | 2 +- .../index-results.components.react.jsx | 16 +- app/addons/documents/index-results/stores.js | 29 +++- .../tests/index-results.actionsSpec.js | 8 + .../tests/index-results.storesSpec.js | 20 +-- app/addons/documents/mango/mango.actions.js | 58 +++++++ app/addons/documents/mango/mango.actiontypes.js | 17 ++ .../documents/mango/mango.components.react.jsx | 156 +++++++++++++++++++ app/addons/documents/mango/mango.stores.js | 63 ++++++++ .../mango/tests/mango.componentsSpec.react.jsx | 93 +++++++++++ app/addons/documents/resources.js | 71 +++++++++ app/addons/documents/routes-documents.js | 29 +--- app/addons/documents/routes-index-editor.js | 4 +- app/addons/documents/routes-mango.js | 154 ++++++++++++++++++ app/addons/documents/routes.js | 9 +- app/addons/documents/shared-resources.js | 8 + app/addons/documents/shared-routes.js | 22 +++ app/addons/documents/shared-views.js | 5 + app/addons/documents/tests/headerSpec.react.jsx | 2 +- .../documents/tests/nightwatch/mangoIndex.js | 48 ++++++ .../tests/nightwatch/mangoIndexList.js | 29 ++++ app/addons/documents/tests/resourcesSpec.js | 95 ++++++++++- .../tests/viewIndex.componentsSpec.react.jsx | 87 ++++++++++- app/addons/documents/views-mango.js | 66 ++++++++ app/initialize.js.underscore | 3 +- i18n.json.default | 8 + package.json | 1 + tasks/fauxton.js | 11 +- tasks/helper.js | 11 ++ .../custom-commands/populateDatabase.js | 30 +++- 36 files changed, 1164 insertions(+), 98 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/d7641f16/Gruntfile.js ---------------------------------------------------------------------- diff --git a/Gruntfile.js b/Gruntfile.js index 781d97d..719f23c 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -121,6 +121,13 @@ module.exports = function (grunt) { }; var settings = helper.readSettingsFile(); + + var i18n = JSON.stringify(helper.readI18nFile(), null, ' '); + + ['development', 'release', 'couchapp'].forEach(function (key) { + settings.template[key].app.i18n = i18n; + }); + return settings.template || defaultSettings; }(); http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/d7641f16/app/addons/components/assets/less/docs.less ---------------------------------------------------------------------- diff --git a/app/addons/components/assets/less/docs.less b/app/addons/components/assets/less/docs.less index 97957c2..3f8a8f8 100644 --- a/app/addons/components/assets/less/docs.less +++ b/app/addons/components/assets/less/docs.less @@ -76,6 +76,12 @@ padding-left: 23px; // 7px to the right-border + 16px around } } + .checkbox-dummy { + width: 20px; + height: 20px; + padding-left: 23px; + margin-right: 15px; + } .doc-item { width: auto; overflow: hidden; http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/d7641f16/app/addons/components/react-components.react.jsx ---------------------------------------------------------------------- diff --git a/app/addons/components/react-components.react.jsx b/app/addons/components/react-components.react.jsx index 91d04cd..26d27cd 100644 --- a/app/addons/components/react-components.react.jsx +++ b/app/addons/components/react-components.react.jsx @@ -65,30 +65,35 @@ function (app, FauxtonAPI, React, Components, beautifyHelper) { var CodeEditor = React.createClass({ render: function () { var code = this.aceEditor ? this.aceEditor.getValue() : this.props.code; - var docsLink; - if (this.props.docs) { - docsLink = <a - className="help-link" - data-bypass="true" - href={this.props.docs} - target="_blank" - > - <i className="icon-question-sign"></i> - </a>; - - } return ( <div className="control-group"> - <label htmlFor="ace-function"> - <strong>{this.props.title}</strong> - {docsLink} - </label> + {this.getTitleFragment()} <div className="js-editor" id={this.props.id}>{this.props.code}</div> <Beautify code={code} beautifiedCode={this.setEditorValue} /> </div> ); }, + getTitleFragment: function () { + if (!this.props.docs) { + return <strong>{this.props.title}</strong>; + } + + return ( + <label> + <strong>{this.props.title}</strong> + <a + className="help-link" + data-bypass="true" + href={this.props.docs} + target="_blank" + > + <i className="icon-question-sign"></i> + </a>; + </label> + ); + }, + setEditorValue: function (code) { this.aceEditor.setValue(code); //this is not a good practice normally but because we working with a backbone view as the mapeditor @@ -213,6 +218,11 @@ function (app, FauxtonAPI, React, Components, beautifyHelper) { }, getCheckbox: function () { + + if (!this.props.isDeletable) { + return <div className="checkbox-dummy"></div>; + } + return ( <div className="checkbox inline"> <input @@ -286,7 +296,6 @@ function (app, FauxtonAPI, React, Components, beautifyHelper) { } }); - var ReactComponents = { ConfirmButton: ConfirmButton, ToggleHeaderButton: ToggleHeaderButton, http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/d7641f16/app/addons/components/tests/docSpec.react.jsx ---------------------------------------------------------------------- diff --git a/app/addons/components/tests/docSpec.react.jsx b/app/addons/components/tests/docSpec.react.jsx index 119abe6..be1f7ce 100644 --- a/app/addons/components/tests/docSpec.react.jsx +++ b/app/addons/components/tests/docSpec.react.jsx @@ -51,7 +51,7 @@ define([ it('you can check it', function () { el = TestUtils.renderIntoDocument( - <ReactComponents.Document checked={true} docIdentifier="foo" />, + <ReactComponents.Document isDeletable={true} checked={true} docIdentifier="foo" />, container ); assert.equal($(el.getDOMNode()).find('input[type="checkbox"]').attr('checked'), 'checked'); @@ -59,7 +59,7 @@ define([ it('you can uncheck it', function () { el = TestUtils.renderIntoDocument( - <ReactComponents.Document docIdentifier="foo" />, + <ReactComponents.Document isDeletable={true} docIdentifier="foo" />, container ); assert.equal($(el.getDOMNode()).find('input[type="checkbox"]').attr('checked'), undefined); @@ -69,7 +69,7 @@ define([ var spy = sinon.spy(); el = TestUtils.renderIntoDocument( - <ReactComponents.Document docChecked={spy} docIdentifier="foo" />, + <ReactComponents.Document isDeletable={true} docChecked={spy} docIdentifier="foo" />, container ); var testEl = $(el.getDOMNode()).find('input[type="checkbox"]')[0]; @@ -81,12 +81,23 @@ define([ var spy = sinon.spy(); el = TestUtils.renderIntoDocument( - <ReactComponents.Document onDoubleClick={spy} docIdentifier="foo" />, + <ReactComponents.Document isDeletable={true} onDoubleClick={spy} docIdentifier="foo" />, container ); React.addons.TestUtils.Simulate.doubleClick(el.getDOMNode()); assert.ok(spy.calledOnce); }); + + it('can render without checkbox', function () { + var spy = sinon.spy(); + + el = TestUtils.renderIntoDocument( + <ReactComponents.Document isDeletable={false} onDoubleClick={spy} docIdentifier="foo" />, + container + ); + assert.notOk($(el.getDOMNode()).find('input[type="checkbox"]').length); + assert.ok($(el.getDOMNode()).find('.checkbox-dummy').length); + }); }); }); http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/d7641f16/app/addons/documents/base.js ---------------------------------------------------------------------- diff --git a/app/addons/documents/base.js b/app/addons/documents/base.js index 776f514..d6ac60d 100644 --- a/app/addons/documents/base.js +++ b/app/addons/documents/base.js @@ -110,5 +110,33 @@ function (app, FauxtonAPI, Documents) { return '/database/' + database + '/' ; }, }); + + FauxtonAPI.registerUrls('mango', { + + 'index-server': function (db, query) { + if (!query) { + query = ''; + } + + return app.host + '/' + db + '/_index' + query; + }, + + 'index-apiurl': function (db, query) { + if (!query) { + query = ''; + } + + return window.location.origin + '/' + db + '/_index' + query; + }, + + 'index-app': function (db, query) { + if (!query) { + query = ''; + } + + return 'database/' + db + '/_index' + query; + } + }); + return Documents; }); http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/d7641f16/app/addons/documents/index-editor/stores.js ---------------------------------------------------------------------- diff --git a/app/addons/documents/index-editor/stores.js b/app/addons/documents/index-editor/stores.js index 2070df3..f9a3606 100644 --- a/app/addons/documents/index-editor/stores.js +++ b/app/addons/documents/index-editor/stores.js @@ -71,7 +71,9 @@ function (FauxtonAPI, ActionTypes) { }, getDesignDocs: function () { - return this._designDocs; + return this._designDocs.filter(function (ddoc) { + return ddoc.get('doc').language !== 'query'; + }); }, getDesignDocId: function () { http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/d7641f16/app/addons/documents/index-results/actions.js ---------------------------------------------------------------------- diff --git a/app/addons/documents/index-results/actions.js b/app/addons/documents/index-results/actions.js index 88d57bf..0f91295 100644 --- a/app/addons/documents/index-results/actions.js +++ b/app/addons/documents/index-results/actions.js @@ -74,7 +74,7 @@ function (app, FauxtonAPI, ActionTypes, Stores, HeaderStores, HeaderActions, Doc reloadResultsList: function () { return this.newResultsList({ collection: indexResultsStore.getCollection(), - deleteable: indexResultsStore.isDeleteable() + isListDeletable: indexResultsStore.isListDeletable() }); }, http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/d7641f16/app/addons/documents/index-results/index-results.components.react.jsx ---------------------------------------------------------------------- diff --git a/app/addons/documents/index-results/index-results.components.react.jsx b/app/addons/documents/index-results/index-results.components.react.jsx index 61d1955..0d0d8d1 100644 --- a/app/addons/documents/index-results/index-results.components.react.jsx +++ b/app/addons/documents/index-results/index-results.components.react.jsx @@ -43,7 +43,7 @@ function (app, FauxtonAPI, React, Stores, Actions, Components, Documents) { }, getUrlFragment: function (url) { - if (this.props.hasReduce) { + if (!this.props.isEditable) { return null; } @@ -55,6 +55,7 @@ function (app, FauxtonAPI, React, Stores, Actions, Components, Documents) { getDocumentList: function () { return _.map(this.props.results, function (doc) { + return ( <Components.Document key={doc.id} @@ -64,6 +65,7 @@ function (app, FauxtonAPI, React, Stores, Actions, Components, Documents) { docContent={doc.content} checked={this.props.isSelected(doc.id)} docChecked={this.props.docChecked} + isDeletable={doc.isDeletable} docIdentifier={doc.id} > {this.getUrlFragment('#' + doc.url)} </Components.Document> @@ -79,7 +81,7 @@ function (app, FauxtonAPI, React, Stores, Actions, Components, Documents) { loadLines = <Components.LoadLines />; } - if (this.props.isDeleteable) { + if (this.props.isListDeletable) { classNames += ' show-select'; } @@ -111,10 +113,10 @@ function (app, FauxtonAPI, React, Stores, Actions, Components, Documents) { return { hasResults: store.hasResults(), results: store.getResults(), - isDeleteable: store.isDeleteable(), + isListDeletable: store.isListDeletable(), isSelected: store.isSelected, - hasReduce: store.hasReduce(), - isLoading: store.isLoading() + isLoading: store.isLoading(), + isEditable: store.isEditable() }; }, @@ -149,8 +151,8 @@ function (app, FauxtonAPI, React, Stores, Actions, Components, Documents) { view = <ResultsScreen isCollapsed={this.isCollapsed} isSelected={this.isSelected} - hasReduce={this.state.hasReduce} - isDeleteable={this.state.isDeleteable} + isEditable={this.state.isEditable} + isListDeletable={this.state.isListDeletable} docChecked={this.docChecked} isLoading={this.state.isLoading} results={this.state.results} />; http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/d7641f16/app/addons/documents/index-results/stores.js ---------------------------------------------------------------------- diff --git a/app/addons/documents/index-results/stores.js b/app/addons/documents/index-results/stores.js index 605ee6f..52cf03e 100644 --- a/app/addons/documents/index-results/stores.js +++ b/app/addons/documents/index-results/stores.js @@ -27,7 +27,7 @@ function (FauxtonAPI, ActionTypes, HeaderActionTypes, Documents) { Stores.IndexResultsStore = FauxtonAPI.Store.extend({ initialize: function () { - this._deleteable = false; + this._isListDeletable = false; this._collection = []; this.clearSelectedItems(); this.clearCollapsedDocs(); @@ -44,16 +44,29 @@ function (FauxtonAPI, ActionTypes, HeaderActionTypes, Documents) { newResults: function (options) { this._collection = options.collection; - this._deleteable = options.deleteable; + this._isListDeletable = options.isListDeletable; this.clearSelectedItems(); this.clearCollapsedDocs(); }, - hasReduce: function () { - if (!this._collection || !this._collection.params) { + isEditable: function (doc) { + if (!this._collection) { return false; } - return this._collection.params.reduce; + + if (!this._collection.isEditable) { + return false; + } + + return this._collection.isEditable(); + }, + + isDeletable: function (doc) { + return doc.isDeletable(); + }, + + isListDeletable: function () { + return this._isListDeletable; }, getCollection: function () { @@ -79,7 +92,7 @@ function (FauxtonAPI, ActionTypes, HeaderActionTypes, Documents) { return doc.id; } - if (!_.isNull(doc.get('key'))) { + if (doc.get('key')) { return doc.get('key').toString(); } @@ -92,7 +105,9 @@ function (FauxtonAPI, ActionTypes, HeaderActionTypes, Documents) { content: this.getDocContent(doc), id: this.getDocId(doc), keylabel: doc.isFromView() ? 'key' : 'id', - url: doc.isFromView() ? doc.url('app') : doc.url('web-index') + url: doc.isFromView() ? doc.url('app') : doc.url('web-index'), + isDeletable: this.isDeletable(doc), + isEditable: this.isEditable(doc), }; }, this); }, http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/d7641f16/app/addons/documents/index-results/tests/index-results.actionsSpec.js ---------------------------------------------------------------------- diff --git a/app/addons/documents/index-results/tests/index-results.actionsSpec.js b/app/addons/documents/index-results/tests/index-results.actionsSpec.js index 5ec9aab..1bbba77 100644 --- a/app/addons/documents/index-results/tests/index-results.actionsSpec.js +++ b/app/addons/documents/index-results/tests/index-results.actionsSpec.js @@ -153,6 +153,10 @@ define([ }; var stub = sinon.stub(store, 'createBulkDeleteFromSelected'); stub.returns(bulkDelete); + var reloadResultsListStub = sinon.stub(Actions, 'reloadResultsList'); + var stubPromise = FauxtonAPI.Deferred(); + stubPromise.resolve(); + reloadResultsListStub.returns(stubPromise); Actions.deleteSelected(); @@ -196,6 +200,10 @@ define([ }; var stub = sinon.stub(store, 'createBulkDeleteFromSelected'); stub.returns(bulkDelete); + var reloadResultsListStub = sinon.stub(Actions, 'reloadResultsList'); + var stubPromise = FauxtonAPI.Deferred(); + stubPromise.resolve(); + reloadResultsListStub.returns(stubPromise); Actions.deleteSelected(); http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/d7641f16/app/addons/documents/index-results/tests/index-results.storesSpec.js ---------------------------------------------------------------------- diff --git a/app/addons/documents/index-results/tests/index-results.storesSpec.js b/app/addons/documents/index-results/tests/index-results.storesSpec.js index 574082f..984d496 100644 --- a/app/addons/documents/index-results/tests/index-results.storesSpec.js +++ b/app/addons/documents/index-results/tests/index-results.storesSpec.js @@ -347,26 +347,22 @@ define([ }); }); - describe('hasReduce', function () { + describe('isEditable', function () { it('returns false for no collection', function () { store._collection = null; - assert.notOk(store.hasReduce()); + assert.notOk(store.isEditable()); }); - it('returns false for no params', function () { + it('returns false for empty collection', function () { store._collection = []; - assert.notOk(store.hasReduce()); + assert.notOk(store.isEditable()); }); - it('returns true for reduce param', function () { - store._collection = []; - store._collection.param = { - reduce: true - }; - assert.notOk(store.hasReduce()); - + it('delegates to collection', function () { + store._collection = {}; + store._collection.isEditable = function () { return 'stub'; }; + assert.equal(store.isEditable(), 'stub'); }); - }); }); http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/d7641f16/app/addons/documents/mango/mango.actions.js ---------------------------------------------------------------------- diff --git a/app/addons/documents/mango/mango.actions.js b/app/addons/documents/mango/mango.actions.js new file mode 100644 index 0000000..d3df146 --- /dev/null +++ b/app/addons/documents/mango/mango.actions.js @@ -0,0 +1,58 @@ +// 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. + +define([ + 'app', + 'api', + 'addons/documents/resources', + 'addons/documents/mango/mango.actiontypes', + 'addons/documents/mango/mango.stores', + 'addons/documents/index-results/actions' + +], +function (app, FauxtonAPI, Documents, ActionTypes, Stores, IndexResultsActions) { + var store = Stores.mangoStore; + + return { + + setDatabase: function (options) { + FauxtonAPI.dispatch({ + type: ActionTypes.MANGO_SET_DB, + options: options + }); + }, + + saveQuery: function (options) { + var mangoIndex = new Documents.MangoIndex(JSON.parse(options.queryCode), {database: options.database}); + + FauxtonAPI.addNotification({ + msg: 'Saving Index for Query...', + type: 'info', + clear: true + }); + + mangoIndex.save().then(function (res) { + var msg = res.result === 'created' ? 'Index created' : 'Index already exits', + url = FauxtonAPI.urls('mango', 'index-app', options.database.safeID()); + + FauxtonAPI.addNotification({ + msg: msg, + type: 'success', + clear: true + }); + + IndexResultsActions.reloadResultsList(); + }.bind(this)); + + } + }; +}); http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/d7641f16/app/addons/documents/mango/mango.actiontypes.js ---------------------------------------------------------------------- diff --git a/app/addons/documents/mango/mango.actiontypes.js b/app/addons/documents/mango/mango.actiontypes.js new file mode 100644 index 0000000..112eacd --- /dev/null +++ b/app/addons/documents/mango/mango.actiontypes.js @@ -0,0 +1,17 @@ +// 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. + +define([], function () { + return { + MANGO_SET_DB: 'MANGO_SET_DB', + }; +}); http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/d7641f16/app/addons/documents/mango/mango.components.react.jsx ---------------------------------------------------------------------- diff --git a/app/addons/documents/mango/mango.components.react.jsx b/app/addons/documents/mango/mango.components.react.jsx new file mode 100644 index 0000000..8764779 --- /dev/null +++ b/app/addons/documents/mango/mango.components.react.jsx @@ -0,0 +1,156 @@ +// 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. + +define([ + 'app', + 'api', + 'react', + 'addons/documents/mango/mango.stores', + 'addons/documents/mango/mango.actions', + 'addons/components/react-components.react', + + 'plugins/prettify' +], + +function (app, FauxtonAPI, React, Stores, Actions, ReactComponents) { + var mangoStore = Stores.mangoStore; + + var PaddedBorderedBox = ReactComponents.PaddedBorderedBox; + var CodeEditor = ReactComponents.CodeEditor; + var ConfirmButton = ReactComponents.ConfirmButton; + + var HelpScreen = React.createClass({ + render: function () { + return ( + <div className="watermark-logo"> + <h3>{this.props.title}</h3> + <div> + Create an Index to query it afterwards.<br/><br/> + The example on the left shows how to create an index for the field '_id' + </div> + </div> + ); + } + }); + + var MangoIndexEditorController = React.createClass({ + getInitialState: function () { + return this.getStoreState(); + }, + + getStoreState: function () { + return { + queryCode: mangoStore.getQueryCode(), + database: mangoStore.getDatabase(), + }; + }, + + onChange: function () { + this.setState(this.getStoreState()); + }, + + componentDidMount: function () { + mangoStore.on('change', this.onChange, this); + }, + + componentWillUnmount: function () { + mangoStore.off('change', this.onChange); + }, + + render: function () { + return ( + <div className="editor-wrapper span5 scrollable"> + <PaddedBorderedBox> + <div className="editor-description">{this.props.description}</div> + </PaddedBorderedBox> + <PaddedBorderedBox> + <strong>Database</strong> + <div className="db-title">{this.state.database.id}</div> + </PaddedBorderedBox> + <form className="form-horizontal" onSubmit={this.saveQuery}> + <PaddedBorderedBox> + <CodeEditor + id="query-field" + ref="indexQueryEditor" + title={'Index'} + docs={false} + code={this.state.queryCode} /> + </PaddedBorderedBox> + <div className="padded-box"> + <div className="control-group"> + <ConfirmButton text="Create Index" /> + </div> + </div> + </form> + </div> + ); + }, + + getEditor: function () { + return this.refs.indexQueryEditor.getEditor(); + }, + + hasValidCode: function () { + var editor = this.getEditor(); + return editor.hadValidCode(); + }, + + clearNotifications: function () { + var editor = this.getEditor(); + editor.editSaved(); + }, + + saveQuery: function (event) { + event.preventDefault(); + + if (!this.hasValidCode()) { + FauxtonAPI.addNotification({ + msg: 'Please fix the Javascript errors and try again.', + type: 'error', + clear: true + }); + return; + } + + this.clearNotifications(); + + Actions.saveQuery({ + database: this.state.database, + queryCode: this.refs.indexQueryEditor.getValue() + }); + } + }); + + var Views = { + renderHelpScreen: function (el) { + React.render( + <HelpScreen title={app.i18n.en_US['mango-help-title']} />, + el + ); + }, + removeHelpScreen: function (el) { + React.unmountComponentAtNode(el); + }, + renderMangoIndexEditor: function (el) { + React.render( + <MangoIndexEditorController description={app.i18n.en_US['mango-descripton']} />, + el + ); + }, + removeMangoIndexEditor: function (el) { + React.unmountComponentAtNode(el); + }, + MangoIndexEditorController: MangoIndexEditorController + }; + + return Views; +}); http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/d7641f16/app/addons/documents/mango/mango.stores.js ---------------------------------------------------------------------- diff --git a/app/addons/documents/mango/mango.stores.js b/app/addons/documents/mango/mango.stores.js new file mode 100644 index 0000000..f72a046 --- /dev/null +++ b/app/addons/documents/mango/mango.stores.js @@ -0,0 +1,63 @@ +// 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. + +define([ + 'api', + 'addons/documents/mango/mango.actiontypes' +], + +function (FauxtonAPI, ActionTypes) { + + + var defaultQuery = '{\n' + + ' "index": {\n' + + ' "fields": ["_id"]\n' + + ' },\n' + + ' "type" : "json"\n' + + '}'; + + var Stores = {}; + + Stores.MangoStore = FauxtonAPI.Store.extend({ + + getQueryCode: function () { + return defaultQuery; + }, + + setDatabase: function (options) { + this._database = options.database; + }, + + getDatabase: function () { + return this._database; + }, + + dispatch: function (action) { + switch (action.type) { + + case ActionTypes.MANGO_SET_DB: + this.setDatabase(action.options); + this.triggerChange(); + break; + + } + } + + }); + + Stores.mangoStore = new Stores.MangoStore(); + + Stores.mangoStore.dispatchToken = FauxtonAPI.dispatcher.register(Stores.mangoStore.dispatch); + + return Stores; + +}); http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/d7641f16/app/addons/documents/mango/tests/mango.componentsSpec.react.jsx ---------------------------------------------------------------------- diff --git a/app/addons/documents/mango/tests/mango.componentsSpec.react.jsx b/app/addons/documents/mango/tests/mango.componentsSpec.react.jsx new file mode 100644 index 0000000..c50897b --- /dev/null +++ b/app/addons/documents/mango/tests/mango.componentsSpec.react.jsx @@ -0,0 +1,93 @@ +// 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. + +define([ + 'api', + 'addons/documents/mango/mango.components.react', + 'addons/documents/mango/mango.stores', + 'addons/documents/mango/mango.actions', + + 'addons/documents/resources', + 'addons/databases/resources', + + 'testUtils', + 'react' +], function (FauxtonAPI, Views, Stores, MangoActions, Resources, Databases, utils, React) { + + var assert = utils.assert; + var TestUtils = React.addons.TestUtils; + + var fakeData = [ + { + ddoc: '_design/e4d338e5d6f047749f5399ab998b4fa04ba0c816', + def: { + fields: [{ + '_id': 'asc' + }] + }, + name: 'e4d338e5d6f047749f5399ab998b4fa04ba0c816', + type: 'json' + }, + { + ddoc: null, + def: { + fields: [{ + '_id': 'asc' + }] + }, + name: '_all_docs', + type: 'special' + } + ]; + + + describe('Mango IndexEditor', function () { + var database = new Databases.Model({id: 'testdb'}), + container, + editor; + + beforeEach(function () { + container = document.createElement('div'); + MangoActions.setDatabase({ + database: database + }); + $('body').append('<div id="query-field"></div>'); + }); + + afterEach(function () { + React.unmountComponentAtNode(container); + $('#query-field').remove(); + }); + + it('renders a default index definition', function () { + editor = TestUtils.renderIntoDocument(<Views.MangoIndexEditorController description="foo" />, container); + var $el = $(editor.getDOMNode()); + var payload = JSON.parse($el.find('.js-editor').text()); + assert.equal(payload.index.fields[0], '_id'); + }); + + it('renders the current database', function () { + editor = TestUtils.renderIntoDocument(<Views.MangoIndexEditorController description="foo" />, container); + var $el = $(editor.getDOMNode()); + + assert.equal($el.find('.db-title').text(), 'testdb'); + }); + + it('renders a description', function () { + editor = TestUtils.renderIntoDocument(<Views.MangoIndexEditorController description="CouchDB Query is great!" />, container); + var $el = $(editor.getDOMNode()); + + assert.equal($el.find('.editor-description').text(), 'CouchDB Query is great!'); + }); + }); + +}); http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/d7641f16/app/addons/documents/resources.js ---------------------------------------------------------------------- diff --git a/app/addons/documents/resources.js b/app/addons/documents/resources.js index e6ee676..00559b9 100644 --- a/app/addons/documents/resources.js +++ b/app/addons/documents/resources.js @@ -72,6 +72,73 @@ function (app, FauxtonAPI, Documents, PagingCollection) { } }); + Documents.MangoIndex = Documents.Doc.extend({ + idAttribute: 'name', + + isNew: function () { + // never use put + return true; + }, + + isDeletable: function () { + return this.get('type') !== 'special'; + }, + + isFromView: function () { + return false; + }, + + url: function () { + var database = this.database.safeID(); + + return FauxtonAPI.urls('mango', 'index-server', database); + } + }); + + Documents.MangoIndexCollection = PagingCollection.extend({ + model: Documents.MangoIndex, + initialize: function (_attr, options) { + var defaultLimit = FauxtonAPI.constants.MISC.DEFAULT_PAGE_SIZE; + + this.database = options.database; + this.params = _.extend({limit: defaultLimit}, options.params); + }, + + url: function () { + return this.urlRef.apply(this, arguments); + }, + + updateSeq: function () { + return false; + }, + + isEditable: function () { + return false; + }, + + parse: function (res) { + return res.indexes; + }, + + urlRef: function (params) { + var database = this.database.safeID(), + query = ''; + + if (params) { + if (!_.isEmpty(params)) { + query = '?' + $.param(params); + } else { + query = ''; + } + } else if (this.params) { + var parsedParam = Documents.QueryParams.stringify(this.params); + query = '?' + $.param(parsedParam); + } + + return FauxtonAPI.urls('mango', 'index-apiurl', database, query); + } + }); + Documents.NewDoc = Documents.Doc.extend({ fetch: function () { var uuid = new FauxtonAPI.UUID(); @@ -206,6 +273,10 @@ function (app, FauxtonAPI, Documents, PagingCollection) { } }, + isEditable: function () { + return !this.params.reduce; + }, + urlRef: function (params) { var query = ""; http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/d7641f16/app/addons/documents/routes-documents.js ---------------------------------------------------------------------- diff --git a/app/addons/documents/routes-documents.js b/app/addons/documents/routes-documents.js index ff57dac..2cf8907 100644 --- a/app/addons/documents/routes-documents.js +++ b/app/addons/documents/routes-documents.js @@ -20,6 +20,7 @@ define([ 'addons/documents/views-changes', 'addons/documents/views-index', 'addons/documents/views-doceditor', + 'addons/documents/views-mango', 'addons/databases/base', 'addons/documents/resources', @@ -28,7 +29,7 @@ define([ 'addons/documents/index-results/actions' ], -function (app, FauxtonAPI, BaseRoute, Documents, Changes, Index, DocEditor, +function (app, FauxtonAPI, BaseRoute, Documents, Changes, Index, DocEditor, Mango, Databases, Resources, Components, PaginationStores, IndexResultsActions) { @@ -44,6 +45,7 @@ function (app, FauxtonAPI, BaseRoute, Documents, Changes, Index, DocEditor, roles: ['fx_loggedIn'] }, 'database/:database/_changes': 'changes' + }, events: { @@ -77,29 +79,6 @@ function (app, FauxtonAPI, BaseRoute, Documents, Changes, Index, DocEditor, this.addSidebar(); }, - getAllDatabases: function () { - return new Databases.List(); //getAllDatabases() can be overwritten instead of hard coded into initViews - }, - - // this safely assumes the db name is valid - onSelectDatabase: function (dbName) { - this.cleanup(); - this.initViews(dbName); - - var url = FauxtonAPI.urls('allDocs', 'app', app.utils.safeURLName(dbName), ''); - FauxtonAPI.navigate(url, { - trigger: true - }); - - // we need to start listening again because cleanup() removed the listener, but in this case - // initialize() doesn't fire to re-set up the listener - this.listenToLookaheadTray(); - }, - - listenToLookaheadTray: function () { - this.listenTo(FauxtonAPI.Events, 'lookaheadTray:update', this.onSelectDatabase); - }, - designDocMetadata: function (database, ddoc) { this.footer && this.footer.remove(); this.toolsView && this.toolsView.remove(); @@ -159,7 +138,7 @@ function (app, FauxtonAPI, BaseRoute, Documents, Changes, Index, DocEditor, IndexResultsActions.newResultsList({ collection: collection, - deleteable: true + isListDeletable: true }); this.database.allDocs.paging.pageSize = PaginationStores.indexPaginationStore.getPerPage(); http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/d7641f16/app/addons/documents/routes-index-editor.js ---------------------------------------------------------------------- diff --git a/app/addons/documents/routes-index-editor.js b/app/addons/documents/routes-index-editor.js index 38c9566..4d1e3b3 100644 --- a/app/addons/documents/routes-index-editor.js +++ b/app/addons/documents/routes-index-editor.js @@ -92,7 +92,7 @@ function (app, FauxtonAPI, Helpers, BaseRoute, Documents, Index, IndexResultsActions.newResultsList({ collection: this.indexedDocs, - deleteable: false + isListDeletable: false }); this.viewEditor = this.setView('#left-content', new Index.ViewEditorReact({ @@ -142,7 +142,7 @@ function (app, FauxtonAPI, Helpers, BaseRoute, Documents, Index, this.resultList = this.setView('#dashboard-lower-content', new Index.ViewResultListReact({})); IndexResultsActions.newResultsList({ collection: [], - deleteable: false + isListDeletable: false }); } http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/d7641f16/app/addons/documents/routes-mango.js ---------------------------------------------------------------------- diff --git a/app/addons/documents/routes-mango.js b/app/addons/documents/routes-mango.js new file mode 100644 index 0000000..baade58 --- /dev/null +++ b/app/addons/documents/routes-mango.js @@ -0,0 +1,154 @@ +// 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. + +define([ + 'app', + 'api', + + // Modules + 'addons/documents/helpers', + 'addons/documents/shared-routes', + 'addons/documents/views-mango', + 'addons/databases/resources', + 'addons/fauxton/components', + 'addons/documents/resources', + 'addons/documents/views', + + + 'addons/documents/index-results/actions', + 'addons/documents/pagination/stores', + +], + +function (app, FauxtonAPI, Helpers, BaseRoute, Mango, Databases, + Components, Resources, Documents, IndexResultsActions, PaginationStores) { + + var MangoIndexList = BaseRoute.extend({ + layout: 'with_tabs_sidebar', + routes: { + 'database/:database/_indexlist(:extra)': { + route: 'mangoIndexList', + roles: ['fx_loggedIn'] + }, + + }, + + establish: function () { + return [ + this.designDocs.fetch({reset: true}), + this.allDatabases.fetchOnce() + ]; + }, + + initialize: function (route, masterLayout, options) { + var databaseName = options[0]; + this.databaseName = databaseName; + this.database = new Databases.Model({id: databaseName}); + + // magic methods + this.allDatabases = this.getAllDatabases(); + this.createDesignDocsCollection(); + this.addLeftHeader(); + this.addSidebar(); + + this.rightHeader = this.setView('#right-header', new Documents.Views.RightAllDocsHeader({ + database: this.database + })); + }, + + mangoIndexList: function () { + var params = this.createParams(), + urlParams = params.urlParams, + mangoIndexCollection = new Resources.MangoIndexCollection(null, { + database: this.database, + params: null, + paging: { + pageSize: PaginationStores.indexPaginationStore.getPerPage() + } + }); + + this.viewEditor && this.viewEditor.remove(); + this.headerView && this.headerView.remove(); + + this.sidebar.setSelectedTab('mango-indexes'); + + IndexResultsActions.newResultsList({ + collection: mangoIndexCollection, + isListDeletable: false + }); + + this.reactHeader = this.setView('#react-headerbar', new Documents.Views.ReactHeaderbar()); + + this.leftheader.updateCrumbs(this.getCrumbs(this.database)); + this.rightHeader.hideQueryOptions(); + + this.resultList = this.setView('#dashboard-lower-content', new Mango.MangoIndexListReact()); + + this.apiUrl = function () { + return [mangoIndexCollection.urlRef(urlParams), FauxtonAPI.constants.DOC_URLS.GENERAL]; + }; + } + }); + + var MangoIndexEditorAndResults = BaseRoute.extend({ + layout: 'two_pane', + routes: { + 'database/:database/_index': { + route: 'createIndex', + roles: ['fx_loggedIn'] + } + }, + + initialize: function (route, masterLayout, options) { + var databaseName = options[0]; + + this.databaseName = databaseName; + this.database = new Databases.Model({id: databaseName}); + }, + + createIndex: function (database) { + var params = this.createParams(), + urlParams = params.urlParams, + mangoIndexCollection = new Resources.MangoIndexCollection(null, { + database: this.database + }); + + IndexResultsActions.newResultsList({ + collection: mangoIndexCollection, + isListDeletable: false + }); + + this.breadcrumbs = this.setView('#breadcrumbs', new Components.Breadcrumbs({ + toggleDisabled: true, + crumbs: [ + {'type': 'back', 'link': Helpers.getPreviousPage(this.database)}, + {'name': 'Create new index', 'link': Databases.databaseUrl(this.database) } + ] + })); + + this.resultList = this.setView('#dashboard-lower-content', new Mango.HelpScreen()); + + this.mangoEditor = this.setView('#left-content', new Mango.MangoIndexEditorReact({ + database: this.database + })); + + this.apiUrl = function () { + return [mangoIndexCollection.urlRef(urlParams), FauxtonAPI.constants.DOC_URLS.GENERAL]; + }; + } + }); + + return { + MangoIndexEditorAndResults: MangoIndexEditorAndResults, + MangoIndexList: MangoIndexList + }; +}); http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/d7641f16/app/addons/documents/routes.js ---------------------------------------------------------------------- diff --git a/app/addons/documents/routes.js b/app/addons/documents/routes.js index 0f16d4a..ad92ded 100644 --- a/app/addons/documents/routes.js +++ b/app/addons/documents/routes.js @@ -14,16 +14,19 @@ define([ "addons/documents/views", "addons/documents/routes-documents", 'addons/documents/routes-doc-editor', - 'addons/documents/routes-index-editor' + 'addons/documents/routes-index-editor', + 'addons/documents/routes-mango' ], -function (Documents, DocumentsRouteObject, docEditor, IndexEditorRouteObject) { +function (Documents, DocumentsRouteObject, docEditor, IndexEditorRouteObject, Mango) { Documents.RouteObjects = [ docEditor.DocEditorRouteObject, docEditor.NewDocEditorRouteObject, DocumentsRouteObject, - IndexEditorRouteObject + IndexEditorRouteObject, + Mango.MangoIndexList, + Mango.MangoIndexEditorAndResults ]; return Documents; http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/d7641f16/app/addons/documents/shared-resources.js ---------------------------------------------------------------------- diff --git a/app/addons/documents/shared-resources.js b/app/addons/documents/shared-resources.js index 04098c0..4800480 100644 --- a/app/addons/documents/shared-resources.js +++ b/app/addons/documents/shared-resources.js @@ -57,6 +57,10 @@ define([ return this.id && this.id.match(/^_design\//) ? "design doc" : "doc"; }, + isDeletable: function () { + return true; + }, + isFromView: function () { return !this.id; }, @@ -215,6 +219,10 @@ define([ } }, + isEditable: function () { + return true; + }, + urlRef: function (context, params) { var query = ""; http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/d7641f16/app/addons/documents/shared-routes.js ---------------------------------------------------------------------- diff --git a/app/addons/documents/shared-routes.js b/app/addons/documents/shared-routes.js index 4766203..9a59afd 100644 --- a/app/addons/documents/shared-routes.js +++ b/app/addons/documents/shared-routes.js @@ -43,6 +43,28 @@ define([ }); }, + onSelectDatabase: function (dbName) { + this.cleanup(); + this.initViews(dbName); + + var url = FauxtonAPI.urls('allDocs', 'app', app.utils.safeURLName(dbName), ''); + FauxtonAPI.navigate(url, { + trigger: true + }); + + // we need to start listening again because cleanup() removed the listener, but in this case + // initialize() doesn't fire to re-set up the listener + this.listenToLookaheadTray(); + }, + + listenToLookaheadTray: function () { + this.listenTo(FauxtonAPI.Events, 'lookaheadTray:update', this.onSelectDatabase); + }, + + getAllDatabases: function () { + return new Databases.List(); //getAllDatabases() can be overwritten instead of hard coded into initViews + }, + showQueryOptions: function (urlParams, ddoc, viewName) { var promise = this.designDocs.fetch({reset: true}), that = this, http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/d7641f16/app/addons/documents/shared-views.js ---------------------------------------------------------------------- diff --git a/app/addons/documents/shared-views.js b/app/addons/documents/shared-views.js index 93876a7..627c682 100644 --- a/app/addons/documents/shared-views.js +++ b/app/addons/documents/shared-views.js @@ -118,6 +118,11 @@ function (app, FauxtonAPI, Components, Documents, Databases) { this.designDocList = []; this.collection.each(function (design) { + + if (design.get('doc').language === 'query') { + return; + } + if (design.has('doc')) { design.collection = this.collection; var view = this.insertView(new Views.DdocSidenav({ http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/d7641f16/app/addons/documents/tests/headerSpec.react.jsx ---------------------------------------------------------------------- diff --git a/app/addons/documents/tests/headerSpec.react.jsx b/app/addons/documents/tests/headerSpec.react.jsx index 33528c5..420aebe 100644 --- a/app/addons/documents/tests/headerSpec.react.jsx +++ b/app/addons/documents/tests/headerSpec.react.jsx @@ -83,7 +83,7 @@ define([ IndexResultsActions.newResultsList({ collection: database.allDocs, - deleteable: false + isListDeletable: false }); http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/d7641f16/app/addons/documents/tests/nightwatch/mangoIndex.js ---------------------------------------------------------------------- diff --git a/app/addons/documents/tests/nightwatch/mangoIndex.js b/app/addons/documents/tests/nightwatch/mangoIndex.js new file mode 100644 index 0000000..1c66ebe --- /dev/null +++ b/app/addons/documents/tests/nightwatch/mangoIndex.js @@ -0,0 +1,48 @@ +// 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. + +module.exports = { + + 'Creating new indexes with mango': function (client) { + /*jshint multistr: true */ + var waitTime = 10000, + newDatabaseName = client.globals.testDatabaseName, + baseUrl = client.globals.test_settings.launch_url; + + client + .populateDatabase(newDatabaseName) + .loginToGUI() + .url(baseUrl + '/#/database/' + newDatabaseName + '/_index') + .waitForElementPresent('.watermark-logo', waitTime, false) + .assert.containsText('.watermark-logo', 'Mango') + .assert.containsText('.editor-description', 'is an easy way to find documents on predefined indexes') + .execute('\ + var json = \'{\ + "index": {\ + "fields": ["ente_ente_mango"]\ + },\ + "name": "rocko-artischocko",\ + "type" : "json"\ + }\';\ + var editor = ace.edit("query-field");\ + editor.getSession().setValue(json);\ + ') + .execute('$(".save")[0].scrollIntoView();') + .click('button.btn-success.save') + + .waitForElementNotVisible('.global-notification', waitTime, false) + .url(baseUrl + '/#/database/' + newDatabaseName + '/_indexlist') + .waitForElementPresent('.prettyprint', waitTime, false) + .assert.containsText('#dashboard-lower-content', 'ente_ente_mango') + .end(); + } +}; http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/d7641f16/app/addons/documents/tests/nightwatch/mangoIndexList.js ---------------------------------------------------------------------- diff --git a/app/addons/documents/tests/nightwatch/mangoIndexList.js b/app/addons/documents/tests/nightwatch/mangoIndexList.js new file mode 100644 index 0000000..a4f8cc0 --- /dev/null +++ b/app/addons/documents/tests/nightwatch/mangoIndexList.js @@ -0,0 +1,29 @@ +// 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. + +module.exports = { + + 'Creating new indexes with mango': function (client) { + var waitTime = 10000, + newDatabaseName = client.globals.testDatabaseName, + baseUrl = client.globals.test_settings.launch_url; + + client + .populateDatabase(newDatabaseName) + .loginToGUI() + .url(baseUrl + '/#/database/' + newDatabaseName + '/_indexlist') + .waitForElementPresent('.prettyprint', waitTime, false) + .assert.containsText('.header-doc-id', '_all_docs') + .assert.containsText('#doc-list', 'ente_ente_mango_ananas') + .end(); + } +}; http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/d7641f16/app/addons/documents/tests/resourcesSpec.js ---------------------------------------------------------------------- diff --git a/app/addons/documents/tests/resourcesSpec.js b/app/addons/documents/tests/resourcesSpec.js index 11181ef..8d0e0c8 100644 --- a/app/addons/documents/tests/resourcesSpec.js +++ b/app/addons/documents/tests/resourcesSpec.js @@ -71,21 +71,110 @@ define([ }); }); + describe('MangoIndex', function () { + var doc; + + it('is deleteable', function () { + var index = { + ddoc: null, + name: '_all_docs', + type: 'json', + def: {fields: [{_id: 'asc'}]} + }; + doc = new Models.MangoIndex(index, {}); + + assert.ok(doc.isDeletable()); + }); + + it('special docs are not deleteable', function () { + var index = { + ddoc: null, + name: '_all_docs', + type: 'special', + def: {fields: [{_id: 'asc'}]} + }; + doc = new Models.MangoIndex(index, {}); + + assert.notOk(doc.isDeletable()); + }); + }); + + describe('MangoIndexCollection', function () { + var collection; + + it('is not editable', function () { + collection = new Models.MangoIndexCollection([{ + name: 'myId1', + doc: 'num1' + }, + { + name: 'myId2', + doc: 'num2' + }], { + database: {id: 'databaseId', safeID: function () { return this.id; }}, + params: {limit: 20} + }); + + assert.notOk(collection.isEditable()); + }); + }); + + + describe('IndexCollection', function () { + var collection; + + it('design docs are editable', function () { + collection = new Models.IndexCollection([{ + _id: 'myId1', + doc: 'num1' + }, + { + _id: 'myId2', + doc: 'num2' + }], { + database: {id: 'databaseId', safeID: function () { return this.id; }}, + params: {limit: 20}, + design: '_design/foobar' + }); + + assert.ok(collection.isEditable()); + }); + + it('reduced design docs are NOT editable', function () { + collection = new Models.IndexCollection([{ + _id: 'myId1', + doc: 'num1' + }, + { + _id: 'myId2', + doc: 'num2' + }], { + database: {id: 'databaseId', safeID: function () { return this.id; }}, + params: {limit: 20, reduce: true}, + design: '_design/foobar' + }); + + assert.notOk(collection.isEditable()); + }); + }); + describe('AllDocs', function () { var collection; - beforeEach(function () { + + it('all-docs-list documents are always editable', function () { collection = new Models.AllDocs([{ - _id:'myId1', + _id: 'myId1', doc: 'num1' }, { - _id:'myId2', + _id: 'myId2', doc: 'num2' }], { database: {id: 'databaseId', safeID: function () { return this.id; }}, params: {limit: 20} }); + assert.ok(collection.isEditable()); }); }); http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/d7641f16/app/addons/documents/tests/viewIndex.componentsSpec.react.jsx ---------------------------------------------------------------------- diff --git a/app/addons/documents/tests/viewIndex.componentsSpec.react.jsx b/app/addons/documents/tests/viewIndex.componentsSpec.react.jsx index 659cc8e..68c4033 100644 --- a/app/addons/documents/tests/viewIndex.componentsSpec.react.jsx +++ b/app/addons/documents/tests/viewIndex.componentsSpec.react.jsx @@ -23,8 +23,20 @@ define([ var assert = utils.assert; var TestUtils = React.addons.TestUtils; - var resetStore = function (designDoc) { - var designDocs = new Documents.AllDocs([designDoc], { + var resetStore = function (designDocs) { + designDocs = designDocs.map(function (doc) { + return Documents.Doc.prototype.parse(doc); + }); + + designDocs.map(function (ddoc) { + return new Documents.Doc(ddoc, { + database: { + safeID: function () { return 'id'; } + } + }); + }); + + var ddocs = new Documents.AllDocs(designDocs, { params: { limit: 10 }, database: { safeID: function () { return 'id';} @@ -35,8 +47,8 @@ define([ database: {id: 'rockos-db'}, newView: false, viewName: 'test-view', - designDocs: designDocs, - designDocId: designDoc._id + designDocs: ddocs, + designDocId: designDocs[0]._id }); }; @@ -72,7 +84,7 @@ define([ } }; - resetStore(designDoc); + resetStore([designDoc]); reduceEl = TestUtils.renderIntoDocument(<Views.ReduceEditor/>, container); assert.ok(_.isNull(reduceEl.getReduceValue())); @@ -91,7 +103,7 @@ define([ } }; - resetStore(designDoc); + resetStore([designDoc]); reduceEl = TestUtils.renderIntoDocument(<Views.ReduceEditor/>, container); assert.equal(reduceEl.getReduceValue(), '_sum'); @@ -107,6 +119,61 @@ define([ container = document.createElement('div'); $('body').append('<div id="map-function"></div>'); $('body').append('<div id="editor"></div>'); + var designDoc = { + "id": "_design/test-doc", + "key": "_design/test-doc", + "value": { + "rev": "20-9e4bc8b76fd7d752d620bbe6e0ea9a80" + }, + "doc": { + "_id": "_design/test-doc", + "_rev": "20-9e4bc8b76fd7d752d620bbe6e0ea9a80", + "views": { + "test-view": { + "map": "function(doc) {\n emit(doc._id, 2);\n}" + }, + "new-view": { + "map": "function(doc) {\n if (doc.class === \"mammal\" && doc.diet === \"herbivore\")\n emit(doc._id, 1);\n}", + "reduce": "_sum" + } + }, + "language": "javascript", + "indexes": { + "newSearch": { + "analyzer": "standard", + "index": "function(doc){\n index(\"default\", doc._id);\n}" + } + } + } + }; + var mangodoc = { + "id": "_design/123mango", + "key": "_design/123mango", + "value": { + "rev": "20-9e4bc8b76fd7d752d620bbe6e0ea9a80" + }, + "doc": { + "_id": "_design/123mango", + "_rev": "20-9e4bc8b76fd7d752d620bbe6e0ea9a80", + "views": { + "test-view": { + "map": "function(doc) {\n emit(doc._id, 2);\n}" + }, + "new-view": { + "map": "function(doc) {\n if (doc.class === \"mammal\" && doc.diet === \"herbivore\")\n emit(doc._id, 1);\n}", + "reduce": "_sum" + } + }, + "language": "query", + "indexes": { + "newSearch": { + "analyzer": "standard", + "index": "function(doc){\n index(\"default\", doc._id);\n}" + } + } + } + }; + resetStore([designDoc, mangodoc]); selectorEl = TestUtils.renderIntoDocument(<Views.DesignDocSelector/>, container); }); @@ -151,6 +218,14 @@ define([ assert.ok(spy.calledWith('_design/new-doc-entered', true)); }); + it('does not filter usual design docs', function () { + assert.ok(/_design\/test-doc/.test($(selectorEl.getDOMNode()).text())); + }); + + it('filters mango docs', function () { + selectorEl = TestUtils.renderIntoDocument(<Views.DesignDocSelector/>, container); + assert.notOk(/_design\/123mango/.test($(selectorEl.getDOMNode()).text())); + }); }); describe('Editor', function () { http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/d7641f16/app/addons/documents/views-mango.js ---------------------------------------------------------------------- diff --git a/app/addons/documents/views-mango.js b/app/addons/documents/views-mango.js new file mode 100644 index 0000000..7b45984 --- /dev/null +++ b/app/addons/documents/views-mango.js @@ -0,0 +1,66 @@ +// 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. + +define([ + 'api', + 'addons/documents/mango/mango.components.react', + 'addons/documents/mango/mango.actions', + 'addons/documents/index-results/index-results.components.react' +], + +function (FauxtonAPI, Mango, MangoActions, ViewResultList) { + + var Views = {}; + + + Views.HelpScreen = FauxtonAPI.View.extend({ + + afterRender: function () { + Mango.renderHelpScreen(this.el); + }, + + cleanup: function () { + Mango.removeHelpScreen(this.el); + } + }); + + Views.MangoIndexListReact = FauxtonAPI.View.extend({ + + afterRender: function () { + ViewResultList.renderViewResultList(this.el); + }, + + cleanup: function () { + ViewResultList.removeViewResultList(this.el); + } + }); + + Views.MangoIndexEditorReact = FauxtonAPI.View.extend({ + initialize: function (options) { + this.database = options.database; + }, + + afterRender: function () { + MangoActions.setDatabase({ + database: this.database + }); + + Mango.renderMangoIndexEditor(this.el); + }, + + cleanup: function () { + Mango.removeMangoIndexEditor(this.el); + } + }); + + return Views; +}); http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/d7641f16/app/initialize.js.underscore ---------------------------------------------------------------------- diff --git a/app/initialize.js.underscore b/app/initialize.js.underscore index c0af5fb..0f6c36f 100644 --- a/app/initialize.js.underscore +++ b/app/initialize.js.underscore @@ -26,7 +26,8 @@ function () { version: "<%= version %>", // Host is used as prefix for urls host: "<%= host %>", - zeroClipboardPath: "<%= zeroClipboardPath %>" + zeroClipboardPath: "<%= zeroClipboardPath %>", + i18n: <%= i18n %> }; return app; http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/d7641f16/i18n.json.default ---------------------------------------------------------------------- diff --git a/i18n.json.default b/i18n.json.default new file mode 100644 index 0000000..aa89b17 --- /dev/null +++ b/i18n.json.default @@ -0,0 +1,8 @@ +{ + "en_US": { + "mango-descripton": "Mango is an easy way to find documents on predefined indexes.", + "all-mango-indexes": "All Mango Indexes", + "new-mango-index": "New Mango Index", + "mango-help-title": "Mango" + } +} http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/d7641f16/package.json ---------------------------------------------------------------------- diff --git a/package.json b/package.json index e9b13ea..cd8efe1 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ "nano": "~5.12.0", "nightwatch": "~0.5.33", "react-tools": "^0.12.0", + "request": "^2.54.0", "send": "~0.1.1", "underscore": "~1.4.2", "url": "~0.7.9", http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/d7641f16/tasks/fauxton.js ---------------------------------------------------------------------- diff --git a/tasks/fauxton.js b/tasks/fauxton.js index 743c8ef..ee73399 100644 --- a/tasks/fauxton.js +++ b/tasks/fauxton.js @@ -92,12 +92,11 @@ module.exports = function (grunt) { grunt.registerMultiTask('gen_initialize', 'Generate the app.js file', function () { var _ = grunt.util._, - settings = this.data, - template = "app/initialize.js.underscore", - dest = "app/initialize.js", - tmpl = _.template(grunt.file.read(template)), - app = {}; - + settings = this.data, + template = "app/initialize.js.underscore", + dest = "app/initialize.js", + tmpl = _.template(grunt.file.read(template)), + app = {}; _.defaults(app, settings.app, { root: '/', http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/d7641f16/tasks/helper.js ---------------------------------------------------------------------- diff --git a/tasks/helper.js b/tasks/helper.js index e0b82bb..abfc01f 100644 --- a/tasks/helper.js +++ b/tasks/helper.js @@ -28,6 +28,17 @@ exports.init = function (grunt) { } }, + readI18nFile: function () { + if (fs.existsSync('i18n.json')) { + return grunt.file.readJSON('i18n.json'); + } + if (fs.existsSync('i18n.json.default')) { + return grunt.file.readJSON('i18n.json.default'); + } + + throw new Error('i18n file missing'); + }, + processAddons: function (callback) { this.readSettingsFile().deps.forEach(callback); }, http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/d7641f16/test/nightwatch_tests/custom-commands/populateDatabase.js ---------------------------------------------------------------------- diff --git a/test/nightwatch_tests/custom-commands/populateDatabase.js b/test/nightwatch_tests/custom-commands/populateDatabase.js index f17df8f..b5cd161 100644 --- a/test/nightwatch_tests/custom-commands/populateDatabase.js +++ b/test/nightwatch_tests/custom-commands/populateDatabase.js @@ -13,7 +13,8 @@ var util = require('util'), events = require('events'), helpers = require('../helpers/helpers.js'), - async = require('async'); + async = require('async'), + request = require('request'); function PopulateDatabase () { events.EventEmitter.call(this); @@ -56,7 +57,9 @@ PopulateDatabase.prototype.command = function (databaseName, count) { createKeyView(null, function () { createBrokenView(null, function () { - that.emit('complete'); + createMangoIndex(null, function () { + that.emit('complete'); + }); }); }); }); @@ -97,6 +100,29 @@ PopulateDatabase.prototype.command = function (databaseName, count) { cb(); }); } + + function createMangoIndex (err, cb) { + request({ + uri: helpers.test_settings.db_url + '/' + databaseName + '/_index', + method: 'POST', + json: true, + body: { + index: { + fields: ['ente_ente_mango_ananas'] + }, + name: 'rocko-artischockbert', + type: 'json' + } + }, function (err, res, body) { + if (err) { + console.log('Error in nano populateDatabase Function: ' + + err.message); + } + + cb && cb(); + }); + } + return this; };
