Part 2/2 for Mango: Adds search functionality, makes the feature visible and enables deletion of mango-created-indexes.
closes COUCHDB-2627 PR: #362 PR-URL: https://github.com/apache/couchdb-fauxton/pull/362 Reviewed-By: Michelle Phung <[email protected]> Reviewed-By: garren smith <[email protected]> Reviewed-By: Benjamin Keen <[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/a9e829c4 Tree: http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/tree/a9e829c4 Diff: http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/diff/a9e829c4 Branch: refs/heads/master Commit: a9e829c441a58aa8eafef75f8d3819ea7bb19a0a Parents: 30f6577 Author: Robert Kowalski <[email protected]> Authored: Wed Apr 1 13:11:15 2015 +0200 Committer: Robert Kowalski <[email protected]> Committed: Tue May 19 19:14:39 2015 +0200 ---------------------------------------------------------------------- .gitignore | 1 + .../components/react-components.react.jsx | 21 +- app/addons/components/tests/docSpec.react.jsx | 16 ++ .../documents/assets/less/view-editor.less | 6 + app/addons/documents/base.js | 24 ++ .../documents/index-editor/components.react.jsx | 6 +- .../tests/viewIndex.componentsSpec.react.jsx | 3 +- app/addons/documents/index-results/actions.js | 45 ++++ .../index-results.components.react.jsx | 12 +- app/addons/documents/index-results/stores.js | 132 +++++++++-- .../tests/index-results.actionsSpec.js | 1 + .../index-results.componentsSpec.react.jsx | 11 +- .../tests/index-results.storesSpec.js | 200 +++++++++------- app/addons/documents/mango/mango.actions.js | 79 ++++++- app/addons/documents/mango/mango.actiontypes.js | 5 + .../documents/mango/mango.components.react.jsx | 236 +++++++++++++++---- app/addons/documents/mango/mango.helper.js | 44 ++++ app/addons/documents/mango/mango.stores.js | 123 +++++++++- .../mango/tests/mango.componentsSpec.react.jsx | 173 +++++++++++--- .../documents/mango/tests/mango.storesSpec.js | 101 ++++++++ app/addons/documents/resources.js | 209 +++++++++++++++- app/addons/documents/routes-documents.js | 7 +- app/addons/documents/routes-index-editor.js | 6 +- app/addons/documents/routes-mango.js | 141 ++++++----- app/addons/documents/routes.js | 3 +- app/addons/documents/shared-resources.js | 28 +-- app/addons/documents/shared-views.js | 49 ++-- app/addons/documents/templates/sidebar.html | 39 ++- .../documents/tests/nightwatch/mangoIndex.js | 49 +++- .../tests/nightwatch/mangoIndexList.js | 31 --- .../documents/tests/nightwatch/mangoQuery.js | 44 ++++ app/addons/documents/tests/resourcesSpec.js | 58 ++++- app/addons/documents/views-mango.js | 66 ------ app/addons/fauxton/components.js | 2 + app/constants.js | 1 + i18n.json.default | 7 +- .../custom-commands/populateDatabase.js | 8 +- 37 files changed, 1531 insertions(+), 456 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/a9e829c4/.gitignore ---------------------------------------------------------------------- diff --git a/.gitignore b/.gitignore index b56fe9d..b3517fa 100644 --- a/.gitignore +++ b/.gitignore @@ -18,6 +18,7 @@ app/addons/* !app/addons/styletests !app/addons/cors settings.json* +i18n.json !settings.json.default !assets/js/plugins/zeroclipboard/ZeroClipboard.swf test/test.config.js http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/a9e829c4/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 6a4f680..bf8c6af 100644 --- a/app/addons/components/react-components.react.jsx +++ b/app/addons/components/react-components.react.jsx @@ -77,6 +77,7 @@ function (app, FauxtonAPI, React, Components, ace, beautifyHelper) { autoScrollEditorIntoView: true, setHeightWithJS: true, isFullPageEditor: false, + disableUnload: false, change: function () {} }; }, @@ -108,14 +109,18 @@ function (app, FauxtonAPI, React, Components, ace, beautifyHelper) { this.editor.getSession().setMode("ace/mode/" + props.mode); this.editor.setTheme("ace/theme/" + props.theme); this.editor.setFontSize(props.fontSize); - + this.editor.getSession().setUseSoftTabs(true); }, setupEvents: function () { + this.editor.on('blur', _.bind(this.saveCodeChange, this)); + + if (this.props.disableUnload) { + return; + } + $(window).on('beforeunload.editor_' + this.props.id, _.bind(this.quitWarningMsg)); FauxtonAPI.beforeUnload('editor_' + this.props.id, _.bind(this.quitWarningMsg, this)); - - this.editor.on('blur', _.bind(this.saveCodeChange, this)); }, saveCodeChange: function () { @@ -129,6 +134,10 @@ function (app, FauxtonAPI, React, Components, ace, beautifyHelper) { }, removeEvents: function () { + if (this.props.disableUnload) { + return; + } + $(window).off('beforeunload.editor_' + this.props.id); FauxtonAPI.removeBeforeUnload('editor_' + this.props.id); }, @@ -201,12 +210,12 @@ function (app, FauxtonAPI, React, Components, ace, beautifyHelper) { getTitleFragment: function () { if (!this.props.docs) { - return <strong>{this.props.title}</strong>; + return (<strong>{this.props.title}</strong>); } return ( <label> - <strong>{this.props.title}</strong> + <strong>{this.props.title + ' '}</strong> <a className="help-link" data-bypass="true" @@ -391,7 +400,7 @@ function (app, FauxtonAPI, React, Components, ace, beautifyHelper) { {this.props.keylabel} </span> <span className="header-doc-id"> - "{this.props.docIdentifier}" + {this.props.header ? '"' + this.props.header + '"' : null} </span> {this.getUrlFragment()} <div className="doc-item-extension-icons pull-right">{this.getExtensionIcons()}</div> http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/a9e829c4/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 3cf82a2..c7d9d9d 100644 --- a/app/addons/components/tests/docSpec.react.jsx +++ b/app/addons/components/tests/docSpec.react.jsx @@ -114,6 +114,22 @@ define([ ); assert.equal(0, $(el.getDOMNode()).find('.doc-data').length); }); + + it('allows empty headers', function () { + el = TestUtils.renderIntoDocument( + <ReactComponents.Document header={null} isDeletable={true} checked={true} docIdentifier="foo" docContent='' />, + container + ); + assert.equal('', $(el.getDOMNode()).find('.header-doc-id').text()); + }); + + it('allows supports headers with "', function () { + el = TestUtils.renderIntoDocument( + <ReactComponents.Document header="foo" isDeletable={true} checked={true} docIdentifier="foo" docContent='' />, + container + ); + assert.equal('"foo"', $(el.getDOMNode()).find('.header-doc-id').text()); + }); }); }); http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/a9e829c4/app/addons/documents/assets/less/view-editor.less ---------------------------------------------------------------------- diff --git a/app/addons/documents/assets/less/view-editor.less b/app/addons/documents/assets/less/view-editor.less index fa2d358..05f40ba 100644 --- a/app/addons/documents/assets/less/view-editor.less +++ b/app/addons/documents/assets/less/view-editor.less @@ -45,6 +45,12 @@ margin-top: 25px; height: 46px; } + pre.prettyprint-left { + padding: 5px; + } + form { + padding-bottom: 50px; + } } body .view-query-save .control-group { http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/a9e829c4/app/addons/documents/base.js ---------------------------------------------------------------------- diff --git a/app/addons/documents/base.js b/app/addons/documents/base.js index d6ac60d..ba4a892 100644 --- a/app/addons/documents/base.js +++ b/app/addons/documents/base.js @@ -135,6 +135,30 @@ function (app, FauxtonAPI, Documents) { } return 'database/' + db + '/_index' + query; + }, + + 'query-server': function (db, query) { + if (!query) { + query = ''; + } + + return app.host + '/' + db + '/_find' + query; + }, + + 'query-apiurl': function (db, query) { + if (!query) { + query = ''; + } + + return window.location.origin + '/' + db + '/_find' + query; + }, + + 'query-app': function (db, query) { + if (!query) { + query = ''; + } + + return 'database/' + db + '/_find' + query; } }); http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/a9e829c4/app/addons/documents/index-editor/components.react.jsx ---------------------------------------------------------------------- diff --git a/app/addons/documents/index-editor/components.react.jsx b/app/addons/documents/index-editor/components.react.jsx index f943c0c..ed99757 100644 --- a/app/addons/documents/index-editor/components.react.jsx +++ b/app/addons/documents/index-editor/components.react.jsx @@ -386,6 +386,8 @@ function (app, FauxtonAPI, React, Stores, Actions, Components, ReactComponents) ); } + var url = FauxtonAPI.urls('allDocs', this.state.database.id, ''); + return ( <div className="define-view"> <PaddedBorderedBox> @@ -393,7 +395,9 @@ function (app, FauxtonAPI, React, Stores, Actions, Components, ReactComponents) </PaddedBorderedBox> <PaddedBorderedBox> <strong>Database</strong> - <div className="db-title">{this.state.database.id}</div> + <div className="db-title"> + <a href={url}>this.state.database.id</a> + </div> </PaddedBorderedBox> <form className="form-horizontal view-query-save" onSubmit={this.saveView}> <DesignDocSelector /> http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/a9e829c4/app/addons/documents/index-editor/tests/viewIndex.componentsSpec.react.jsx ---------------------------------------------------------------------- diff --git a/app/addons/documents/index-editor/tests/viewIndex.componentsSpec.react.jsx b/app/addons/documents/index-editor/tests/viewIndex.componentsSpec.react.jsx index 9cac62d..5fea728 100644 --- a/app/addons/documents/index-editor/tests/viewIndex.componentsSpec.react.jsx +++ b/app/addons/documents/index-editor/tests/viewIndex.componentsSpec.react.jsx @@ -110,8 +110,7 @@ define([ beforeEach(function () { 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", http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/a9e829c4/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 c3c10d6..d8bb584 100644 --- a/app/addons/documents/index-results/actions.js +++ b/app/addons/documents/index-results/actions.js @@ -71,7 +71,52 @@ function (app, FauxtonAPI, ActionTypes, Stores, HeaderStores, HeaderActions, Doc }); }, + newMangoResultsList: function (options) { + FauxtonAPI.dispatch({ + type: ActionTypes.INDEX_RESULTS_NEW_RESULTS, + options: options + }); + }, + + runMangoFindQuery: function (options) { + var query = JSON.parse(options.queryCode), + collection = indexResultsStore.getCollection(); + + this.clearResults(); + + return collection + .setQuery(query) + .fetch() + .then(function () { + this.resultsListReset(); + this.newMangoResultsList({ + collection: collection, + isListDeletable: indexResultsStore.isListDeletable(), + query: options.queryCode, + textEmptyIndex: 'No Results Found!', + bulkCollection: Documents.BulkDeleteDocCollection + }); + }.bind(this), function (res) { + FauxtonAPI.addNotification({ + msg: res.reason, + clear: true, + type: 'error' + }); + + this.resultsListReset(); + }.bind(this)); + }, + reloadResultsList: function () { + if (indexResultsStore.getTypeOfIndex() === 'mango') { + return this.newResultsList({ + collection: indexResultsStore.getCollection(), + isListDeletable: true, + bulkCollection: Documents.MangoBulkDeleteDocCollection, + typeOfIndex: 'mango' + }); + } + return this.newResultsList({ collection: indexResultsStore.getCollection(), isListDeletable: indexResultsStore.isListDeletable() http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/a9e829c4/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 f930f61..2b5fe39 100644 --- a/app/addons/documents/index-results/index-results.components.react.jsx +++ b/app/addons/documents/index-results/index-results.components.react.jsx @@ -19,7 +19,7 @@ define([ 'addons/components/react-components.react', 'addons/documents/resources', - "plugins/prettify" + 'plugins/prettify' ], function (app, FauxtonAPI, React, Stores, Actions, Components, Documents) { @@ -54,20 +54,22 @@ function (app, FauxtonAPI, React, Stores, Actions, Components, Documents) { }, getDocumentList: function () { - return _.map(this.props.results, function (doc) { + var noop = function () {}; + return _.map(this.props.results, function (doc, i) { return ( <Components.Document - key={doc.id} + key={doc.id + i} doc={doc} - onDoubleClick={this.onDoubleClick} + onDoubleClick={this.props.isEditable ? this.onDoubleClick : noop} keylabel={doc.keylabel} docContent={doc.content} checked={this.props.isSelected(doc.id)} + header={doc.header} docChecked={this.props.docChecked} isDeletable={doc.isDeletable} docIdentifier={doc.id} > - {this.getUrlFragment('#' + doc.url)} + {doc.url ? this.getUrlFragment('#' + doc.url) : doc.url} </Components.Document> ); }, this); http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/a9e829c4/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 3d6cb1f..37989f1 100644 --- a/app/addons/documents/index-results/stores.js +++ b/app/addons/documents/index-results/stores.js @@ -14,10 +14,11 @@ define([ 'api', 'addons/documents/index-results/actiontypes', 'addons/documents/header/header.actiontypes', - "addons/documents/resources" + 'addons/documents/resources', + 'addons/documents/mango/mango.helper' ], -function (FauxtonAPI, ActionTypes, HeaderActionTypes, Documents) { +function (FauxtonAPI, ActionTypes, HeaderActionTypes, Documents, MangoHelper) { var Stores = {}; /*TODO: @@ -33,6 +34,9 @@ function (FauxtonAPI, ActionTypes, HeaderActionTypes, Documents) { this.clearCollapsedDocs(); this._isLoading = false; this._textEmptyIndex = 'No Index Created Yet!'; + this._typeOfIndex = 'view'; + this._lastQuery = null; + this._bulkDeleteDocCollection = null; }, clearSelectedItems: function () { @@ -49,9 +53,27 @@ function (FauxtonAPI, ActionTypes, HeaderActionTypes, Documents) { this.clearSelectedItems(); this.clearCollapsedDocs(); + this._bulkDeleteDocCollection = options.bulkCollection; + if (options.textEmptyIndex) { this._textEmptyIndex = options.textEmptyIndex; } + + if (options.typeOfIndex) { + this._typeOfIndex = options.typeOfIndex; + } + + if (options.query) { + this._lastQuery = options.query; + } + }, + + getTypeOfIndex: function () { + return this._typeOfIndex; + }, + + getLastQuery: function () { + return this._lastQuery; }, isEditable: function (doc) { @@ -59,6 +81,14 @@ function (FauxtonAPI, ActionTypes, HeaderActionTypes, Documents) { return false; } + if (doc && this.isGhostDoc(doc)) { + return false; + } + + if (doc && !doc.get('_id')) { + return false; + } + if (!this._collection.isEditable) { return false; } @@ -66,7 +96,17 @@ function (FauxtonAPI, ActionTypes, HeaderActionTypes, Documents) { return this._collection.isEditable(); }, + isGhostDoc: function (doc) { + // ghost docs are empty results where all properties were + // filtered away by mango + return !doc || !doc.attributes || !Object.keys(doc.attributes).length; + }, + isDeletable: function (doc) { + if (this.isGhostDoc(doc)) { + return false; + } + return doc.isDeletable(); }, @@ -80,7 +120,8 @@ function (FauxtonAPI, ActionTypes, HeaderActionTypes, Documents) { getDocContent: function (originalDoc) { var doc = originalDoc.toJSON(); - return (this.isCollapsed(doc._id)) ? '' : JSON.stringify(doc, null, ' '); + + return this.isCollapsed(doc._id) ? '' : JSON.stringify(doc, null, ' '); }, getDocId: function (doc) { @@ -96,17 +137,68 @@ function (FauxtonAPI, ActionTypes, HeaderActionTypes, Documents) { return ''; }, - getResults: function () { - return this._collection.map(function (doc) { + getMangoDocContent: function (originalDoc) { + var doc = originalDoc.toJSON(); + + delete doc.ddoc; + delete doc.name; + + return this.isCollapsed(originalDoc.id) ? '' : JSON.stringify(doc, null, ' '); + }, + + getMangoDoc: function (doc, index) { + var selector, + header; + + if (doc.get('def') && doc.get('def').fields) { + + header = MangoHelper.getIndexName(doc); + return { - content: this.getDocContent(doc), - id: this.getDocId(doc), - keylabel: doc.isFromView() ? 'key' : 'id', + content: this.getMangoDocContent(doc), + header: header, + id: doc.getId(), + keylabel: '', url: doc.isFromView() ? doc.url('app') : doc.url('web-index'), isDeletable: this.isDeletable(doc), isEditable: this.isEditable(doc) }; - }, this); + } + + // we filtered away our content with the fields param + return { + content: ' ', + header: header, + id: this.getDocId(doc) + index, + keylabel: '', + url: this.isEditable(doc) ? doc.url('app') : null, + isDeletable: this.isDeletable(doc), + isEditable: this.isEditable(doc) + }; + + }, + + getResults: function () { + function filterOutGeneratedMangoDocs (doc) { + return doc.get('language') !== 'query'; + } + + return this._collection + .filter(filterOutGeneratedMangoDocs) + .map(function (doc, i) { + if (doc.get('def') || this.isGhostDoc(doc)) { + return this.getMangoDoc(doc, i); + } + return { + content: this.getDocContent(doc), + id: this.getDocId(doc), + header: this.getDocId(doc), + keylabel: doc.isFromView() ? 'key' : 'id', + url: this.getDocId(doc) ? doc.url('app') : null, + isDeletable: this.isDeletable(doc), + isEditable: this.isEditable(doc) + }; + }, this); }, hasResults: function () { @@ -118,11 +210,11 @@ function (FauxtonAPI, ActionTypes, HeaderActionTypes, Documents) { return this._isLoading; }, - isDeleteable: function () { - return this._deleteable; - }, - selectDoc: function (id) { + if (!id || id === '_all_docs') { + return; + } + if (!this._selectedItems[id]) { this._selectedItems[id] = true; } else { @@ -168,6 +260,10 @@ function (FauxtonAPI, ActionTypes, HeaderActionTypes, Documents) { return this._textEmptyIndex; }, + setbulkDeleteDocCollection: function (bulkDeleteDocCollection) { + this._bulkDeleteDocCollection = bulkDeleteDocCollection; + }, + createBulkDeleteFromSelected: function () { var items = _.map(_.keys(this._selectedItems), function (id) { var doc = this._collection.get(id); @@ -179,7 +275,7 @@ function (FauxtonAPI, ActionTypes, HeaderActionTypes, Documents) { }; }, this); - var bulkDelete = new Documents.BulkDeleteDocCollection(items, { + var bulkDelete = new this._bulkDeleteDocCollection(items, { databaseId: this.getDatabase().safeID() }); @@ -187,7 +283,13 @@ function (FauxtonAPI, ActionTypes, HeaderActionTypes, Documents) { }, canSelectAll: function () { - return this._collection.length > this.getSelectedItemsLength(); + var length = this._collection.length; + + if (this._collection.get && this._collection.get('_all_docs')) { + length = length - 1; + } + + return length > this.getSelectedItemsLength(); }, canDeselectAll: function () { http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/a9e829c4/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 1bbba77..fb67414 100644 --- a/app/addons/documents/index-results/tests/index-results.actionsSpec.js +++ b/app/addons/documents/index-results/tests/index-results.actionsSpec.js @@ -99,6 +99,7 @@ define([ safeID: function () { return '1';} } }); + store._bulkDeleteDocCollection = Documents.BulkDeleteDocCollection; store._selectedItems = { 'testId1': true, http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/a9e829c4/app/addons/documents/index-results/tests/index-results.componentsSpec.react.jsx ---------------------------------------------------------------------- diff --git a/app/addons/documents/index-results/tests/index-results.componentsSpec.react.jsx b/app/addons/documents/index-results/tests/index-results.componentsSpec.react.jsx index 22d9366..85dfecd 100644 --- a/app/addons/documents/index-results/tests/index-results.componentsSpec.react.jsx +++ b/app/addons/documents/index-results/tests/index-results.componentsSpec.react.jsx @@ -13,9 +13,11 @@ define([ 'api', 'addons/documents/index-results/index-results.components.react', 'addons/documents/index-results/actions', + 'addons/documents/resources', + 'addons/databases/resources', 'testUtils', "react" -], function (FauxtonAPI, Views, IndexResultsActions, utils, React) { +], function (FauxtonAPI, Views, IndexResultsActions, Resources, Databases, utils, React) { FauxtonAPI.router = new FauxtonAPI.Router([]); var assert = utils.assert; @@ -26,7 +28,6 @@ define([ beforeEach(function () { container = document.createElement('div'); - }); afterEach(function () { @@ -59,7 +60,13 @@ define([ }); describe('loading', function () { + beforeEach(function () { + container = document.createElement('div'); + }); + afterEach(function () { + React.unmountComponentAtNode(container); + }); it('should show loading component', function () { var resultsEl = TestUtils.renderIntoDocument(<Views.ResultsScreen isLoading={true} http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/a9e829c4/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 7b5c0b1..e72d25c 100644 --- a/app/addons/documents/index-results/tests/index-results.storesSpec.js +++ b/app/addons/documents/index-results/tests/index-results.storesSpec.js @@ -20,12 +20,22 @@ define([ var assert = testUtils.assert; var dispatchToken; var store; + var opts; describe('Index Results Store', function () { - beforeEach(function () { store = new Stores.IndexResultsStore(); dispatchToken = FauxtonAPI.dispatcher.register(store.dispatch); + opts = { + params: {}, + database: { + safeID: function () { return '1';} + } + }; + }); + + afterEach(function () { + FauxtonAPI.dispatcher.unregister(dispatchToken); }); describe('#hasResults', function () { @@ -47,12 +57,7 @@ define([ describe('#getResults', function () { it('has correct doc format', function () { - store._collection = new Documents.AllDocs([{_id: 'testId'}], { - params: {}, - database: { - safeID: function () { return '1';} - } - }); + store._collection = new Documents.AllDocs([{_id: 'testId'}], opts); var doc = store.getResults()[0]; assert.equal(doc.id, 'testId'); @@ -60,33 +65,19 @@ define([ }); }); - - afterEach(function () { - FauxtonAPI.dispatcher.unregister(dispatchToken); - }); }); describe('canSelectAll', function () { it('returns true for selected docs less than collection', function () { - store._collection = new Documents.AllDocs([{_id: 'testId1'}, {_id: 'testId2'}], { - params: {}, - database: { - safeID: function () { return '1';} - } - }); + store._collection = new Documents.AllDocs([{_id: 'testId1'}, {_id: 'testId2'}], opts); store._selectedItems = {'testId1': true}; assert.ok(store.canSelectAll()); }); it('returns false for selected docs same as collection', function () { - store._collection = new Documents.AllDocs([{_id: 'testId1'}, {_id: 'testId2'}], { - params: {}, - database: { - safeID: function () { return '1';} - } - }); + store._collection = new Documents.AllDocs([{_id: 'testId1'}, {_id: 'testId2'}], opts); store._selectedItems = { 'testId1': true, @@ -96,6 +87,20 @@ define([ assert.notOk(store.canSelectAll()); }); + it('returns true even with _all_docs (mango)', function () { + store._collection = new Documents.AllDocs([ + {_id: 'testId1'}, + {_id: 'testId2'}, + {_id: '_all_docs'} + ], opts); + + store._selectedItems = { + 'testId1': true, + 'testId2': true + }; + + assert.notOk(store.canSelectAll()); + }); }); describe('canDeselectAll', function () { @@ -121,12 +126,7 @@ define([ }); it('returns false for all collapsed docs', function () { - store._collection = new Documents.AllDocs([{_id: 'testId1'}, {_id: 'testId2'}], { - params: {}, - database: { - safeID: function () { return '1';} - } - }); + store._collection = new Documents.AllDocs([{_id: 'testId1'}, {_id: 'testId2'}], opts); store._collapsedDocs = { 'testId1': true, @@ -156,12 +156,7 @@ define([ describe('getDocContent', function () { it('returns full doc if not collapsed', function () { - store._collection = new Documents.AllDocs([{_id: 'testId1', 'value': 'one'}] , { - params: {}, - database: { - safeID: function () { return '1';} - } - }); + store._collection = new Documents.AllDocs([{_id: 'testId1', 'value': 'one'}], opts); var doc = store._collection.first(); var result = store.getDocContent(doc); @@ -170,12 +165,7 @@ define([ }); it('returns no doc content if collapsed', function () { - store._collection = new Documents.AllDocs([{_id: 'testId1', 'value': 'one'}] , { - params: {}, - database: { - safeID: function () { return '1';} - } - }); + store._collection = new Documents.AllDocs([{_id: 'testId1', 'value': 'one'}], opts); var doc = store._collection.first(); store._collapsedDocs = {'testId1': true}; @@ -204,12 +194,7 @@ define([ describe('#selectAllDocuments', function () { it('selects all documents', function () { - store._collection = new Documents.AllDocs([{_id: 'testId1', 'value': 'one'}] , { - params: {}, - database: { - safeID: function () { return '1';} - } - }); + store._collection = new Documents.AllDocs([{_id: 'testId1', 'value': 'one'}], opts); store.selectAllDocuments(); assert.ok(store.getSelectedItems().testId1); @@ -220,12 +205,7 @@ define([ describe('#deSelectAllDocuments', function () { it('deselects all documents', function () { - store._collection = new Documents.AllDocs([{_id: 'testId1', 'value': 'one'}] , { - params: {}, - database: { - safeID: function () { return '1';} - } - }); + store._collection = new Documents.AllDocs([{_id: 'testId1', 'value': 'one'}], opts); store.selectAllDocuments(); assert.ok(store.getSelectedItems().testId1); @@ -237,12 +217,7 @@ define([ describe('#collapseSelectedDocs', function () { it('collapses all selected docs', function () { - store._collection = new Documents.AllDocs([{_id: 'testId1'}, {_id: 'testId2'}], { - params: {}, - database: { - safeID: function () { return '1';} - } - }); + store._collection = new Documents.AllDocs([{_id: 'testId1'}, {_id: 'testId2'}], opts); store.clearCollapsedDocs(); @@ -260,12 +235,7 @@ define([ describe('#unCollapseSelectedDocs', function () { it('uncollapses all selected docs', function () { - store._collection = new Documents.AllDocs([{_id: 'testId1'}, {_id: 'testId2'}], { - params: {}, - database: { - safeID: function () { return '1';} - } - }); + store._collection = new Documents.AllDocs([{_id: 'testId1'}, {_id: 'testId2'}], opts); store.clearCollapsedDocs(); @@ -284,12 +254,9 @@ define([ describe('#createBulkDeleteFromSelected', function () { it('correctly creates BulkDeleteDocCollection', function () { - store._collection = new Documents.AllDocs([{_id: 'testId1'}, {_id: 'testId2'}], { - params: {}, - database: { - safeID: function () { return '1';} - } - }); + store._collection = new Documents.AllDocs([{_id: 'testId1'}, {_id: 'testId2'}], opts); + + store._bulkDeleteDocCollection = Documents.BulkDeleteDocCollection; store._selectedItems = { 'testId1': true, @@ -304,16 +271,62 @@ define([ }); + describe('#getMangoDoc', function () { + var store = new Stores.IndexResultsStore(); + var fakeMango = { + ddoc: '_design/e4d338e5d6f047749f5399ab998b4fa04ba0c816', + def: { + fields: [ + {'_id': 'asc'}, + {'foo': 'bar'}, + {'ente': 'gans'} + ] + }, + name: 'e4d338e5d6f047749f5399ab998b4fa04ba0c816', + type: 'json' + }; + + it('creates a special id from the header fields', function () { + var doc = new Documents.MangoIndex(fakeMango, opts); + assert.equal(store.getMangoDoc(doc).header, 'json: _id, foo, ente'); + }); + + it('supports custom header fields', function () { + FauxtonAPI.registerExtension('mango:additionalIndexes', { + createHeader: function (doc) { + return ['foobar']; + } + }); + + var doc = new Documents.MangoIndex({ + ddoc: '_design/e4d338e5d6f047749f5399ab998b4fa04ba0c816', + def: { + fields: [] + }, + name: 'e4d338e5d6f047749f5399ab998b4fa04ba0c816', + type: 'json' + }, opts); + assert.equal(store.getMangoDoc(doc).header, 'foobar'); + }); + + it('removes the name and ddoc field', function () { + var doc = new Documents.MangoIndex(fakeMango, opts); + assert.ok(doc.get('name')); + assert.ok(doc.get('ddoc')); + + var newDoc = store.getMangoDoc(doc); + assert.notOk(JSON.parse(newDoc.content).name); + assert.notOk(JSON.parse(newDoc.content).ddoc); + assert.ok(JSON.parse(newDoc.content).type); + }); + }); + describe('#getDocId', function () { it('returns id if it exists', function () { var doc = new Documents.Doc({ _id: 'doc-id' - }, { - database: { - safeID: function () { return '1';} - } - }); + }, opts); assert.equal(store.getDocId(doc), 'doc-id'); @@ -322,11 +335,7 @@ define([ it('returns key if it exists', function () { var doc = new Documents.Doc({ key: 'doc-key' - }, { - database: { - safeID: function () { return '1';} - } - }); + }, opts); assert.equal(store.getDocId(doc), 'doc-key'); @@ -336,11 +345,7 @@ define([ var doc = new Documents.Doc({ key: null, value: 'the-value' - }, { - database: { - safeID: function () { return '1';} - } - }); + }, opts); assert.equal(store.getDocId(doc), ''); @@ -348,6 +353,7 @@ define([ }); describe('isEditable', function () { + store = new Stores.IndexResultsStore(); it('returns false for no collection', function () { store._collection = null; @@ -360,9 +366,27 @@ define([ }); it('delegates to collection', function () { + store._collection = { + attributes: { + fields: ["foo"] + } + }; + store._collection.isEditable = function () { return {'stub': true}; }; + assert.deepEqual(store.isEditable(), {'stub': true}); store._collection = {}; - store._collection.isEditable = function () { return 'stub'; }; - assert.equal(store.isEditable(), 'stub'); + }); + + it('retuns false for ghost-docs that are filtered away', function () { + store._collection = {}; + assert.equal(store.isEditable({}), false); + }); + }); + + describe('isDeletable', function () { + store = new Stores.IndexResultsStore(); + + it('retuns false for ghost-docs that are filtered away', function () { + assert.equal(store.isDeletable({}), false); }); }); }); http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/a9e829c4/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 index d3df146..0c7c3ed 100644 --- a/app/addons/documents/mango/mango.actions.js +++ b/app/addons/documents/mango/mango.actions.js @@ -16,10 +16,10 @@ define([ 'addons/documents/resources', 'addons/documents/mango/mango.actiontypes', 'addons/documents/mango/mango.stores', - 'addons/documents/index-results/actions' - + 'addons/documents/pagination/stores', + 'addons/documents/index-results/actions', ], -function (app, FauxtonAPI, Documents, ActionTypes, Stores, IndexResultsActions) { +function (app, FauxtonAPI, Documents, ActionTypes, Stores, PaginationStores, IndexResultActions) { var store = Stores.mangoStore; return { @@ -31,8 +31,23 @@ function (app, FauxtonAPI, Documents, ActionTypes, Stores, IndexResultsActions) }); }, + newQueryFindCode: function (options) { + FauxtonAPI.dispatch({ + type: ActionTypes.MANGO_NEW_QUERY_FIND_CODE, + options: options + }); + }, + + newQueryCreateIndexCode: function (options) { + FauxtonAPI.dispatch({ + type: ActionTypes.MANGO_NEW_QUERY_CREATE_INDEX_CODE, + options: options + }); + }, + saveQuery: function (options) { - var mangoIndex = new Documents.MangoIndex(JSON.parse(options.queryCode), {database: options.database}); + var queryCode = JSON.parse(options.queryCode), + mangoIndex = new Documents.MangoIndex(queryCode, {database: options.database}); FauxtonAPI.addNotification({ msg: 'Saving Index for Query...', @@ -41,18 +56,60 @@ function (app, FauxtonAPI, Documents, ActionTypes, Stores, IndexResultsActions) }); mangoIndex.save().then(function (res) { - var msg = res.result === 'created' ? 'Index created' : 'Index already exits', - url = FauxtonAPI.urls('mango', 'index-app', options.database.safeID()); + var url = FauxtonAPI.urls('mango', 'query-app', options.database.safeID()); - FauxtonAPI.addNotification({ - msg: msg, - type: 'success', - clear: true + FauxtonAPI.dispatch({ + type: ActionTypes.MANGO_NEW_QUERY_FIND_CODE_FROM_FIELDS, + options: { + fields: queryCode.index.fields + } }); - IndexResultsActions.reloadResultsList(); + var mangoIndexCollection = new Documents.MangoIndexCollection(null, { + database: options.database, + params: null, + paging: { + pageSize: PaginationStores.indexPaginationStore.getPerPage() + } + }); + + this.getIndexList({indexList: mangoIndexCollection}).then(function () { + + IndexResultActions.reloadResultsList(); + + FauxtonAPI.addNotification({ + msg: 'Index is ready for querying. <a href="' + url + '">Run a Query.</a>', + type: 'success', + clear: true, + escape: false + }); + }.bind(this)); + }.bind(this)); + }, + + mangoResetIndexList: function (options) { + FauxtonAPI.dispatch({ + type: ActionTypes.MANGO_RESET, + options: options + }); + }, + getIndexList: function (options) { + FauxtonAPI.dispatch({ + type: ActionTypes.MANGO_NEW_AVAILABLE_INDEXES, + options: options + }); + + return options.indexList.fetch({reset: true}).then(function () { + this.mangoResetIndexList({isLoading: false}); + }.bind(this), function () { + FauxtonAPI.addNotification({ + msg: 'Bad request!', + type: "error", + clear: true + }); + }); } }; }); http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/a9e829c4/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 index 112eacd..b6bdbcc 100644 --- a/app/addons/documents/mango/mango.actiontypes.js +++ b/app/addons/documents/mango/mango.actiontypes.js @@ -13,5 +13,10 @@ define([], function () { return { MANGO_SET_DB: 'MANGO_SET_DB', + MANGO_NEW_QUERY_FIND_CODE_FROM_FIELDS: 'MANGO_NEW_QUERY_FIND_CODE_FROM_FIELDS', + MANGO_NEW_QUERY_FIND_CODE: 'MANGO_NEW_QUERY_FIND_CODE', + MANGO_NEW_QUERY_CREATE_INDEX_CODE: 'MANGO_NEW_QUERY_CREATE_INDEX_CODE', + MANGO_NEW_AVAILABLE_INDEXES: 'MANGO_NEW_AVAILABLE_INDEXES', + MANGO_RESET: 'MANGO_RESET' }; }); http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/a9e829c4/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 index 8764779..468c189 100644 --- a/app/addons/documents/mango/mango.components.react.jsx +++ b/app/addons/documents/mango/mango.components.react.jsx @@ -17,40 +17,35 @@ define([ 'addons/documents/mango/mango.stores', 'addons/documents/mango/mango.actions', 'addons/components/react-components.react', + 'addons/documents/index-results/actions', + 'addons/documents/mango/mango.helper', 'plugins/prettify' ], -function (app, FauxtonAPI, React, Stores, Actions, ReactComponents) { +function (app, FauxtonAPI, React, Stores, Actions, + ReactComponents, IndexResultActions, MangoHelper) { + var mangoStore = Stores.mangoStore; + var getDocUrl = app.helpers.getDocUrl; 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({ + var MangoQueryEditorController = React.createClass({ getInitialState: function () { return this.getStoreState(); }, getStoreState: function () { return { - queryCode: mangoStore.getQueryCode(), + queryCode: mangoStore.getQueryFindCode(), database: mangoStore.getDatabase(), + changedQuery: mangoStore.getQueryFindCodeChanged(), + availableIndexes: mangoStore.getAvailableQueryIndexes(), + additionalIndexes: mangoStore.getAvailableAdditionalIndexes(), + isLoading: mangoStore.getLoadingIndexes(), }; }, @@ -58,7 +53,12 @@ function (app, FauxtonAPI, React, Stores, Actions, ReactComponents) { this.setState(this.getStoreState()); }, + componentDidUpdate: function () { + prettyPrint(); + }, + componentDidMount: function () { + prettyPrint(); mangoStore.on('change', this.onChange, this); }, @@ -66,28 +66,93 @@ function (app, FauxtonAPI, React, Stores, Actions, ReactComponents) { mangoStore.off('change', this.onChange); }, + getMangoEditor: function () { + return this.refs.mangoEditor; + }, + + render: function () { + var loadLines; + if (this.state.isLoading) { + return ( + <div className="editor-wrapper span5 scrollable"> + <ReactComponents.LoadLines /> + </div> + ); + } + + return ( + <MangoEditor + ref="mangoEditor" + description={this.props.description} + dbName={this.state.database.id} + onSubmit={this.runQuery} + title={this.props.editorTitle} + additionalIndexesText={this.props.additionalIndexesText} + docs={getDocUrl('MANGO')} + exampleCode={this.state.queryCode} + changedQuery={this.state.changedQuery} + availableIndexes={this.state.availableIndexes} + additionalIndexes={this.state.additionalIndexes} + confirmbuttonText="Run Query" /> + ); + }, + + runQuery: function (event) { + event.preventDefault(); + + if (!this.getMangoEditor().hasValidCode()) { + FauxtonAPI.addNotification({ + msg: 'Please fix the Javascript errors and try again.', + type: 'error', + clear: true + }); + return; + } + + IndexResultActions.runMangoFindQuery({ + database: this.state.database, + queryCode: this.getMangoEditor().getEditorValue() + }); + } + }); + + var MangoEditor = React.createClass({ + getDefaultProps: function () { + return { + changedQuery: null, + availableIndexes: null, + additionalIndexes: null + }; + }, + render: function () { return ( <div className="editor-wrapper span5 scrollable"> <PaddedBorderedBox> - <div className="editor-description">{this.props.description}</div> + <div + dangerouslySetInnerHTML={{__html: this.props.description}} + className="editor-description"> + </div> </PaddedBorderedBox> <PaddedBorderedBox> <strong>Database</strong> - <div className="db-title">{this.state.database.id}</div> + <div className="db-title">{this.props.dbName}</div> </PaddedBorderedBox> - <form className="form-horizontal" onSubmit={this.saveQuery}> + <form className="form-horizontal" onSubmit={this.props.onSubmit}> <PaddedBorderedBox> <CodeEditor id="query-field" - ref="indexQueryEditor" - title={'Index'} - docs={false} - code={this.state.queryCode} /> + ref="field" + title={this.props.title} + docs={this.props.docs} + code={this.props.exampleCode} + disableUnload={true} /> + {this.getChangedQueryText()} </PaddedBorderedBox> + {this.getIndexBox()} <div className="padded-box"> <div className="control-group"> - <ConfirmButton text="Create Index" /> + <ConfirmButton text={this.props.confirmbuttonText} /> </div> </div> </form> @@ -95,24 +160,110 @@ function (app, FauxtonAPI, React, Stores, Actions, ReactComponents) { ); }, + getChangedQueryText: function () { + if (!this.props.changedQuery) { + return null; + } + + return ( + <div className="info-changed-query"> + <strong>Info:</strong> + <div>We changed the default query based on the last Index you created.</div> + </div> + ); + }, + + getIndexBox: function () { + if (!this.props.availableIndexes) { + return null; + } + + return ( + <PaddedBorderedBox> + <strong>Your available Indexes:</strong> + <pre + className="mango-available-indexes"> + {this.getIndexes('index', this.props.availableIndexes)} + {this.getIndexes('additonal', this.props.additionalIndexes)} + </pre> + </PaddedBorderedBox> + ); + }, + + getIndexes: function (prefix, indexes) { + if (!indexes) { + return; + } + + return indexes.map(function (index, i) { + var name = MangoHelper.getIndexName(index); + + return ( + <div key={prefix + i}>{name}</div> + ); + }); + }, + + getEditorValue: function () { + return this.refs.field.getValue(); + }, + getEditor: function () { - return this.refs.indexQueryEditor.getEditor(); + return this.refs.field.getEditor(); }, hasValidCode: function () { var editor = this.getEditor(); return editor.hadValidCode(); + } + }); + + var MangoIndexEditorController = React.createClass({ + getInitialState: function () { + return this.getStoreState(); }, - clearNotifications: function () { - var editor = this.getEditor(); - editor.editSaved(); + getStoreState: function () { + return { + queryIndexCode: mangoStore.getQueryIndexCode(), + database: mangoStore.getDatabase(), + }; + }, + + onChange: function () { + this.setState(this.getStoreState()); + }, + + componentDidMount: function () { + mangoStore.on('change', this.onChange, this); + }, + + componentWillUnmount: function () { + mangoStore.off('change', this.onChange); + }, + + getMangoEditor: function () { + return this.refs.mangoEditor; + }, + + render: function () { + return ( + <MangoEditor + ref="mangoEditor" + description={this.props.description} + dbName={this.state.database.id} + onSubmit={this.saveQuery} + title="Index" + docs={getDocUrl('MANGO')} + exampleCode={this.state.queryIndexCode} + confirmbuttonText="Create Index" /> + ); }, saveQuery: function (event) { event.preventDefault(); - if (!this.hasValidCode()) { + if (!this.getMangoEditor().hasValidCode()) { FauxtonAPI.addNotification({ msg: 'Please fix the Javascript errors and try again.', type: 'error', @@ -121,35 +272,16 @@ function (app, FauxtonAPI, React, Stores, Actions, ReactComponents) { return; } - this.clearNotifications(); - Actions.saveQuery({ database: this.state.database, - queryCode: this.refs.indexQueryEditor.getValue() + queryCode: this.getMangoEditor().getEditorValue() }); } }); 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 + MangoIndexEditorController: MangoIndexEditorController, + MangoQueryEditorController: MangoQueryEditorController }; return Views; http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/a9e829c4/app/addons/documents/mango/mango.helper.js ---------------------------------------------------------------------- diff --git a/app/addons/documents/mango/mango.helper.js b/app/addons/documents/mango/mango.helper.js new file mode 100644 index 0000000..d311973 --- /dev/null +++ b/app/addons/documents/mango/mango.helper.js @@ -0,0 +1,44 @@ +// 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' +], function (FauxtonAPI) { + + function getIndexName (doc) { + var nameArray = [], + indexes; + + nameArray = doc.get('def').fields.reduce(function (acc, el, i) { + if (i === 0) { + acc.push('json: ' + Object.keys(el)[0]); + } else { + acc.push(Object.keys(el)[0]); + } + + return acc; + }, []); + + if (!nameArray.length) { + indexes = FauxtonAPI.getExtensions('mango:additionalIndexes')[0]; + nameArray = indexes.createHeader(doc); + } + + return nameArray.join(', '); + } + + return { + getIndexName: getIndexName + }; + +}); + http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/a9e829c4/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 index f72a046..f0f2638 100644 --- a/app/addons/documents/mango/mango.stores.js +++ b/app/addons/documents/mango/mango.stores.js @@ -18,19 +18,80 @@ define([ function (FauxtonAPI, ActionTypes) { - var defaultQuery = '{\n' + - ' "index": {\n' + - ' "fields": ["_id"]\n' + - ' },\n' + - ' "type" : "json"\n' + - '}'; + var defaultQueryIndexCode = { + "index": { + "fields": ["_id"] + }, + "type" : "json" + }; + + var defaultQueryFindCode = { + "selector": { + "_id": {"$gt": null} + } + }; var Stores = {}; Stores.MangoStore = FauxtonAPI.Store.extend({ - getQueryCode: function () { - return defaultQuery; + initialize: function () { + this._queryFindCode = defaultQueryFindCode; + this._queryIndexCode = defaultQueryIndexCode; + this._queryFindCodeChanged = false; + this._availableIndexes = []; + this._getLoadingIndexes = true; + }, + + getQueryIndexCode: function () { + return this.formatCode(this._queryIndexCode); + }, + + setQueryIndexCode: function (options) { + this._queryIndexCode = options.code; + }, + + getQueryFindCode: function () { + return this.formatCode(this._queryFindCode); + }, + + setQueryFindCode: function (options) { + this._queryFindCode = options.code; + }, + + getLoadingIndexes: function () { + return this._getLoadingIndexes; + }, + + setLoadingIndexes: function (options) { + this._getLoadingIndexes = options.isLoading; + }, + + formatCode: function (code) { + return JSON.stringify(code, null, ' '); + }, + + newQueryFindCodeFromFields: function (options) { + var fields = options.fields, + queryCode = JSON.parse(JSON.stringify(this._queryFindCode)), + selectorContent; + + if (!fields) { + return; + } + + selectorContent = fields.reduce(function (acc, field) { + acc[field] = {"$gt": null}; + return acc; + }, {}); + + queryCode.selector = selectorContent; + this._queryFindCode = queryCode; + this._queryFindCodeChanged = true; + }, + + getQueryFindCodeChanged: function () { + return this._queryFindCodeChanged; }, setDatabase: function (options) { @@ -41,6 +102,28 @@ function (FauxtonAPI, ActionTypes) { return this._database; }, + setAvailableIndexes: function (options) { + this._availableIndexes = options.indexList; + }, + + getAvailableQueryIndexes: function () { + return this._availableIndexes.filter(function (el) { + return ['json', 'special'].indexOf(el.get('type')) !== -1; + }); + }, + + getAvailableAdditionalIndexes: function () { + var indexes = FauxtonAPI.getExtensions('mango:additionalIndexes')[0]; + + if (!indexes) { + return; + } + + return this._availableIndexes.filter(function (el) { + return el.get('type').indexOf(indexes.type) !== -1; + }); + }, + dispatch: function (action) { switch (action.type) { @@ -49,6 +132,30 @@ function (FauxtonAPI, ActionTypes) { this.triggerChange(); break; + case ActionTypes.MANGO_NEW_QUERY_CREATE_INDEX_CODE: + this.setQueryIndexCode(action.options); + this.triggerChange(); + break; + + case ActionTypes.MANGO_NEW_QUERY_FIND_CODE_FROM_FIELDS: + this.newQueryFindCodeFromFields(action.options); + this.triggerChange(); + break; + + case ActionTypes.MANGO_NEW_QUERY_FIND_CODE: + this.setQueryFindCode(action.options); + this.triggerChange(); + break; + + case ActionTypes.MANGO_NEW_AVAILABLE_INDEXES: + this.setAvailableIndexes(action.options); + this.triggerChange(); + break; + + case ActionTypes.MANGO_RESET: + this.setLoadingIndexes(action.options); + this.triggerChange(); + break; } } http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/a9e829c4/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 index 0f71fa3..2c126ad 100644 --- a/app/addons/documents/mango/tests/mango.componentsSpec.react.jsx +++ b/app/addons/documents/mango/tests/mango.componentsSpec.react.jsx @@ -15,29 +15,91 @@ define([ 'addons/documents/mango/mango.components.react', 'addons/documents/mango/mango.stores', 'addons/documents/mango/mango.actions', + 'addons/documents/mango/mango.actiontypes', 'addons/documents/resources', 'addons/databases/resources', 'testUtils', 'react' -], function (FauxtonAPI, Views, Stores, MangoActions, Resources, Databases, utils, React) { +], function (FauxtonAPI, Views, Stores, MangoActions, ActionTypes, Resources, Databases, utils, React) { var assert = utils.assert; var TestUtils = React.addons.TestUtils; - var fakeData = [ - { + describe('Mango IndexEditor', function () { + var database = new Databases.Model({id: 'testdb'}), + container, + editor; + + beforeEach(function () { + container = document.createElement('div'); + MangoActions.setDatabase({ + database: database + }); + }); + + afterEach(function () { + React.unmountComponentAtNode(container); + }); + + it('renders a default index definition', function () { + editor = TestUtils.renderIntoDocument( + <Views.MangoIndexEditorController description="foo" />, + container + ); + + var payload = JSON.parse(editor.getMangoEditor().getEditorValue()); + 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!'); + }); + }); + + describe('Mango QueryEditor', function () { + var database = new Databases.Model({id: 'testdb'}), + container, + editor, + mangoCollection; + + beforeEach(function () { + container = document.createElement('div'); + MangoActions.setDatabase({ + database: database + }); + + MangoActions.mangoResetIndexList({isLoading: false}); + + mangoCollection = new Resources.MangoIndexCollection([{ ddoc: '_design/e4d338e5d6f047749f5399ab998b4fa04ba0c816', def: { - fields: [{ - '_id': 'asc' - }] + fields: [ + {'_id': 'asc'}, + {'foo': 'bar'}, + {'ente': 'gans'} + ] }, name: 'e4d338e5d6f047749f5399ab998b4fa04ba0c816', type: 'json' - }, - { + }, { ddoc: null, def: { fields: [{ @@ -46,46 +108,103 @@ define([ }, name: '_all_docs', type: 'special' - } - ]; - - - describe('Mango IndexEditor', function () { - var database = new Databases.Model({id: 'testdb'}), - container, - editor; + }], { + params: {}, + database: { + safeID: function () { return '1'; } + } + }); - beforeEach(function () { - container = document.createElement('div'); - MangoActions.setDatabase({ - database: database + FauxtonAPI.dispatch({ + type: ActionTypes.MANGO_NEW_AVAILABLE_INDEXES, + options: {indexList: mangoCollection} }); + }); afterEach(function () { React.unmountComponentAtNode(container); }); - it('renders a default index definition', function () { - editor = TestUtils.renderIntoDocument(<Views.MangoIndexEditorController description="foo" />, container); + it('lists our available indexes', function () { + editor = TestUtils.renderIntoDocument( + <Views.MangoQueryEditorController description="foo" />, + container + ); var $el = $(editor.getDOMNode()); - var payload = JSON.parse(editor.refs.indexQueryEditor.getValue()); - assert.equal(payload.index.fields[0], '_id'); + assert.equal($el.find('.mango-available-indexes').length, 1); + + assert.include( + $el.find('.mango-available-indexes').text(), + 'json: _id, foo, ente' + ); + assert.include( + $el.find('.mango-available-indexes').text(), + 'json: _id' + ); + }); + + it('has a default query', function () { + editor = React.render( + <Views.MangoQueryEditorController description="foo" />, + container + ); + var json = JSON.parse(editor.getMangoEditor().getEditorValue()); + assert.equal(Object.keys(json.selector)[0], '_id'); + }); + + it('can render a query based on the last defined index', function () { + FauxtonAPI.dispatch({ + type: ActionTypes.MANGO_NEW_QUERY_FIND_CODE_FROM_FIELDS, + options: { + fields: ['zetti', 'mussmaennchen'] + } + }); + + editor = TestUtils.renderIntoDocument( + <Views.MangoQueryEditorController description="foo" />, + container + ); + + var json = JSON.parse(editor.getMangoEditor().getEditorValue()); + assert.equal(Object.keys(json.selector)[0], 'zetti'); + assert.equal(Object.keys(json.selector)[1], 'mussmaennchen'); + }); + + it('informs the user that it uses a query based on the last defined index', function () { + FauxtonAPI.dispatch({ + type: ActionTypes.MANGO_NEW_QUERY_FIND_CODE_FROM_FIELDS, + options: { + fields: ['zetti', 'mussmaennchen'] + } + }); + + editor = TestUtils.renderIntoDocument( + <Views.MangoQueryEditorController description="foo" />, + container + ); + var $el = $(editor.getDOMNode()); + assert.equal($el.find('.info-changed-query').length, 1); }); it('renders the current database', function () { - editor = TestUtils.renderIntoDocument(<Views.MangoIndexEditorController description="foo" />, container); + editor = TestUtils.renderIntoDocument( + <Views.MangoQueryEditorController 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); + editor = TestUtils.renderIntoDocument( + <Views.MangoQueryEditorController 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/a9e829c4/app/addons/documents/mango/tests/mango.storesSpec.js ---------------------------------------------------------------------- diff --git a/app/addons/documents/mango/tests/mango.storesSpec.js b/app/addons/documents/mango/tests/mango.storesSpec.js new file mode 100644 index 0000000..b41d7f6 --- /dev/null +++ b/app/addons/documents/mango/tests/mango.storesSpec.js @@ -0,0 +1,101 @@ +// 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.stores', + 'addons/documents/mango/mango.actiontypes', + 'addons/documents/resources', + + 'testUtils' +], function (FauxtonAPI, Stores, ActionTypes, Resources, testUtils) { + var assert = testUtils.assert; + var dispatchToken; + var store; + + describe('Mango Store', function () { + + describe('getQueryCode', function () { + + beforeEach(function () { + store = new Stores.MangoStore(); + dispatchToken = FauxtonAPI.dispatcher.register(store.dispatch); + }); + + afterEach(function () { + FauxtonAPI.dispatcher.unregister(dispatchToken); + }); + + it('returns a default query', function () { + assert.ok(store.getQueryFindCode()); + }); + + it('can set new selectors', function () { + store.newQueryFindCodeFromFields({fields: ['foo', 'bar']}); + var res = store.getQueryFindCode(); + assert.equal(res, JSON.stringify({ + "selector": { + "foo": {"$gt": null}, + "bar": {"$gt": null} + } + }, null, ' ')); + }); + + it('indicates that we set another query for the user', function () { + assert.notOk(store.getQueryFindCodeChanged()); + store.newQueryFindCodeFromFields({fields: ['mussman', 'zetti']}); + assert.ok(store.getQueryFindCodeChanged()); + }); + + it('alters the default query', function () { + assert.notOk(store.getQueryFindCodeChanged()); + store.newQueryFindCodeFromFields({fields: ['mussman', 'zetti']}); + assert.deepEqual(store.getQueryFindCode(), JSON.stringify({ + "selector": { + "mussman": {"$gt": null}, + "zetti": {"$gt": null} + } + }, null, ' ')); + }); + + it('filters querytypes that are not needed', function () { + + var collection = new Resources.MangoIndexCollection([ + new Resources.MangoIndex({ + ddoc: null, + name: 'emma', + type: 'special', + def: {fields: [{_id: 'asc'}]} + }, {}), + new Resources.MangoIndex({ + ddoc: null, + name: 'biene', + type: 'json', + def: {fields: [{_id: 'desc'}]} + }, {}), + new Resources.MangoIndex({ + ddoc: null, + name: 'alf', + type: 'nickname', + def: {fields: [{_id: 'asc'}]} + }, {}) + ], { + database: {id: 'databaseId', safeID: function () { return this.id; }}, + params: {limit: 20} + }); + store._availableIndexes = collection; + assert.equal(store.getAvailableQueryIndexes().length, 2); + }); + + }); + }); +}); http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/a9e829c4/app/addons/documents/resources.js ---------------------------------------------------------------------- diff --git a/app/addons/documents/resources.js b/app/addons/documents/resources.js index 7385a68..7857231 100644 --- a/app/addons/documents/resources.js +++ b/app/addons/documents/resources.js @@ -73,7 +73,17 @@ function (app, FauxtonAPI, Documents, PagingCollection) { }); Documents.MangoIndex = Documents.Doc.extend({ - idAttribute: 'name', + idAttribute: 'ddoc', + + getId: function () { + + if (this.id) { + return this.id; + } + + + return '_all_docs'; + }, isNew: function () { // never use put @@ -120,22 +130,168 @@ function (app, FauxtonAPI, Documents, PagingCollection) { return res.indexes; }, - urlRef: function (params) { + urlRef: function (context, params) { var database = this.database.safeID(), query = ''; - if (params) { - if (!_.isEmpty(params)) { - query = '?' + $.param(params); - } else { + if (!context) { + context = 'index-server'; + } + + return FauxtonAPI.urls('mango', context, database, query); + } + }); + + // MANGO INDEX EDITOR + Documents.MangoDoc = Documents.Doc.extend({ + isMangoDoc: function () { + return true; + } + }); + + Documents.MangoDocumentCollection = PagingCollection.extend({ + model: Documents.MangoDoc, + initialize: function (_attr, options) { + var defaultLimit = FauxtonAPI.constants.MISC.DEFAULT_PAGE_SIZE; + + this.database = options.database; + this.params = _.extend({limit: defaultLimit}, options.params); + + this.paging = _.defaults((options.paging || {}), { + defaultParams: _.defaults({}, options.params), + hasNext: false, + hasPrevious: false, + params: {}, + pageSize: FauxtonAPI.constants.MISC.DEFAULT_PAGE_SIZE, + direction: undefined + }); + + this.paging.params = _.clone(this.paging.defaultParams); + }, + + url: function () { + return this.urlRef.apply(this, arguments); + }, + + updateSeq: function () { + return false; + }, + + isEditable: function () { + return true; + }, + + setQuery: function (query) { + this.query = query; + return this; + }, + + pageSizeReset: function (pageSize, opts) { + var options = _.defaults((opts || {}), {fetch: true}); + this.paging.direction = undefined; + this.paging.pageSize = pageSize; + this.paging.params = this.paging.defaultParams; + this.paging.params.limit = pageSize; + + if (options.fetch) { + return this.fetch(); + } + }, + + _iterate: function (offset, opts) { + var options = _.defaults((opts || {}), {fetch: true}); + + this.paging.params = this.calculateParams(this.paging.params, offset, this.paging.pageSize); + + return this.fetch(); + }, + + getPaginatedQuery: function () { + var paginatedQuery = JSON.parse(JSON.stringify(this.query)); + + if (!this.paging.direction && this.paging.params.limit > 0) { + this.paging.direction = 'fetch'; + this.paging.params.limit = this.paging.params.limit + 1; + } + + // just update if NOT provided by editor + if (!paginatedQuery.limit) { + paginatedQuery.limit = this.paging.params.limit; + } + + if (!paginatedQuery.skip) { + paginatedQuery.skip = this.paging.params.skip; + } + + return paginatedQuery; + }, + + fetch: function () { + var url = this.urlRef(), + promise = FauxtonAPI.Deferred(), + query = this.getPaginatedQuery(); + + $.ajax({ + type: 'POST', + url: url, + contentType: 'application/json', + dataType: 'json', + data: JSON.stringify(query), + }) + .then(function (res) { + this.handleResponse(res, promise); + }.bind(this)) + .fail(function (res) { + promise.reject(res.responseJSON); + }.bind(this)); + + return promise; + }, + + parse: function (resp) { + var rows = resp.docs; + + this.paging.hasNext = this.paging.hasPrevious = false; + + this.viewMeta = { + total_rows: resp.total_rows, + offset: resp.offset, + update_seq: resp.update_seq + }; + + var skipLimit = this.paging.defaultParams.skip || 0; + if (this.paging.params.skip > skipLimit) { + this.paging.hasPrevious = true; + } + + if (rows.length === this.paging.pageSize + 1) { + this.paging.hasNext = true; + + // remove the next page marker result + rows.pop(); + this.viewMeta.total_rows = this.viewMeta.total_rows - 1; + } + + return rows; + }, + + handleResponse: function (res, promise) { + var models = this.parse(res); + + this.reset(models); + + promise.resolve(); + }, + + urlRef: function (context) { + var database = this.database.safeID(), query = ''; - } - } else if (this.params) { - var parsedParam = Documents.QueryParams.stringify(this.params); - query = '?' + $.param(parsedParam); + + if (!context) { + context = 'query-server'; } - return FauxtonAPI.urls('mango', 'index-apiurl', database, query); + return FauxtonAPI.urls('mango', context, database, query); } }); @@ -171,6 +327,10 @@ function (app, FauxtonAPI, Documents, PagingCollection) { this.databaseId = options.databaseId; }, + url: function () { + return app.host + '/' + this.databaseId + '/_bulk_docs'; + }, + bulkDelete: function () { var payload = this.createPayload(this.toJSON()), promise = FauxtonAPI.Deferred(), @@ -178,7 +338,7 @@ function (app, FauxtonAPI, Documents, PagingCollection) { $.ajax({ type: 'POST', - url: app.host + '/' + this.databaseId + '/_bulk_docs', + url: this.url(), contentType: 'application/json', dataType: 'json', data: JSON.stringify(payload), @@ -254,6 +414,27 @@ function (app, FauxtonAPI, Documents, PagingCollection) { } }); + Documents.MangoBulkDeleteDocCollection = Documents.BulkDeleteDocCollection.extend({ + url: function () { + return app.host + '/' + this.databaseId + '/_index/_bulk_delete'; + }, + + createPayload: function (documents) { + var documentList = documents + .filter(function (doc) { + return doc._id !== '_all_docs'; + }) + .map(function (doc) { + return doc._id; + }); + + return { + docids: documentList + }; + } + + }); + Documents.IndexCollection = PagingCollection.extend({ model: Documents.Doc, documentation: function () { @@ -422,6 +603,10 @@ function (app, FauxtonAPI, Documents, PagingCollection) { title: 'New View', url: newUrlPrefix + '/new_view', icon: 'fonticon-plus-circled' + }, { + title: app.i18n.en_US['new-mango-index'], + url: newUrlPrefix + '/_index', + icon: 'fonticon-plus-circled' }]; return _.reduce(FauxtonAPI.getExtensions('sidebar:links'), function (menuLinks, link) { http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/a9e829c4/app/addons/documents/routes-documents.js ---------------------------------------------------------------------- diff --git a/app/addons/documents/routes-documents.js b/app/addons/documents/routes-documents.js index 7f8535c..82fcc35 100644 --- a/app/addons/documents/routes-documents.js +++ b/app/addons/documents/routes-documents.js @@ -20,7 +20,6 @@ define([ 'addons/documents/changes/components.react', 'addons/documents/changes/actions', 'addons/documents/views-doceditor', - 'addons/documents/views-mango', 'addons/databases/base', 'addons/documents/resources', @@ -33,11 +32,10 @@ define([ 'addons/documents/header/header.actions' ], -function (app, FauxtonAPI, BaseRoute, Documents, Changes, ChangesActions, DocEditor, Mango, +function (app, FauxtonAPI, BaseRoute, Documents, Changes, ChangesActions, DocEditor, Databases, Resources, Components, PaginationStores, IndexResultsActions, IndexResultsComponents, ReactPagination, ReactHeader, ReactActions) { - var DocumentsRouteObject = BaseRoute.extend({ layout: "with_tabs_sidebar", routes: { @@ -138,7 +136,8 @@ function (app, FauxtonAPI, BaseRoute, Documents, Changes, ChangesActions, DocEdi IndexResultsActions.newResultsList({ collection: collection, isListDeletable: true, - textEmptyIndex: 'No Document Created Yet!' + textEmptyIndex: 'No Document Created Yet!', + bulkCollection: Documents.BulkDeleteDocCollection }); this.database.allDocs.paging.pageSize = PaginationStores.indexPaginationStore.getPerPage(); http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/a9e829c4/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 217cc7e..981d6f7 100644 --- a/app/addons/documents/routes-index-editor.js +++ b/app/addons/documents/routes-index-editor.js @@ -94,7 +94,8 @@ function (app, FauxtonAPI, Helpers, BaseRoute, Documents, IndexEditorComponents, IndexResultsActions.newResultsList({ collection: this.indexedDocs, - isListDeletable: false + isListDeletable: false, + bulkCollection: Documents.BulkDeleteDocCollection }); ActionsIndexEditor.fetchDesignDocsBeforeEdit({ @@ -147,7 +148,8 @@ function (app, FauxtonAPI, Helpers, BaseRoute, Documents, IndexEditorComponents, IndexResultsActions.newResultsList({ collection: [], - isListDeletable: false + isListDeletable: false, + bulkCollection: Documents.BulkDeleteDocCollection }); } http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/a9e829c4/app/addons/documents/routes-mango.js ---------------------------------------------------------------------- diff --git a/app/addons/documents/routes-mango.js b/app/addons/documents/routes-mango.js index d8c3d64..2bad50a 100644 --- a/app/addons/documents/routes-mango.js +++ b/app/addons/documents/routes-mango.js @@ -13,42 +13,45 @@ 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', + 'addons/documents/header/header.react', - 'addons/documents/header/header.actions' + 'addons/documents/header/header.actions', + 'addons/documents/pagination/pagination.react', + + 'addons/documents/mango/mango.components.react', + 'addons/documents/mango/mango.actions', + 'addons/documents/mango/mango.stores', + 'addons/documents/index-results/index-results.components.react' ], -function (app, FauxtonAPI, Helpers, BaseRoute, Mango, Databases, - Components, Resources, Documents, IndexResultsActions, - PaginationStores, ReactHeader, ReactActions) { - var MangoIndexList = BaseRoute.extend({ - layout: 'with_tabs_sidebar', +function (app, FauxtonAPI, Helpers, BaseRoute, Databases, + Components, Resources, Documents, IndexResultsActions, PaginationStores, + ReactHeader, ReactActions, ReactPagination, + MangoComponents, MangoActions, MangoStores, IndexResultsComponents) { + + var MangoIndexEditorAndQueryEditor = BaseRoute.extend({ + layout: 'two_pane', routes: { - 'database/:database/_indexlist(:extra)': { - route: 'mangoIndexList', + 'database/:database/_index': { + route: 'createIndex', + roles: ['fx_loggedIn'] + }, + 'database/:database/_find': { + route: 'findUsingIndex', roles: ['fx_loggedIn'] }, - - }, - - establish: function () { - return [ - this.designDocs.fetch({reset: true}), - this.allDatabases.fetchOnce() - ]; }, initialize: function (route, masterLayout, options) { @@ -62,15 +65,21 @@ function (app, FauxtonAPI, Helpers, BaseRoute, Mango, Databases, this.addLeftHeader(); this.addSidebar(); - this.rightHeader = this.setView('#right-header', new Documents.Views.RightAllDocsHeader({ + MangoActions.setDatabase({ database: this.database - })); + }); }, - mangoIndexList: function () { + findUsingIndex: function () { var params = this.createParams(), urlParams = params.urlParams, - mangoIndexCollection = new Resources.MangoIndexCollection(null, { + mangoResultCollection = new Resources.MangoDocumentCollection(null, { + database: this.database, + paging: { + pageSize: PaginationStores.indexPaginationStore.getPerPage() + } + }), + mangoIndexList = new Resources.MangoIndexCollection(null, { database: this.database, params: null, paging: { @@ -78,81 +87,85 @@ function (app, FauxtonAPI, Helpers, BaseRoute, Mango, Databases, } }); - ReactActions.resetHeaderController(); - - this.viewEditor && this.viewEditor.remove(); - this.headerView && this.headerView.remove(); - - this.sidebar.setSelectedTab('mango-indexes'); + // magic method + this.sidebar.setSelectedTab('mango-query'); + this.setComponent('#react-headerbar', ReactHeader.HeaderBarController); + this.setComponent('#footer', ReactPagination.Footer); - IndexResultsActions.newResultsList({ - collection: mangoIndexCollection, - isListDeletable: false + IndexResultsActions.newMangoResultsList({ + collection: mangoResultCollection, + isListDeletable: true, + textEmptyIndex: 'No Results', + bulkCollection: Documents.BulkDeleteDocCollection }); - this.setComponent('#react-headerbar', ReactHeader.HeaderBarController); + MangoActions.getIndexList({ + indexList: mangoIndexList + }); - this.leftheader.updateCrumbs(this.getCrumbs(this.database)); - this.rightHeader.hideQueryOptions(); + this.breadcrumbs = this.setView('#breadcrumbs', new Components.Breadcrumbs({ + toggleDisabled: true, + crumbs: [ + {'type': 'back', 'link': Databases.databaseUrl(this.database)}, + {'name': app.i18n.en_US['mango-title-editor'], 'link': Databases.databaseUrl(this.database)} + ] + })); - this.resultList = this.setView('#dashboard-lower-content', new Mango.MangoIndexListReact()); + this.setComponent('#left-content', MangoComponents.MangoQueryEditorController, { + description: app.i18n.en_US['mango-descripton'], + editorTitle: app.i18n.en_US['mango-title-editor'], + additionalIndexesText: app.i18n.en_US['mango-additional-indexes-heading'] + }); + this.setComponent('#dashboard-lower-content', IndexResultsComponents.List); this.apiUrl = function () { - return [mangoIndexCollection.urlRef(urlParams), FauxtonAPI.constants.DOC_URLS.GENERAL]; + return [mangoResultCollection.urlRef('query-apiurl', urlParams), FauxtonAPI.constants.DOC_URLS.MANGO]; }; - } - }); - - 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 + database: this.database, + params: null, + paging: { + pageSize: PaginationStores.indexPaginationStore.getPerPage() + } }); + IndexResultsActions.newResultsList({ collection: mangoIndexCollection, - isListDeletable: false + isListDeletable: true, + bulkCollection: Documents.MangoBulkDeleteDocCollection, + typeOfIndex: 'mango' }); this.breadcrumbs = this.setView('#breadcrumbs', new Components.Breadcrumbs({ toggleDisabled: true, crumbs: [ - {'type': 'back', 'link': Helpers.getPreviousPage(this.database)}, + {'type': 'back', 'link': Databases.databaseUrl(this.database)}, {'name': 'Create new index', 'link': Databases.databaseUrl(this.database) } ] })); - this.resultList = this.setView('#dashboard-lower-content', new Mango.HelpScreen()); + ReactActions.resetHeaderController(); + this.setComponent('#react-headerbar', ReactHeader.HeaderBarController); + this.setComponent('#footer', ReactPagination.Footer); - this.mangoEditor = this.setView('#left-content', new Mango.MangoIndexEditorReact({ - database: this.database - })); + this.setComponent('#dashboard-lower-content', IndexResultsComponents.List); + this.setComponent('#left-content', MangoComponents.MangoIndexEditorController, { + description: app.i18n.en_US['mango-descripton-index-editor'] + }); this.apiUrl = function () { - return [mangoIndexCollection.urlRef(urlParams), FauxtonAPI.constants.DOC_URLS.GENERAL]; + return [mangoIndexCollection.urlRef('index-apiurl', urlParams), FauxtonAPI.constants.DOC_URLS.MANGO]; }; } }); return { - MangoIndexEditorAndResults: MangoIndexEditorAndResults, - MangoIndexList: MangoIndexList + MangoIndexEditorAndQueryEditor: MangoIndexEditorAndQueryEditor }; }); http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/a9e829c4/app/addons/documents/routes.js ---------------------------------------------------------------------- diff --git a/app/addons/documents/routes.js b/app/addons/documents/routes.js index ad92ded..4401edc 100644 --- a/app/addons/documents/routes.js +++ b/app/addons/documents/routes.js @@ -25,8 +25,7 @@ function (Documents, DocumentsRouteObject, docEditor, IndexEditorRouteObject, Ma docEditor.NewDocEditorRouteObject, DocumentsRouteObject, IndexEditorRouteObject, - Mango.MangoIndexList, - Mango.MangoIndexEditorAndResults + Mango.MangoIndexEditorAndQueryEditor ]; return Documents; http://git-wip-us.apache.org/repos/asf/couchdb-fauxton/blob/a9e829c4/app/addons/documents/shared-resources.js ---------------------------------------------------------------------- diff --git a/app/addons/documents/shared-resources.js b/app/addons/documents/shared-resources.js index 35c0e51..00e0a59 100644 --- a/app/addons/documents/shared-resources.js +++ b/app/addons/documents/shared-resources.js @@ -58,13 +58,26 @@ define([ }, isDeletable: function () { - return true; + return !!this.id; }, isFromView: function () { return !this.id; }, + isMangoDoc: function () { + if (!this.isDdoc()) return false; + if (this.get('language') === 'query') { + return true; + } + + if (this.get('doc') && this.get('doc').language === 'query') { + return true; + } + + return false; + }, + isReducedShown : function () { if (this.collection) { return this.collection.params.reduce; @@ -130,19 +143,6 @@ define([ this.set({views: views}); }, - isMangoDoc: function () { - if (!this.isDdoc()) return false; - if (this.get('language') === 'query') { - return true; - } - - if (this.get('doc') && this.get('doc').language === 'query') { - return true; - } - - return false; - }, - dDocModel: function () { if (!this.isDdoc()) return false; var doc = this.get('doc');
