You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@couchdb.apache.org by ga...@apache.org on 2017/09/20 08:47:32 UTC

[couchdb-fauxton] branch master updated: Refactor documents/mango addon to use Redux (#971)

This is an automated email from the ASF dual-hosted git repository.

garren pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/couchdb-fauxton.git


The following commit(s) were added to refs/heads/master by this push:
     new 5107a80  Refactor documents/mango addon to use Redux (#971)
5107a80 is described below

commit 5107a800609de7f4269b120aec97630572c79626
Author: Antonio Maranhao <30...@users.noreply.github.com>
AuthorDate: Wed Sep 20 04:47:30 2017 -0400

    Refactor documents/mango addon to use Redux (#971)
    
    This uses the new pagination and redux stores. It also adds support for custom middleware
---
 .../{base-api.test.js => base-actions.test.js}     |  10 +-
 .../{fetch-api.test.js => fetch-actions.test.js}   |  18 +-
 .../documents/__tests__/index-results.test.js      |  32 +-
 app/addons/documents/__tests__/json-view.test.js   |  55 ++--
 ...tion-api.test.js => pagination-actions.test.js} |   2 +-
 ...ns-api.test.js => queryoptions-actions.test.js} |  28 +-
 app/addons/documents/__tests__/reducers.test.js    |  16 +-
 .../documents/__tests__/results-toolbar.test.js    |  12 +
 .../documents/__tests__/shared-helpers.test.js     | 119 +++++++-
 app/addons/documents/assets/less/view-editor.less  |   1 +
 app/addons/documents/base.js                       |  12 +-
 .../documents/components/header-docs-right.js      |  15 +-
 app/addons/documents/constants.js                  |   6 +
 app/addons/documents/header/header.js              |  14 +-
 app/addons/documents/index-results/actions.js      |   2 +-
 .../index-results/{apis => actions}/base.js        |  17 +-
 .../index-results/{apis => actions}/fetch.js       |  96 +++---
 .../index-results/{apis => actions}/pagination.js  |  14 +-
 .../{apis => actions}/queryoptions.js              |   6 +-
 app/addons/documents/index-results/api.js          |  64 ++++
 .../components/results/IndexResults.js             |  18 +-
 .../components/results/ResultsScreen.js            |   5 +-
 .../index-results/components/results/TableView.js  |   1 -
 .../index-results/containers/ApiBarContainer.js    |  26 +-
 .../containers/IndexResultsContainer.js            |  31 +-
 .../containers/PaginationContainer.js              |  17 +-
 .../containers/QueryOptionsContainer.js            |  17 +-
 .../documents/index-results/helpers/json-view.js   |  32 +-
 .../index-results/helpers/shared-helpers.js        |  43 ++-
 .../documents/index-results/helpers/table-view.js  |  14 +-
 app/addons/documents/index-results/reducers.js     |  32 +-
 app/addons/documents/index-results/stores.js       |  20 +-
 .../tests/index-results.componentsSpec.js          |  12 +-
 .../tests/index-results.storesSpec.js              |   2 +-
 app/addons/documents/layouts.js                    |  13 +-
 .../documents/mango/__tests__/mango.api.test.js    | 122 ++++++++
 .../mango/__tests__/mango.components.test.js       | 100 +++++++
 .../documents/mango/__tests__/mango.store.test.js  | 128 --------
 .../ExplainPage.js}                                |  28 +-
 .../documents/mango/components/MangoIndexEditor.js | 128 ++++++++
 .../mango/components/MangoIndexEditorContainer.js  |  54 ++++
 .../documents/mango/components/MangoQueryEditor.js | 168 +++++++++++
 .../mango/components/MangoQueryEditorContainer.js  |  99 +++++++
 app/addons/documents/mango/mango.actions.js        | 139 +++++----
 app/addons/documents/mango/mango.actiontypes.js    |   8 +-
 app/addons/documents/mango/mango.api.js            | 129 ++++++++
 app/addons/documents/mango/mango.components.js     | 325 +--------------------
 app/addons/documents/mango/mango.helper.js         |  35 ++-
 app/addons/documents/mango/mango.reducers.js       | 205 +++++++++++++
 app/addons/documents/mango/mango.stores.js         | 168 -----------
 .../documents/mango/tests/mango.componentsSpec.js  |  83 ------
 app/addons/documents/mangolayout.js                | 119 ++++----
 app/addons/documents/resources.js                  |   3 +-
 app/addons/documents/routes-index-editor.js        |   3 +-
 app/addons/documents/routes-mango.js               |  67 ++---
 .../documents/tests/nightwatch/mangoIndex.js       |   1 +
 .../documents/tests/nightwatch/mangoQuery.js       |   4 +-
 app/core/base.js                                   |   8 +
 app/main.js                                        |   5 +-
 59 files changed, 1839 insertions(+), 1112 deletions(-)

diff --git a/app/addons/documents/__tests__/base-api.test.js b/app/addons/documents/__tests__/base-actions.test.js
similarity index 93%
rename from app/addons/documents/__tests__/base-api.test.js
rename to app/addons/documents/__tests__/base-actions.test.js
index c966316..608e738 100644
--- a/app/addons/documents/__tests__/base-api.test.js
+++ b/app/addons/documents/__tests__/base-actions.test.js
@@ -19,7 +19,7 @@ import {
   bulkCheckOrUncheck,
   changeLayout,
   changeTableHeaderAttribute
-} from '../index-results/apis/base';
+} from '../index-results/actions/base';
 import ActionTypes from '../index-results/actiontypes';
 import Constants from '../constants';
 
@@ -56,12 +56,14 @@ describe('Docs Base API', () => {
       limit: 21
     };
     const canShowNext = true;
+    const docType = Constants.INDEX_RESULTS_DOC_TYPE.VIEW;
 
-    expect(newResultsAvailable(docs, params, canShowNext)).toEqual({
+    expect(newResultsAvailable(docs, params, canShowNext, docType)).toEqual({
       type: ActionTypes.INDEX_RESULTS_REDUX_NEW_RESULTS,
       docs: docs,
       params: params,
-      canShowNext: canShowNext
+      canShowNext: canShowNext,
+      docType: docType
     });
   });
 
@@ -116,7 +118,7 @@ describe('Docs Base API', () => {
     it('returns the proper event to dispatch when allDocumentsSelected false', () => {
       const selectedDocs = [];
       const allDocumentsSelected = false;
-      expect(bulkCheckOrUncheck(docs, selectedDocs, allDocumentsSelected)).toEqual({
+      expect(bulkCheckOrUncheck(docs, selectedDocs, allDocumentsSelected, Constants.INDEX_RESULTS_DOC_TYPE.MANGO_QUERY)).toEqual({
         type: ActionTypes.INDEX_RESULTS_REDUX_NEW_SELECTED_DOCS,
         selectedDocs: [
           {
diff --git a/app/addons/documents/__tests__/fetch-api.test.js b/app/addons/documents/__tests__/fetch-actions.test.js
similarity index 95%
rename from app/addons/documents/__tests__/fetch-api.test.js
rename to app/addons/documents/__tests__/fetch-actions.test.js
index 59fdd82..1c45bf0 100644
--- a/app/addons/documents/__tests__/fetch-api.test.js
+++ b/app/addons/documents/__tests__/fetch-actions.test.js
@@ -13,17 +13,17 @@
 import {
   mergeParams,
   removeOverflowDocsAndCalculateHasNext,
-  queryEndpoint,
   validateBulkDelete,
-  postToBulkDocs,
   processBulkDeleteResponse
-} from '../index-results/apis/fetch';
+} from '../index-results/actions/fetch';
+import {queryAllDocs, postToBulkDocs} from '../index-results/api';
 import fetchMock from 'fetch-mock';
 import queryString from 'query-string';
 import sinon from 'sinon';
 import SidebarActions from '../sidebar/actions';
 import FauxtonAPI from '../../../core/api';
 import '../base';
+import Constants from '../constants';
 
 describe('Docs Fetch API', () => {
   describe('mergeParams', () => {
@@ -180,7 +180,7 @@ describe('Docs Fetch API', () => {
     });
   });
 
-  describe('queryEndpoint', () => {
+  describe('queryAllDocs', () => {
     const params = {
       limit: 21,
       skip: 0
@@ -212,8 +212,10 @@ describe('Docs Fetch API', () => {
       const url = `${fetchUrl}?${query}`;
       fetchMock.getOnce(url, docs);
 
-      return queryEndpoint(fetchUrl, params).then((docs) => {
-        expect(docs).toEqual([
+      return queryAllDocs(fetchUrl, params).then((res) => {
+        expect(res).toEqual({
+          docType: Constants.INDEX_RESULTS_DOC_TYPE.VIEW,
+          docs: [
           {
             id: "foo",
             key: "foo",
@@ -227,8 +229,8 @@ describe('Docs Fetch API', () => {
             value: {
               rev: "2-1390740c4877979dbe8998382876556c"
             }
-          }
-        ]);
+          }]
+        });
       });
     });
   });
diff --git a/app/addons/documents/__tests__/index-results.test.js b/app/addons/documents/__tests__/index-results.test.js
index 574db68..93734ef 100644
--- a/app/addons/documents/__tests__/index-results.test.js
+++ b/app/addons/documents/__tests__/index-results.test.js
@@ -17,18 +17,32 @@ import IndexResults from '../index-results/components/results/IndexResults';
 import sinon from 'sinon';
 
 describe('IndexResults', () => {
-  it('calls fetchAllDocs on mount', () => {
+  it('calls fetchDocs on mount only when fetchAtStartup is set to true', () => {
     const spy = sinon.spy();
-    const wrapper = shallow(<IndexResults
+    const wrapperFetch = shallow(<IndexResults
       fetchParams={{}}
       selectedDocs={[]}
       queryOptionsParams={{}}
-      fetchAllDocs={spy}
+      fetchDocs={spy}
       results={[]}
+      fetchAtStartup={true}
     />);
 
-    wrapper.instance().componentDidMount();
+    wrapperFetch.instance().componentDidMount();
     expect(spy.calledOnce).toBe(true);
+
+    spy.reset();
+    const wrapperDontFetch = shallow(<IndexResults
+      fetchParams={{}}
+      selectedDocs={[]}
+      queryOptionsParams={{}}
+      fetchDocs={spy}
+      results={[]}
+      fetchAtStartup={false}
+    />);
+
+    wrapperDontFetch.instance().componentDidMount();
+    expect(spy.notCalled).toBe(true);
   });
 
   it('calls fetchAllDocs on update if ddocsOnly switches', () => {
@@ -37,16 +51,17 @@ describe('IndexResults', () => {
       fetchParams={{}}
       selectedDocs={[]}
       queryOptionsParams={{}}
-      fetchAllDocs={() => {}}
+      fetchDocs={() => {}}
       results={[]}
       ddocsOnly={false}
+      fetchAtStartup={true}
     />);
 
     wrapper.instance().componentWillUpdate({
       ddocsOnly: true,
       fetchParams: {},
       queryOptionsParams: {},
-      fetchAllDocs: spy
+      fetchDocs: spy
     });
 
     expect(spy.calledOnce).toBe(true);
@@ -61,6 +76,7 @@ describe('IndexResults', () => {
       queryOptionsParams={{}}
       fetchAllDocs={() => {}}
       results={[]}
+      fetchAtStartup={true}
     />);
 
     wrapper.instance().deleteSelectedDocs();
@@ -75,6 +91,7 @@ describe('IndexResults', () => {
       selectedDocs={selectedDocs}
       fetchAllDocs={() => {}}
       results={[]}
+      fetchAtStartup={true}
     />);
 
     expect(wrapper.instance().isSelected('foo')).toBe(true);
@@ -88,6 +105,7 @@ describe('IndexResults', () => {
       selectedDocs={selectedDocs}
       fetchAllDocs={() => {}}
       results={[]}
+      fetchAtStartup={true}
     />);
 
     expect(wrapper.instance().isSelected('foo')).toBe(false);
@@ -100,6 +118,7 @@ describe('IndexResults', () => {
       fetchAllDocs={() => {}}
       results={[]}
       selectDoc={spy}
+      fetchAtStartup={true}
     />);
 
     wrapper.instance().docChecked('foo', '1-123324345');
@@ -115,6 +134,7 @@ describe('IndexResults', () => {
       docs={[]}
       allDocumentsSelected={false}
       bulkCheckOrUncheck={spy}
+      fetchAtStartup={true}
     />);
 
     wrapper.instance().toggleSelectAll();
diff --git a/app/addons/documents/__tests__/json-view.test.js b/app/addons/documents/__tests__/json-view.test.js
index 0e3c2a2..6384bb5 100644
--- a/app/addons/documents/__tests__/json-view.test.js
+++ b/app/addons/documents/__tests__/json-view.test.js
@@ -13,10 +13,11 @@
 import { getJsonViewData } from '../index-results/helpers/json-view';
 import { getDocUrl } from '../index-results/helpers/shared-helpers';
 import '../base';
+import Constants from '../constants';
 
 describe('Docs JSON View', () => {
   const databaseName = 'testdb';
-  let typeOfIndex = 'view';
+  let docType = Constants.INDEX_RESULTS_DOC_TYPE.VIEW;
   const docs = [
     {
       id: "aardvark",
@@ -57,15 +58,19 @@ describe('Docs JSON View', () => {
       }
     }
   ];
+  const mangoIndexes = [
+    {ddoc: null, name: "_all_docs", type: "special", def: {fields: [{_id: "asc"}]}},
+    {ddoc: "_design/34223ecd7b6bcdc4dcdbc1a09bd63db365dd5f69", name: "idx1", type: "json", def: {fields: [{host3: "asc"}]}}
+  ];
   let testDocs;
 
   beforeEach(() => {
     testDocs = docs;
-    typeOfIndex = 'view';
+    docType = Constants.INDEX_RESULTS_DOC_TYPE.VIEW;
   });
 
   it('getJsonViewData returns proper meta object with vanilla inputs', () => {
-    expect(getJsonViewData(testDocs, {databaseName, typeOfIndex})).toEqual({
+    expect(getJsonViewData(testDocs, {databaseName, docType})).toEqual({
       displayedFields: null,
       hasBulkDeletableDoc: true,
       results: [
@@ -94,33 +99,41 @@ describe('Docs JSON View', () => {
   });
 
   it('getJsonViewData false hasBulkDeletableDoc when all special mango docs', () => {
-    typeOfIndex = 'MangoIndex';
+    docType = Constants.INDEX_RESULTS_DOC_TYPE.MANGO_INDEX;
+    testDocs = mangoIndexes;
     testDocs[0].type = 'special';
     testDocs[1].type = 'special';
 
-    expect(getJsonViewData(testDocs, {databaseName, typeOfIndex})).toEqual({
+    const idx0 = { ...testDocs[0] };
+    delete idx0.ddoc;
+    delete idx0.name;
+    const idx1 = { ...testDocs[1] };
+    delete idx1.ddoc;
+    delete idx1.name;
+
+    expect(getJsonViewData(testDocs, {databaseName, docType})).toEqual({
       displayedFields: null,
       hasBulkDeletableDoc: false,
       results: [
         {
-          content: JSON.stringify(testDocs[0], null, ' '),
-          id: testDocs[0].id,
-          _rev: testDocs[0].value.rev,
-          header: testDocs[0].id,
-          keylabel: 'id',
-          url: getDocUrl('app', testDocs[0].id, databaseName),
-          isDeletable: true,
-          isEditable: true
+          content: JSON.stringify(idx0, null, ' '),
+          id: mangoIndexes[0].name,
+          _rev: undefined,
+          header: 'special: _id',
+          keylabel: '',
+          url: null,
+          isDeletable: false,
+          isEditable: false
         },
         {
-          content: JSON.stringify(testDocs[1], null, ' '),
-          id: testDocs[1].id,
-          _rev: testDocs[1].value.rev,
-          header: testDocs[1].id,
-          keylabel: 'id',
-          url: getDocUrl('app', testDocs[1].id, databaseName),
-          isDeletable: true,
-          isEditable: true
+          content: JSON.stringify(idx1, null, ' '),
+          id: '_all_docs',
+          _rev: undefined,
+          header: 'special: host3',
+          keylabel: '',
+          url: null,
+          isDeletable: false,
+          isEditable: false
         }
       ]
     });
diff --git a/app/addons/documents/__tests__/pagination-api.test.js b/app/addons/documents/__tests__/pagination-actions.test.js
similarity index 98%
rename from app/addons/documents/__tests__/pagination-api.test.js
rename to app/addons/documents/__tests__/pagination-actions.test.js
index 75de7e6..2cb11c1 100644
--- a/app/addons/documents/__tests__/pagination-api.test.js
+++ b/app/addons/documents/__tests__/pagination-actions.test.js
@@ -17,7 +17,7 @@ import {
   incrementSkipForPageNext,
   decrementSkipForPagePrevious,
   resetPagination
-} from '../index-results/apis/pagination';
+} from '../index-results/actions/pagination';
 import ActionTypes from '../index-results/actiontypes';
 import FauxtonAPI from '../../../core/api';
 
diff --git a/app/addons/documents/__tests__/queryoptions-api.test.js b/app/addons/documents/__tests__/queryoptions-actions.test.js
similarity index 80%
rename from app/addons/documents/__tests__/queryoptions-api.test.js
rename to app/addons/documents/__tests__/queryoptions-actions.test.js
index 133feb2..0fa54f3 100644
--- a/app/addons/documents/__tests__/queryoptions-api.test.js
+++ b/app/addons/documents/__tests__/queryoptions-actions.test.js
@@ -10,13 +10,13 @@
 // License for the specific language governing permissions and limitations under
 // the License.
 
-import * as Api from '../index-results/apis/queryoptions';
+import * as Actions from '../index-results/actions/queryoptions';
 import ActionTypes from '../index-results/actiontypes';
 
 describe('Docs Query Options API', () => {
   it('resetFetchParamsBeforeExecute returns proper fetch params', () => {
     const perPage = 20;
-    expect(Api.resetFetchParamsBeforeExecute(perPage)).toEqual({
+    expect(Actions.resetFetchParamsBeforeExecute(perPage)).toEqual({
       limit: 21,
       skip: 0
     });
@@ -24,7 +24,7 @@ describe('Docs Query Options API', () => {
 
   it('queryOptionsToggleVisibility returns the proper event to dispatch', () => {
     const newVisibility = true;
-    expect(Api.queryOptionsToggleVisibility(newVisibility)).toEqual({
+    expect(Actions.queryOptionsToggleVisibility(newVisibility)).toEqual({
       type: ActionTypes.INDEX_RESULTS_REDUX_NEW_QUERY_OPTIONS,
       options: {
         isVisible: true
@@ -34,7 +34,7 @@ describe('Docs Query Options API', () => {
 
   it('queryOptionsToggleReduce returns the proper event to dispatch', () => {
     const previousReduce = true;
-    expect(Api.queryOptionsToggleReduce(previousReduce)).toEqual({
+    expect(Actions.queryOptionsToggleReduce(previousReduce)).toEqual({
       type: ActionTypes.INDEX_RESULTS_REDUX_NEW_QUERY_OPTIONS,
       options: {
         reduce: false
@@ -44,7 +44,7 @@ describe('Docs Query Options API', () => {
 
   it('queryOptionsUpdateGroupLevel returns the proper event to dispatch', () => {
     const newGroupLevel = 'exact';
-    expect(Api.queryOptionsUpdateGroupLevel(newGroupLevel)).toEqual({
+    expect(Actions.queryOptionsUpdateGroupLevel(newGroupLevel)).toEqual({
       type: ActionTypes.INDEX_RESULTS_REDUX_NEW_QUERY_OPTIONS,
       options: {
         groupLevel: 'exact'
@@ -54,7 +54,7 @@ describe('Docs Query Options API', () => {
 
   it('queryOptionsToggleByKeys returns the proper event to dispatch', () => {
     const previousShowByKeys = true;
-    expect(Api.queryOptionsToggleByKeys(previousShowByKeys)).toEqual({
+    expect(Actions.queryOptionsToggleByKeys(previousShowByKeys)).toEqual({
       type: ActionTypes.INDEX_RESULTS_REDUX_NEW_QUERY_OPTIONS,
       options: {
         showByKeys: false,
@@ -65,7 +65,7 @@ describe('Docs Query Options API', () => {
 
   it('queryOptionsToggleBetweenKeys returns the proper event to dispatch', () => {
     const previousShowBetweenKeys = true;
-    expect(Api.queryOptionsToggleBetweenKeys(previousShowBetweenKeys)).toEqual({
+    expect(Actions.queryOptionsToggleBetweenKeys(previousShowBetweenKeys)).toEqual({
       type: ActionTypes.INDEX_RESULTS_REDUX_NEW_QUERY_OPTIONS,
       options: {
         showBetweenKeys: false,
@@ -80,7 +80,7 @@ describe('Docs Query Options API', () => {
       startkey: '\"_design\"',
       endkey: '\"_design\"'
     };
-    expect(Api.queryOptionsUpdateBetweenKeys(newBetweenKeys)).toEqual({
+    expect(Actions.queryOptionsUpdateBetweenKeys(newBetweenKeys)).toEqual({
       type: ActionTypes.INDEX_RESULTS_REDUX_NEW_QUERY_OPTIONS,
       options: {
         betweenKeys: {
@@ -94,7 +94,7 @@ describe('Docs Query Options API', () => {
 
   it('queryOptionsUpdateByKeys returns the proper event to dispatch', () => {
     const newByKeys = ['foo', 'bar'];
-    expect(Api.queryOptionsUpdateByKeys(newByKeys)).toEqual({
+    expect(Actions.queryOptionsUpdateByKeys(newByKeys)).toEqual({
       type: ActionTypes.INDEX_RESULTS_REDUX_NEW_QUERY_OPTIONS,
       options: {
         byKeys: ['foo', 'bar']
@@ -104,7 +104,7 @@ describe('Docs Query Options API', () => {
 
   it('queryOptionsToggleDescending returns the proper event to dispatch', () => {
     const previousDescending = true;
-    expect(Api.queryOptionsToggleDescending(previousDescending)).toEqual({
+    expect(Actions.queryOptionsToggleDescending(previousDescending)).toEqual({
       type: ActionTypes.INDEX_RESULTS_REDUX_NEW_QUERY_OPTIONS,
       options: {
         descending: false
@@ -114,7 +114,7 @@ describe('Docs Query Options API', () => {
 
   it('queryOptionsUpdateSkip returns the proper event to dispatch', () => {
     const newSkip = 5;
-    expect(Api.queryOptionsUpdateSkip(newSkip)).toEqual({
+    expect(Actions.queryOptionsUpdateSkip(newSkip)).toEqual({
       type: ActionTypes.INDEX_RESULTS_REDUX_NEW_QUERY_OPTIONS,
       options: {
         skip: 5
@@ -124,7 +124,7 @@ describe('Docs Query Options API', () => {
 
   it('queryOptionsUpdateLimit returns the proper event to dispatch', () => {
     const newLimit = 50;
-    expect(Api.queryOptionsUpdateLimit(newLimit)).toEqual({
+    expect(Actions.queryOptionsUpdateLimit(newLimit)).toEqual({
       type: ActionTypes.INDEX_RESULTS_REDUX_NEW_QUERY_OPTIONS,
       options: {
         limit: 50
@@ -134,7 +134,7 @@ describe('Docs Query Options API', () => {
 
   it('queryOptionsToggleIncludeDocs returns the proper event to dispatch', () => {
     const previousIncludeDocs = true;
-    expect(Api.queryOptionsToggleIncludeDocs(previousIncludeDocs)).toEqual({
+    expect(Actions.queryOptionsToggleIncludeDocs(previousIncludeDocs)).toEqual({
       type: ActionTypes.INDEX_RESULTS_REDUX_NEW_QUERY_OPTIONS,
       options: {
         includeDocs: false
@@ -143,7 +143,7 @@ describe('Docs Query Options API', () => {
   });
 
   it('queryOptionsFilterOnlyDdocs returns the proper event to dispatch', () => {
-    expect(Api.queryOptionsFilterOnlyDdocs()).toEqual({
+    expect(Actions.queryOptionsFilterOnlyDdocs()).toEqual({
       type: ActionTypes.INDEX_RESULTS_REDUX_NEW_QUERY_OPTIONS,
       options: {
         betweenKeys: {
diff --git a/app/addons/documents/__tests__/reducers.test.js b/app/addons/documents/__tests__/reducers.test.js
index 9f79a12..1257300 100644
--- a/app/addons/documents/__tests__/reducers.test.js
+++ b/app/addons/documents/__tests__/reducers.test.js
@@ -27,7 +27,7 @@ describe('Docs Reducers', () => {
     isEditable: true,  // can the user manipulate the results returned?
     selectedLayout: Constants.LAYOUT_ORIENTATION.METADATA,
     textEmptyIndex: 'No Documents Found',
-    typeOfIndex: 'view',
+    docType: Constants.INDEX_RESULTS_DOC_TYPE.VIEW,
     fetchParams: {
       limit: FauxtonAPI.constants.MISC.DEFAULT_PAGE_SIZE + 1,
       skip: 0
@@ -58,7 +58,7 @@ describe('Docs Reducers', () => {
     }
   };
   const testDoc = {
-    _id: 'foo',
+    id: 'foo',
     key: 'foo',
     value: {
       rev: '1-967a00dff5e02add41819138abb3284d'
@@ -128,8 +128,8 @@ describe('Docs Reducers', () => {
     expect(Reducers.getTextEmptyIndex(initialState)).toMatch('No Documents Found');
   });
 
-  it('getTypeOfIndex returns the typeOfIndex attribute from the state', () => {
-    expect(Reducers.getTypeOfIndex(initialState)).toMatch('view');
+  it('getDocType returns the docType attribute from the state', () => {
+    expect(Reducers.getDocType(initialState)).toMatch(Constants.INDEX_RESULTS_DOC_TYPE.VIEW);
   });
 
   it('getFetchParams returns the fetchParams attribute from the state', () => {
@@ -288,7 +288,7 @@ describe('Docs Reducers', () => {
       expect(Reducers.getAllDocsSelected(newState)).toBe(false);
     });
 
-    it('returns true when all docs in the docs array are in the selectedDocs array', () => {
+    it('returns true when all selectable docs in the docs array are in the selectedDocs array', () => {
       const newDocAction = {
         type: ActionTypes.INDEX_RESULTS_REDUX_NEW_RESULTS,
         docs: [testDoc],
@@ -296,10 +296,11 @@ describe('Docs Reducers', () => {
           limit: 21,
           skip: 0
         },
-        canShowNext: true
+        canShowNext: true,
+        docType: Constants.INDEX_RESULTS_DOC_TYPE.VIEW
       };
-      const newState1 = Reducers.default(initialState, newDocAction);
 
+      const newState1 = Reducers.default(initialState, newDocAction);
       const selectedDoc = {
         _id: 'foo',
         _rev: '1-967a00dff5e02add41819138abb3284d',
@@ -310,7 +311,6 @@ describe('Docs Reducers', () => {
         selectedDocs: [selectedDoc]
       };
       const newState2 = Reducers.default(newState1, newSelectedDocAction);
-
       expect(Reducers.getAllDocsSelected(newState2)).toBe(true);
     });
   });
diff --git a/app/addons/documents/__tests__/results-toolbar.test.js b/app/addons/documents/__tests__/results-toolbar.test.js
index 7ce5331..77bccd6 100644
--- a/app/addons/documents/__tests__/results-toolbar.test.js
+++ b/app/addons/documents/__tests__/results-toolbar.test.js
@@ -9,6 +9,10 @@
 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 // License for the specific language governing permissions and limitations under
 // the License.
+
+import sinon from "sinon";
+import utils from "../../../../test/mocha/testUtils";
+import FauxtonAPI from "../../../core/api";
 import {ResultsToolBar} from "../components/results-toolbar";
 import React from 'react';
 import ReactDOM from 'react-dom';
@@ -23,6 +27,14 @@ describe('Results Toolbar', () => {
     isLoading: false
   };
 
+  beforeEach(() => {
+    sinon.stub(FauxtonAPI, 'urls').withArgs('new').returns('mock-url');
+  });
+
+  afterEach(() => {
+    utils.restore(FauxtonAPI.urls);
+  });
+
   it('renders all content when there are results and they are deletable', () => {
     const wrapper = mount(<ResultsToolBar hasResults={true} isListDeletable={true} {...restProps}/>);
     expect(wrapper.find('.bulk-action-component').length).toBe(1);
diff --git a/app/addons/documents/__tests__/shared-helpers.test.js b/app/addons/documents/__tests__/shared-helpers.test.js
index a5e479b..80308e5 100644
--- a/app/addons/documents/__tests__/shared-helpers.test.js
+++ b/app/addons/documents/__tests__/shared-helpers.test.js
@@ -14,10 +14,13 @@ import {
   getDocUrl,
   isJSONDocEditable,
   isJSONDocBulkDeletable,
-  hasBulkDeletableDoc
+  hasBulkDeletableDoc,
+  getDocId,
+  getDocRev
 } from '../index-results/helpers/shared-helpers';
 import FauxtonAPI from '../../../core/api';
 import '../base';
+import Constants from '../constants';
 import sinon from 'sinon';
 
 describe('Docs Shared Helpers', () => {
@@ -66,11 +69,11 @@ describe('Docs Shared Helpers', () => {
       class: "mammal",
       diet: "omnivore"
     };
-    let docType = 'view';
+    let docType = Constants.INDEX_RESULTS_DOC_TYPE.VIEW;
     let testDoc = Object.assign({}, doc);
 
     afterEach(() => {
-      docType = 'view';
+      docType = Constants.INDEX_RESULTS_DOC_TYPE.VIEW;
       testDoc = Object.assign({}, doc);
     });
 
@@ -80,7 +83,7 @@ describe('Docs Shared Helpers', () => {
     });
 
     it('returns false when type is MangoIndex', () => {
-      docType = 'MangoIndex';
+      docType = Constants.INDEX_RESULTS_DOC_TYPE.MANGO_INDEX;
       expect(isJSONDocEditable(testDoc, docType)).toBe(false);
     });
 
@@ -90,7 +93,7 @@ describe('Docs Shared Helpers', () => {
     });
 
     it('returns false if the doc does not have an _id', () => {
-      delete(testDoc._id);
+      delete (testDoc._id);
       expect(isJSONDocEditable(testDoc, docType)).toBe(false);
     });
 
@@ -112,12 +115,12 @@ describe('Docs Shared Helpers', () => {
       class: "mammal",
       diet: "omnivore"
     };
-    let docType = 'view';
+    let docType = Constants.INDEX_RESULTS_DOC_TYPE.VIEW;
     let testDoc = Object.assign({}, doc);
 
     afterEach(() => {
       testDoc = Object.assign({}, doc);
-      docType = 'view';
+      docType = Constants.INDEX_RESULTS_DOC_TYPE.VIEW;
     });
 
     it('returns true for normal doc and views', () => {
@@ -125,18 +128,18 @@ describe('Docs Shared Helpers', () => {
     });
 
     it('returns false if mango index and doc has type of special', () => {
-      docType = 'MangoIndex';
+      docType = Constants.INDEX_RESULTS_DOC_TYPE.MANGO_INDEX;
       testDoc.type = 'special';
       expect(isJSONDocBulkDeletable(testDoc, docType)).toBe(false);
     });
 
     it('returns false if doc does not have _id or id', () => {
-      delete(testDoc._id);
+      delete (testDoc._id);
       expect(isJSONDocBulkDeletable(testDoc, docType)).toBe(false);
     });
 
     it('returns false if doc does not have _rev or doc.value.rev', () => {
-      delete(testDoc._rev);
+      delete (testDoc._rev);
       expect(isJSONDocBulkDeletable(testDoc, docType)).toBe(false);
     });
   });
@@ -156,7 +159,7 @@ describe('Docs Shared Helpers', () => {
         diet: "omnivore"
       }
     ];
-    let docType = 'MangoIndex';
+    const docType = Constants.INDEX_RESULTS_DOC_TYPE.MANGO_INDEX;
 
     it('returns true if any docs are bulk deletable', () => {
       expect(hasBulkDeletableDoc(docs, docType)).toBe(true);
@@ -167,4 +170,98 @@ describe('Docs Shared Helpers', () => {
       expect(hasBulkDeletableDoc(docs, docType)).toBe(false);
     });
   });
+
+  describe('getDocId', () => {
+    it('returns correct ID for docType "view"', () => {
+      const docView = {
+        id: "20c76d4ff9851694792654ab3e2ca303",
+        key: "20c76d4ff9851694792654ab3e2ca303",
+        value: {
+          rev: "1-c59f5770929653147ab939344b84e933"
+        }
+      };
+      const docType = Constants.INDEX_RESULTS_DOC_TYPE.VIEW;
+      expect(getDocId(docView, docType)).toBe(docView.id);
+    });
+
+    it('returns correct ID for docType "MangoQueryResult"', () => {
+      const docMangoResult = {
+        _id: "aardvark",
+        _rev: "5-717f5e88689af3ad191b47321de10c95"
+      };
+      const docType = Constants.INDEX_RESULTS_DOC_TYPE.MANGO_QUERY;
+      expect(getDocId(docMangoResult, docType)).toBe(docMangoResult._id);
+    });
+
+    it('returns _all_docs as ID for special Mango index', () => {
+      const docSpecialMangoIndex = {
+        "ddoc": null,
+        "name": "_all_docs",
+        "type": "special",
+        "def": {
+          "fields": [{ "_id": "asc" }]
+        }
+      };
+      const docType = Constants.INDEX_RESULTS_DOC_TYPE.MANGO_INDEX;
+      expect(getDocId(docSpecialMangoIndex, docType)).toBe('_all_docs');
+    });
+
+    it('returns design doc ID as ID for Mango indexes', () => {
+      const docMangoIndex = {
+        ddoc: "_design/a7ee061f1a2c0c6882258b2f1e148b714e79ccea",
+        name: "a7ee061f1a2c0c6882258b2f1e148b714e79ccea",
+        type: "json",
+        def: { "fields": [{ "foo": "asc" }] }
+      };
+      const docType = Constants.INDEX_RESULTS_DOC_TYPE.MANGO_INDEX;
+      expect(getDocId(docMangoIndex, docType)).toBe(docMangoIndex.ddoc);
+    });
+  });
+
+  describe('getDocRev', () => {
+    it('returns document revision for docType "view"', () => {
+      const docView = {
+        id: "20c76d4ff9851694792654ab3e2ca303",
+        key: "20c76d4ff9851694792654ab3e2ca303",
+        value: {
+          rev: "1-c59f5770929653147ab939344b84e933"
+        }
+      };
+      const docType = Constants.INDEX_RESULTS_DOC_TYPE.VIEW;
+      expect(getDocRev(docView, docType)).toBe(docView.value.rev);
+    });
+
+    it('returns document revision for docType "MangoQueryResult"', () => {
+      const docMangoResult = {
+        _id: "aardvark",
+        _rev: "5-717f5e88689af3ad191b47321de10c95"
+      };
+      const docType = Constants.INDEX_RESULTS_DOC_TYPE.MANGO_QUERY;
+      expect(getDocRev(docMangoResult, docType)).toBe(docMangoResult._rev);
+    });
+
+    it('returns undefined as revision for special Mango index', () => {
+      const docSpecialMangoIndex = {
+        "ddoc": null,
+        "name": "_all_docs",
+        "type": "special",
+        "def": {
+          "fields": [{ "_id": "asc" }]
+        }
+      };
+      const docType = Constants.INDEX_RESULTS_DOC_TYPE.MANGO_INDEX;
+      expect(getDocRev(docSpecialMangoIndex, docType)).toBe(undefined);
+    });
+
+    it('returns undefined as revision for Mango indexes', () => {
+      const docMangoIndex = {
+        ddoc: "_design/a7ee061f1a2c0c6882258b2f1e148b714e79ccea",
+        name: "a7ee061f1a2c0c6882258b2f1e148b714e79ccea",
+        type: "json",
+        def: { "fields": [{ "foo": "asc" }] }
+      };
+      const docType = Constants.INDEX_RESULTS_DOC_TYPE.MANGO_INDEX;
+      expect(getDocRev(docMangoIndex, docType)).toBe(undefined);
+    });
+  });
 });
diff --git a/app/addons/documents/assets/less/view-editor.less b/app/addons/documents/assets/less/view-editor.less
index cec910e..ddfb6ce 100644
--- a/app/addons/documents/assets/less/view-editor.less
+++ b/app/addons/documents/assets/less/view-editor.less
@@ -58,6 +58,7 @@
   a.edit-link {
     float: right;
     margin-right: 5px;
+    cursor: pointer;
   }
   .help-link {
     margin-left: 4px;
diff --git a/app/addons/documents/base.js b/app/addons/documents/base.js
index 98d2b48..93534c1 100644
--- a/app/addons/documents/base.js
+++ b/app/addons/documents/base.js
@@ -14,10 +14,12 @@ import app from "../../app";
 import FauxtonAPI from "../../core/api";
 import Documents from "./routes";
 import reducers from "./index-results/reducers";
+import mangoReducers from "./mango/mango.reducers";
 import "./assets/less/documents.less";
 
 FauxtonAPI.addReducers({
-  indexResults: reducers
+  indexResults: reducers,
+  mangoQuery: mangoReducers
 });
 
 function getQueryParam (query) {
@@ -200,6 +202,10 @@ FauxtonAPI.registerUrls('mango', {
     return 'database/' + db + '/_index' + query;
   },
 
+  'index-server-bulk-delete': function (db) {
+    return app.host + '/' + encodeURIComponent(db) + '/_index/_bulk_delete';
+  },
+
   'query-server': function (db, query) {
     if (!query) {
       query = '';
@@ -225,11 +231,11 @@ FauxtonAPI.registerUrls('mango', {
   },
 
   'explain-server': function (db) {
-    return app.host + '/' + db + '/_explain';
+    return app.host + '/' + app.utils.safeURLName(db) + '/_explain';
   },
 
   'explain-apiurl': function (db) {
-    return window.location.origin + '/' + db + '/_explain';
+    return window.location.origin + '/' + app.utils.safeURLName(db) + '/_explain';
   }
 });
 
diff --git a/app/addons/documents/components/header-docs-right.js b/app/addons/documents/components/header-docs-right.js
index 4f03e50..63a5dfe 100644
--- a/app/addons/documents/components/header-docs-right.js
+++ b/app/addons/documents/components/header-docs-right.js
@@ -18,20 +18,22 @@ import Actions from './actions';
 
 const { QueryOptionsController } = QueryOptions;
 
-const getQueryOptionsComponent = (hideQueryOptions, isRedux, fetchUrl, ddocsOnly) => {
+const getQueryOptionsComponent = (hideQueryOptions, isRedux, queryDocs, ddocsOnly) => {
   if (hideQueryOptions) {
     return null;
   }
 
   let queryOptionsComponent = <QueryOptionsController />;
   if (isRedux) {
-    queryOptionsComponent = <QueryOptionsContainer fetchUrl={fetchUrl} ddocsOnly={ddocsOnly} />;
+    queryOptionsComponent = <QueryOptionsContainer
+      ddocsOnly={ddocsOnly}
+      queryDocs={ queryDocs } />;
   }
 
   return queryOptionsComponent;
 };
 
-const RightAllDocsHeader = ({database, hideQueryOptions, isRedux, fetchUrl, ddocsOnly}) =>
+const RightAllDocsHeader = ({database, hideQueryOptions, isRedux, queryDocs, ddocsOnly}) =>
   <div className="header-right right-db-header flex-layout flex-row">
 
     <div className="faux-header__searchboxwrapper">
@@ -39,11 +41,14 @@ const RightAllDocsHeader = ({database, hideQueryOptions, isRedux, fetchUrl, ddoc
         <JumpToDoc cache={false} loadOptions={Actions.fetchAllDocsWithKey(database)} database={database} />
       </div>
     </div>
-    {getQueryOptionsComponent(hideQueryOptions, isRedux, fetchUrl, ddocsOnly)}
+    {getQueryOptionsComponent(hideQueryOptions, isRedux, queryDocs, ddocsOnly)}
   </div>;
 
 RightAllDocsHeader.propTypes = {
-  database: React.PropTypes.object.isRequired
+  database: React.PropTypes.object.isRequired,
+  hideQueryOptions: React.PropTypes.bool,
+  isRedux: React.PropTypes.bool,
+  queryDocs: React.PropTypes.func
 };
 
 RightAllDocsHeader.defaultProps = {
diff --git a/app/addons/documents/constants.js b/app/addons/documents/constants.js
index a7063d3..3d9f33b 100644
--- a/app/addons/documents/constants.js
+++ b/app/addons/documents/constants.js
@@ -15,5 +15,11 @@ export default {
     TABLE: 'LAYOUT_TABLE',
     METADATA: 'LAYOUT_METADATA',
     JSON: 'LAYOUT_JSON'
+  },
+  // Document types that can be displayed by Index Results components
+  INDEX_RESULTS_DOC_TYPE: {
+    MANGO_INDEX: 'MangoIndex',
+    MANGO_QUERY: 'MangoQueryResult',
+    VIEW: 'view'
   }
 };
diff --git a/app/addons/documents/header/header.js b/app/addons/documents/header/header.js
index 0217c65..4479220 100644
--- a/app/addons/documents/header/header.js
+++ b/app/addons/documents/header/header.js
@@ -30,8 +30,8 @@ export default class BulkDocumentHeaderController extends React.Component {
     return {
       selectedLayout: indexResultsStore.getSelectedLayout(),
       bulkDocCollection: indexResultsStore.getBulkDocCollection(),
-      isMango: indexResultsStore.getIsMangoResults(),
-      isMangoIndexList: indexResultsStore.getIsMangoIndexResults()
+      //TODO: Replace below with a test for docType
+      isMango: indexResultsStore.getIsMangoResults()
     };
   }
 
@@ -54,7 +54,7 @@ export default class BulkDocumentHeaderController extends React.Component {
     const {
       changeLayout,
       selectedLayout,
-      typeOfIndex
+      docType
     } = this.props;
 
     // If the changeLayout function is not undefined, default to using prop values
@@ -62,14 +62,14 @@ export default class BulkDocumentHeaderController extends React.Component {
     // TODO: migrate completely to redux and eliminate this check.
     const layout = changeLayout ? selectedLayout : this.state.selectedLayout;
     let metadata, json, table;
-    if ((typeOfIndex && typeOfIndex === 'view') || !this.state.isMango) {
+    if ((docType === Constants.INDEX_RESULTS_DOC_TYPE.VIEW)) {
       metadata = <Button
           className={layout === Constants.LAYOUT_ORIENTATION.METADATA ? 'active' : ''}
           onClick={this.toggleLayout.bind(this, Constants.LAYOUT_ORIENTATION.METADATA)}
         >
           Metadata
         </Button>;
-    } else if (this.state.isMangoIndexList) {
+    } else if ((docType === Constants.INDEX_RESULTS_DOC_TYPE.MANGO_INDEX)) {
       return null;
     }
 
@@ -108,7 +108,7 @@ export default class BulkDocumentHeaderController extends React.Component {
     const {
       changeLayout,
       selectedLayout,
-      fetchAllDocs,
+      fetchDocs,
       fetchParams,
       queryOptionsParams,
       queryOptionsToggleIncludeDocs
@@ -127,7 +127,7 @@ export default class BulkDocumentHeaderController extends React.Component {
 
       // tell the query options panel we're updating include_docs
       queryOptionsToggleIncludeDocs(!queryOptionsParams.include_docs);
-      fetchAllDocs(fetchParams, queryOptionsParams);
+      fetchDocs(fetchParams, queryOptionsParams);
       return;
     }
 
diff --git a/app/addons/documents/index-results/actions.js b/app/addons/documents/index-results/actions.js
index c5e2a25..554d101 100644
--- a/app/addons/documents/index-results/actions.js
+++ b/app/addons/documents/index-results/actions.js
@@ -52,7 +52,7 @@ export default {
       FauxtonAPI.addNotification({
         msg: msg,
         clear:  false,
-        type: 'error'
+        type: 'info'
       });
     }
   },
diff --git a/app/addons/documents/index-results/apis/base.js b/app/addons/documents/index-results/actions/base.js
similarity index 86%
rename from app/addons/documents/index-results/apis/base.js
rename to app/addons/documents/index-results/actions/base.js
index c0ce4c5..7a0d2fd 100644
--- a/app/addons/documents/index-results/apis/base.js
+++ b/app/addons/documents/index-results/actions/base.js
@@ -11,6 +11,7 @@
 // the License.
 
 import ActionTypes from '../actiontypes';
+import { getDocId, getDocRev, isJSONDocBulkDeletable } from "../helpers/shared-helpers";
 
 export const nowLoading = () => {
   return {
@@ -24,12 +25,13 @@ export const resetState = () => {
   };
 };
 
-export const newResultsAvailable = (docs, params, canShowNext) => {
+export const newResultsAvailable = (docs, params, canShowNext, docType) => {
   return {
     type: ActionTypes.INDEX_RESULTS_REDUX_NEW_RESULTS,
     docs: docs,
     params: params,
-    canShowNext: canShowNext
+    canShowNext: canShowNext,
+    docType: docType
   };
 };
 
@@ -60,11 +62,14 @@ export const selectDoc = (doc, selectedDocs) => {
   return newSelectedDocs(selectedDocs);
 };
 
-export const bulkCheckOrUncheck = (docs, selectedDocs, allDocumentsSelected) => {
+export const bulkCheckOrUncheck = (docs, selectedDocs, allDocumentsSelected, docType) => {
   docs.forEach((doc) => {
+    if (!isJSONDocBulkDeletable(doc, docType)) {
+      return;
+    }
     // find the index of the doc in the selectedDocs array
     const indexInSelectedDocs = selectedDocs.findIndex((selectedDoc) => {
-      return (doc._id || doc.id) === selectedDoc._id;
+      return getDocId(doc, docType) === selectedDoc._id;
     });
 
     // remove the doc if we know all the documents are currently selected
@@ -73,8 +78,8 @@ export const bulkCheckOrUncheck = (docs, selectedDocs, allDocumentsSelected) =>
     // otherwise, add the doc if it doesn't exist in the selectedDocs array
     } else if (indexInSelectedDocs === -1) {
       selectedDocs.push({
-        _id: doc._id || doc.id,
-        _rev: doc._rev || doc.rev || doc.value.rev,
+        _id: getDocId(doc, docType),
+        _rev: getDocRev(doc, docType),
         _deleted: true
       });
     }
diff --git a/app/addons/documents/index-results/apis/fetch.js b/app/addons/documents/index-results/actions/fetch.js
similarity index 69%
rename from app/addons/documents/index-results/apis/fetch.js
rename to app/addons/documents/index-results/actions/fetch.js
index 7aada7e..973c64c 100644
--- a/app/addons/documents/index-results/apis/fetch.js
+++ b/app/addons/documents/index-results/actions/fetch.js
@@ -10,12 +10,13 @@
 // License for the specific language governing permissions and limitations under
 // the License.
 
-import 'url-polyfill';
-import 'whatwg-fetch';
 import FauxtonAPI from '../../../../core/api';
-import queryString from 'query-string';
 import SidebarActions from '../../sidebar/actions';
-import { nowLoading, newResultsAvailable, newSelectedDocs } from './base';
+import Constants from '../../constants';
+import { errorReason } from '../helpers/shared-helpers';
+import * as IndexResultsAPI from '../api';
+import { nowLoading, newResultsAvailable, newSelectedDocs,
+  changeLayout, resetState } from './base';
 
 const maxDocLimit = 10000;
 
@@ -78,10 +79,10 @@ export const removeOverflowDocsAndCalculateHasNext = (docs, totalDocsRemaining,
 
 // All the business logic for fetching docs from couch.
 // Arguments:
-// - fetchUrl -> the endpoint to fetch from
+// - queryDocs -> a function that fetches the docs. Accepts fetch params and returns a Promise of {docs[], docType}.
 // - fetchParams -> the internal params fauxton uses to emulate pagination
 // - queryOptionsParams -> manual query params entered by user
-export const fetchAllDocs = (fetchUrl, fetchParams, queryOptionsParams) => {
+export const fetchDocs = (queryDocs, fetchParams, queryOptionsParams) => {
   const { params, totalDocsRemaining } = mergeParams(fetchParams, queryOptionsParams);
   params.limit = Math.min(params.limit, maxDocLimit);
 
@@ -90,32 +91,30 @@ export const fetchAllDocs = (fetchUrl, fetchParams, queryOptionsParams) => {
     dispatch(nowLoading());
 
     // now fetch the results
-    return queryEndpoint(fetchUrl, params).then((docs) => {
+    return queryDocs(params).then(({ docs, docType }) => {
       const {
         finalDocList,
         canShowNext
       } = removeOverflowDocsAndCalculateHasNext(docs, totalDocsRemaining, params.limit);
 
+      if (docType === Constants.INDEX_RESULTS_DOC_TYPE.MANGO_INDEX) {
+        dispatch(changeLayout(Constants.LAYOUT_ORIENTATION.JSON));
+      }
       // dispatch that we're all done
-      dispatch(newResultsAvailable(finalDocList, params, canShowNext));
+      dispatch(newResultsAvailable(finalDocList, params, canShowNext, docType));
+    }).catch((error) => {
+      FauxtonAPI.addNotification({
+        msg: 'Error running query. ' + errorReason(error),
+        type: 'error',
+        clear: true
+      });
+      dispatch(resetState());
     });
   };
 };
 
-export const queryEndpoint = (fetchUrl, params) => {
-  const query = queryString.stringify(params);
-  const url = `${fetchUrl}${fetchUrl.includes('?') ? '&' : '?'}${query}`;
-  return fetch(url, {
-    credentials: 'include',
-    headers: {
-      'Accept': 'application/json; charset=utf-8'
-    }
-  })
-  .then(res => res.json())
-  .then(res => res.error ? [] : res.rows);
-};
 
-export const errorMessage = (ids) => {
+export const deleteErrorMessage = (ids) => {
   let msg = 'Failed to delete your document!';
 
   if (ids) {
@@ -147,53 +146,54 @@ export const validateBulkDelete = (docs) => {
   return true;
 };
 
-export const bulkDeleteDocs = (databaseName, fetchUrl, docs, designDocs, fetchParams, queryOptionsParams) => {
+export const bulkDeleteDocs = (databaseName, queryDocs, docs, designDocs, fetchParams, queryOptionsParams, docType) => {
   if (!validateBulkDelete(docs)) {
     return false;
   }
 
   return (dispatch) => {
-    const payload = {
-      docs: docs
-    };
-
-    return postToBulkDocs(databaseName, payload).then((res) => {
+    let postPromise, payload;
+    if (docType === Constants.INDEX_RESULTS_DOC_TYPE.MANGO_INDEX) {
+      payload = { docids: docs.map(doc => doc._id) };
+      postPromise = IndexResultsAPI.postToIndexBulkDelete(databaseName, payload);
+    } else if (docType === Constants.INDEX_RESULTS_DOC_TYPE.VIEW
+        || docType === Constants.INDEX_RESULTS_DOC_TYPE.MANGO_QUERY) {
+      payload = { docs: docs };
+      postPromise = IndexResultsAPI.postToBulkDocs(databaseName, payload);
+    } else {
+      throw new Error('Invalid document type: ' + docType);
+    }
+    return postPromise.then((res) => {
       if (res.error) {
-        errorMessage();
+        deleteErrorMessage();
         return;
       }
-      processBulkDeleteResponse(res, docs, designDocs);
+      processBulkDeleteResponse(res, docs, designDocs, docType);
       dispatch(newSelectedDocs());
-      dispatch(fetchAllDocs(fetchUrl, fetchParams, queryOptionsParams));
+      dispatch(fetchDocs(queryDocs, fetchParams, queryOptionsParams));
     });
   };
 };
 
-export const postToBulkDocs = (databaseName, payload) => {
-  const url = FauxtonAPI.urls('bulk_docs', 'server', databaseName);
-  return fetch(url, {
-    method: 'POST',
-    credentials: 'include',
-    body: JSON.stringify(payload),
-    headers: {
-      'Accept': 'application/json; charset=utf-8',
-      'Content-Type': 'application/json'
-    }
-  })
-  .then(res => res.json());
-};
-
-export const processBulkDeleteResponse = (res, originalDocs, designDocs) => {
+export const processBulkDeleteResponse = (res, deletedDocs, designDocs, docType) => {
   FauxtonAPI.addNotification({
     msg: 'Successfully deleted your docs',
     clear:  true
   });
 
-  const failedDocs = res.filter(doc => !!doc.error).map(doc => doc.id);
-  const hasDesignDocs = !!originalDocs.map(d => d._id).find((_id) => /_design/.test(_id));
+  let failedDocs = [];
+  if (docType === Constants.INDEX_RESULTS_DOC_TYPE.MANGO_INDEX) {
+    if (res.fail) {
+      failedDocs = res.fail.map(doc => doc.id);
+    }
+  } else {
+    failedDocs = res.filter(doc => !!doc.error).map(doc => doc.id);
+  }
+
+  const hasDesignDocs = !!deletedDocs.map(d => d._id).find((_id) => /_design/.test(_id));
 
   if (failedDocs.length > 0) {
-    errorMessage(failedDocs);
+    deleteErrorMessage(failedDocs);
   }
 
   if (designDocs && hasDesignDocs) {
diff --git a/app/addons/documents/index-results/apis/pagination.js b/app/addons/documents/index-results/actions/pagination.js
similarity index 81%
rename from app/addons/documents/index-results/apis/pagination.js
rename to app/addons/documents/index-results/actions/pagination.js
index 366f9e7..8843e20 100644
--- a/app/addons/documents/index-results/apis/pagination.js
+++ b/app/addons/documents/index-results/actions/pagination.js
@@ -11,7 +11,7 @@
 // the License.
 
 import FauxtonAPI from '../../../../core/api';
-import { fetchAllDocs } from './fetch';
+import { fetchDocs } from './fetch';
 import ActionTypes from '../actiontypes';
 
 export const toggleShowAllColumns = () => {
@@ -34,7 +34,7 @@ export const resetFetchParamsBeforePerPageChange = (fetchParams, queryOptionsPar
   });
 };
 
-export const updatePerPageResults = (databaseName, fetchParams, queryOptionsParams, amount) => {
+export const updatePerPageResults = (queryDocs, fetchParams, queryOptionsParams, amount) => {
   // Set the query limit to the perPage + 1 so we know if there is
   // a next page.  We also need to reset to the beginning of all
   // possible pages since our logic to paginate backwards can't handle
@@ -43,7 +43,7 @@ export const updatePerPageResults = (databaseName, fetchParams, queryOptionsPara
 
   return (dispatch) => {
     dispatch(setPerPage(amount));
-    dispatch(fetchAllDocs(databaseName, fetchParams, queryOptionsParams));
+    dispatch(fetchDocs(queryDocs, fetchParams, queryOptionsParams));
   };
 };
 
@@ -53,7 +53,7 @@ export const incrementSkipForPageNext = (fetchParams, perPage) => {
   });
 };
 
-export const paginateNext = (databaseName, fetchParams, queryOptionsParams, perPage) => {
+export const paginateNext = (queryDocs, fetchParams, queryOptionsParams, perPage) => {
   // add the perPage to the previous skip.
   fetchParams = incrementSkipForPageNext(fetchParams, perPage);
 
@@ -61,7 +61,7 @@ export const paginateNext = (databaseName, fetchParams, queryOptionsParams, perP
     dispatch({
       type: ActionTypes.INDEX_RESULTS_REDUX_PAGINATE_NEXT
     });
-    dispatch(fetchAllDocs(databaseName, fetchParams, queryOptionsParams));
+    dispatch(fetchDocs(queryDocs, fetchParams, queryOptionsParams));
   };
 };
 
@@ -71,7 +71,7 @@ export const decrementSkipForPagePrevious = (fetchParams, perPage) => {
   });
 };
 
-export const paginatePrevious = (databaseName, fetchParams, queryOptionsParams, perPage) => {
+export const paginatePrevious = (queryDocs, fetchParams, queryOptionsParams, perPage) => {
   // subtract the perPage to the previous skip.
   fetchParams = decrementSkipForPagePrevious(fetchParams, perPage);
 
@@ -79,7 +79,7 @@ export const paginatePrevious = (databaseName, fetchParams, queryOptionsParams,
     dispatch({
       type: ActionTypes.INDEX_RESULTS_REDUX_PAGINATE_PREVIOUS
     });
-    dispatch(fetchAllDocs(databaseName, fetchParams, queryOptionsParams));
+    dispatch(fetchDocs(queryDocs, fetchParams, queryOptionsParams));
   };
 };
 
diff --git a/app/addons/documents/index-results/apis/queryoptions.js b/app/addons/documents/index-results/actions/queryoptions.js
similarity index 93%
rename from app/addons/documents/index-results/apis/queryoptions.js
rename to app/addons/documents/index-results/actions/queryoptions.js
index 94e1620..b419103 100644
--- a/app/addons/documents/index-results/apis/queryoptions.js
+++ b/app/addons/documents/index-results/actions/queryoptions.js
@@ -11,7 +11,7 @@
 // the License.
 
 import ActionTypes from '../actiontypes';
-import { fetchAllDocs } from './fetch';
+import { fetchDocs } from './fetch';
 
 const updateQueryOptions = (queryOptions) => {
   return {
@@ -27,9 +27,9 @@ export const resetFetchParamsBeforeExecute = (perPage) => {
   };
 };
 
-export const queryOptionsExecute = (fetchUrl, queryOptionsParams, perPage) => {
+export const queryOptionsExecute = (queryDocs, queryOptionsParams, perPage) => {
   const fetchParams = resetFetchParamsBeforeExecute(perPage);
-  return fetchAllDocs(fetchUrl, fetchParams, queryOptionsParams);
+  return fetchDocs(queryDocs, fetchParams, queryOptionsParams);
 };
 
 export const queryOptionsToggleVisibility = (newVisibility) => {
diff --git a/app/addons/documents/index-results/api.js b/app/addons/documents/index-results/api.js
new file mode 100644
index 0000000..d285363
--- /dev/null
+++ b/app/addons/documents/index-results/api.js
@@ -0,0 +1,64 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+import 'url-polyfill';
+import 'whatwg-fetch';
+import queryString from 'query-string';
+import Constants from '../constants';
+import FauxtonAPI from '../../../core/api';
+
+export const queryAllDocs = (fetchUrl, params) => {
+  const query = queryString.stringify(params);
+  const url = `${fetchUrl}${fetchUrl.includes('?') ? '&' : '?'}${query}`;
+  return fetch(url, {
+    credentials: 'include',
+    headers: {
+      'Accept': 'application/json; charset=utf-8'
+    }
+  })
+  .then(res => res.json())
+  .then(res => {
+    return {
+      //TODO: handle error situation
+      docs: res.error ? [] : res.rows,
+      docType: Constants.INDEX_RESULTS_DOC_TYPE.VIEW
+    };
+  });
+};
+
+export const postToBulkDocs = (databaseName, payload) => {
+  const url = FauxtonAPI.urls('bulk_docs', 'server', databaseName);
+  return fetch(url, {
+    method: 'POST',
+    credentials: 'include',
+    body: JSON.stringify(payload),
+    headers: {
+      'Accept': 'application/json; charset=utf-8',
+      'Content-Type': 'application/json'
+    }
+  })
+  .then(res => res.json());
+};
+
+export const postToIndexBulkDelete = (databaseName, payload) => {
+  const url = FauxtonAPI.urls('mango', 'index-server-bulk-delete', databaseName);
+  return fetch(url, {
+    method: 'POST',
+    credentials: 'include',
+    body: JSON.stringify(payload),
+    headers: {
+      'Accept': 'application/json; charset=utf-8',
+      'Content-Type': 'application/json'
+    }
+  })
+  .then(res => res.json());
+};
diff --git a/app/addons/documents/index-results/components/results/IndexResults.js b/app/addons/documents/index-results/components/results/IndexResults.js
index 58f17eb..e5b5cf1 100644
--- a/app/addons/documents/index-results/components/results/IndexResults.js
+++ b/app/addons/documents/index-results/components/results/IndexResults.js
@@ -20,25 +20,28 @@ export default class IndexResults extends React.Component {
 
   componentDidMount () {
     const {
-      fetchAllDocs,
+      fetchDocs,
       fetchParams,
       queryOptionsParams,
+      fetchAtStartup
     } = this.props;
 
-    // now get the docs!
-    fetchAllDocs(fetchParams, queryOptionsParams);
+    // Fetch docs if requested
+    if (fetchAtStartup) {
+      fetchDocs(fetchParams, queryOptionsParams);
+    }
   }
 
   componentWillUpdate (nextProps) {
     const {
-      fetchAllDocs,
+      fetchDocs,
       fetchParams,
       queryOptionsParams,
       ddocsOnly
     } = nextProps;
 
     if (this.props.ddocsOnly !== ddocsOnly) {
-      fetchAllDocs(fetchParams, queryOptionsParams);
+      fetchDocs(fetchParams, queryOptionsParams);
     }
   }
 
@@ -54,7 +57,6 @@ export default class IndexResults extends React.Component {
 
   isSelected (id) {
     const { selectedDocs } = this.props;
-
     // check whether this id exists in our array of selected docs
     return selectedDocs.findIndex((doc) => {
       return id === doc._id;
@@ -98,3 +100,7 @@ export default class IndexResults extends React.Component {
     );
   }
 };
+
+IndexResults.propTypes = {
+  fetchAtStartup: React.PropTypes.bool.isRequired
+};
diff --git a/app/addons/documents/index-results/components/results/ResultsScreen.js b/app/addons/documents/index-results/components/results/ResultsScreen.js
index 7975368..4cdb0f6 100644
--- a/app/addons/documents/index-results/components/results/ResultsScreen.js
+++ b/app/addons/documents/index-results/components/results/ResultsScreen.js
@@ -34,7 +34,9 @@ export default class ResultsScreen extends React.Component {
   }
 
   onClick (id, doc) {
-    FauxtonAPI.navigate(doc.url);
+    if (doc.url) {
+      FauxtonAPI.navigate(doc.url);
+    }
   }
 
   getUrlFragment (url) {
@@ -51,7 +53,6 @@ export default class ResultsScreen extends React.Component {
   getDocumentList () {
     let noop = () => {};
     let data = this.props.results.results;
-
     return _.map(data, function (doc, i) {
       return (
        <Document
diff --git a/app/addons/documents/index-results/components/results/TableView.js b/app/addons/documents/index-results/components/results/TableView.js
index 299ce71..580f2a0 100644
--- a/app/addons/documents/index-results/components/results/TableView.js
+++ b/app/addons/documents/index-results/components/results/TableView.js
@@ -23,7 +23,6 @@ export default class TableView extends React.Component {
     const data = this.props.data.results;
 
     return data.map(function (el, i) {
-
       return (
         <TableRow
           onClick={this.props.onClick}
diff --git a/app/addons/documents/index-results/containers/ApiBarContainer.js b/app/addons/documents/index-results/containers/ApiBarContainer.js
index 9767acb..145b6e8 100644
--- a/app/addons/documents/index-results/containers/ApiBarContainer.js
+++ b/app/addons/documents/index-results/containers/ApiBarContainer.js
@@ -10,8 +10,9 @@
 // License for the specific language governing permissions and limitations under
 // the License.
 
-import queryString from 'query-string';
+import React from 'react';
 import { connect } from 'react-redux';
+import queryString from 'query-string';
 import { ApiBarWrapper } from '../../../components/layouts';
 import { getQueryOptionsParams } from '../reducers';
 import FauxtonAPI from '../../../../core/api';
@@ -26,11 +27,22 @@ const urlRef = (databaseName, params) => {
   return FauxtonAPI.urls('allDocs', "apiurl", encodeURIComponent(databaseName), query);
 };
 
-const mapStateToProps = ({indexResults}, ownProps) => {
-  return {
-    docUrl: FauxtonAPI.constants.DOC_URLS.GENERAL,
-    endpoint: urlRef(ownProps.databaseName, getQueryOptionsParams(indexResults))
-  };
+const mapStateToProps = ({indexResults}, {docUrl, endpoint, databaseName}) => {
+  if (!docUrl) {
+    docUrl = FauxtonAPI.constants.DOC_URLS.GENERAL;
+  }
+  if (!endpoint) {
+    endpoint = urlRef(databaseName, getQueryOptionsParams(indexResults));
+  }
+  return { docUrl, endpoint };
 };
 
-export default connect (mapStateToProps)(ApiBarWrapper);
+const ApiBarContainer = connect (
+  mapStateToProps
+)(ApiBarWrapper);
+
+export default ApiBarContainer;
+
+ApiBarContainer.propTypes = {
+  databaseName: React.PropTypes.string
+};
diff --git a/app/addons/documents/index-results/containers/IndexResultsContainer.js b/app/addons/documents/index-results/containers/IndexResultsContainer.js
index d865401..fbb35d7 100644
--- a/app/addons/documents/index-results/containers/IndexResultsContainer.js
+++ b/app/addons/documents/index-results/containers/IndexResultsContainer.js
@@ -10,17 +10,18 @@
 // License for the specific language governing permissions and limitations under
 // the License.
 
+import React from 'react';
 import { connect } from 'react-redux';
 import IndexResults from '../components/results/IndexResults';
-import { fetchAllDocs, bulkDeleteDocs } from '../apis/fetch';
-import { queryOptionsToggleIncludeDocs } from '../apis/queryoptions';
+import { fetchDocs, bulkDeleteDocs } from '../actions/fetch';
+import { queryOptionsToggleIncludeDocs } from '../actions/queryoptions';
 import {
   selectDoc,
   changeLayout,
   bulkCheckOrUncheck,
   changeTableHeaderAttribute,
   resetState
-} from '../apis/base';
+} from '../actions/base';
 import {
   getDocs,
   getSelectedDocs,
@@ -33,7 +34,7 @@ import {
   getHasDocsSelected,
   getNumDocsSelected,
   getTextEmptyIndex,
-  getTypeOfIndex,
+  getDocType,
   getFetchParams,
   getQueryOptionsParams
 } from '../reducers';
@@ -52,7 +53,7 @@ const mapStateToProps = ({indexResults}, ownProps) => {
     hasSelectedItem: getHasDocsSelected(indexResults),
     numDocsSelected: getNumDocsSelected(indexResults),
     textEmptyIndex: getTextEmptyIndex(indexResults),
-    typeOfIndex: getTypeOfIndex(indexResults),
+    docType: getDocType(indexResults),
     fetchParams: getFetchParams(indexResults),
     queryOptionsParams: getQueryOptionsParams(indexResults)
   };
@@ -60,25 +61,26 @@ const mapStateToProps = ({indexResults}, ownProps) => {
 
 const mapDispatchToProps = (dispatch, ownProps) => {
   return {
-    fetchAllDocs: (fetchParams, queryOptionsParams) => {
-      dispatch(fetchAllDocs(ownProps.fetchUrl, fetchParams, queryOptionsParams));
+    fetchDocs: (fetchParams, queryOptionsParams) => {
+      dispatch(fetchDocs(ownProps.queryDocs, fetchParams, queryOptionsParams));
     },
     selectDoc: (doc, selectedDocs) => {
       dispatch(selectDoc(doc, selectedDocs));
     },
     bulkDeleteDocs: (docs, fetchParams, queryOptionsParams) => {
       dispatch(bulkDeleteDocs(ownProps.databaseName,
-                              ownProps.fetchUrl,
+                              ownProps.queryDocs,
                               docs,
                               ownProps.designDocs,
                               fetchParams,
-                              queryOptionsParams));
+                              queryOptionsParams,
+                              ownProps.docType));
     },
     changeLayout: (newLayout) => {
       dispatch(changeLayout(newLayout));
     },
     bulkCheckOrUncheck: (docs, selectedDocs, allDocumentsSelected) => {
-      dispatch(bulkCheckOrUncheck(docs, selectedDocs, allDocumentsSelected));
+      dispatch(bulkCheckOrUncheck(docs, selectedDocs, allDocumentsSelected, ownProps.docType));
     },
     changeTableHeaderAttribute: (newField, selectedFields) => {
       dispatch(changeTableHeaderAttribute(newField, selectedFields));
@@ -92,7 +94,14 @@ const mapDispatchToProps = (dispatch, ownProps) => {
   };
 };
 
-export default connect (
+const IndexResultsContainer = connect (
   mapStateToProps,
   mapDispatchToProps
 )(IndexResults);
+
+export default IndexResultsContainer;
+
+IndexResultsContainer.propTypes = {
+  queryDocs: React.PropTypes.func.isRequired,
+  docType: React.PropTypes.string.isRequired
+};
diff --git a/app/addons/documents/index-results/containers/PaginationContainer.js b/app/addons/documents/index-results/containers/PaginationContainer.js
index 27a7757..4077003 100644
--- a/app/addons/documents/index-results/containers/PaginationContainer.js
+++ b/app/addons/documents/index-results/containers/PaginationContainer.js
@@ -10,6 +10,7 @@
 // License for the specific language governing permissions and limitations under
 // the License.
 
+import React from 'react';
 import { connect } from 'react-redux';
 import PaginationFooter from '../components/pagination/PaginationFooter';
 import {
@@ -17,7 +18,7 @@ import {
   updatePerPageResults,
   paginateNext,
   paginatePrevious
-} from '../apis/pagination';
+} from '../actions/pagination';
 import {
   getDocs,
   getSelectedDocs,
@@ -59,18 +60,24 @@ const mapDispatchToProps = (dispatch, ownProps) => {
       dispatch(toggleShowAllColumns());
     },
     updatePerPageResults: (amount, fetchParams, queryOptionsParams) => {
-      dispatch(updatePerPageResults(ownProps.fetchUrl, fetchParams, queryOptionsParams, amount));
+      dispatch(updatePerPageResults(ownProps.queryDocs, fetchParams, queryOptionsParams, amount));
     },
     paginateNext: (fetchParams, queryOptionsParams, perPage) => {
-      dispatch(paginateNext(ownProps.fetchUrl, fetchParams, queryOptionsParams, perPage));
+      dispatch(paginateNext(ownProps.queryDocs, fetchParams, queryOptionsParams, perPage));
     },
     paginatePrevious: (fetchParams, queryOptionsParams, perPage) => {
-      dispatch(paginatePrevious(ownProps.fetchUrl, fetchParams, queryOptionsParams, perPage));
+      dispatch(paginatePrevious(ownProps.queryDocs, fetchParams, queryOptionsParams, perPage));
     }
   };
 };
 
-export default connect (
+const PaginationFooterContainer = connect (
   mapStateToProps,
   mapDispatchToProps
 )(PaginationFooter);
+
+export default PaginationFooterContainer;
+
+PaginationFooterContainer.propTypes = {
+  queryDocs: React.PropTypes.func.isRequired
+};
diff --git a/app/addons/documents/index-results/containers/QueryOptionsContainer.js b/app/addons/documents/index-results/containers/QueryOptionsContainer.js
index eb08ae2..9679e1e 100644
--- a/app/addons/documents/index-results/containers/QueryOptionsContainer.js
+++ b/app/addons/documents/index-results/containers/QueryOptionsContainer.js
@@ -10,10 +10,11 @@
 // License for the specific language governing permissions and limitations under
 // the License.
 
+import React from 'react';
 import { connect } from 'react-redux';
 import QueryOptions from '../components/queryoptions/QueryOptions';
-import { changeLayout, resetState } from '../apis/base';
-import { resetPagination } from '../apis/pagination';
+import { changeLayout, resetState } from '../actions/base';
+import { resetPagination } from '../actions/pagination';
 import {
   queryOptionsExecute,
   queryOptionsToggleReduce,
@@ -28,7 +29,7 @@ import {
   queryOptionsToggleIncludeDocs,
   queryOptionsToggleVisibility,
   queryOptionsFilterOnlyDdocs
-} from '../apis/queryoptions';
+} from '../actions/queryoptions';
 import {
   getQueryOptionsPanel,
   getFetchParams,
@@ -99,7 +100,7 @@ const mapDispatchToProps = (dispatch, ownProps) => {
       dispatch(queryOptionsToggleVisibility(newVisibility));
     },
     queryOptionsExecute: (queryOptionsParams, perPage) => {
-      dispatch(queryOptionsExecute(ownProps.fetchUrl, queryOptionsParams, perPage));
+      dispatch(queryOptionsExecute(ownProps.queryDocs, queryOptionsParams, perPage));
     },
     queryOptionsFilterOnlyDdocs: () => {
       dispatch(queryOptionsFilterOnlyDdocs());
@@ -113,7 +114,13 @@ const mapDispatchToProps = (dispatch, ownProps) => {
   };
 };
 
-export default connect (
+const QueryOptionsContainer = connect (
   mapStateToProps,
   mapDispatchToProps
 )(QueryOptions);
+
+export default QueryOptionsContainer;
+
+QueryOptionsContainer.propTypes = {
+  queryDocs: React.PropTypes.func.isRequired
+};
diff --git a/app/addons/documents/index-results/helpers/json-view.js b/app/addons/documents/index-results/helpers/json-view.js
index 89a04dd..9358162 100644
--- a/app/addons/documents/index-results/helpers/json-view.js
+++ b/app/addons/documents/index-results/helpers/json-view.js
@@ -10,18 +10,32 @@
 // License for the specific language governing permissions and limitations under
 // the License.
 
-import { hasBulkDeletableDoc, getDocUrl } from "./shared-helpers";
+import { hasBulkDeletableDoc, getDocUrl, getDocId, getDocRev } from "./shared-helpers";
+import MangoHelper from "../../mango/mango.helper";
 
-export const getJsonViewData = (docs, { databaseName, typeOfIndex }) => {
-  // expand on this when refactoring views and mango to use redux
+export const getJsonViewData = (docs, { databaseName, docType }) => {
+
+  // expand on this when refactoring views to use redux
   const stagedResults = docs.map((doc) => {
+    if (docType === "MangoIndex") {
+      return {
+        header: MangoHelper.getIndexName(doc),
+        content: MangoHelper.getIndexContent(doc),
+        id: doc.type === 'special' ? '_all_docs' : doc.ddoc,
+        keylabel: '',
+        url: doc.id ? getDocUrl('app', doc.id, databaseName) : null,
+        isDeletable: doc.type !== 'special',
+        isEditable: false
+      };
+    }
+    const docID = getDocId(doc, docType);
     return {
+      header: docID,
       content: JSON.stringify(doc, null, ' '),
-      id: doc.id, //|| doc.key.toString(),
-      _rev: doc._rev || (doc.value && doc.value.rev),
-      header: doc.id, //|| doc.key.toString(),
-      keylabel: 'id', //doc.isFromView() ? 'key' : 'id',
-      url: doc.id ? getDocUrl('app', doc.id, databaseName) : null,
+      id: docID || (doc.key && doc.key.toString()),
+      _rev: getDocRev(doc, docType),
+      keylabel: 'id',
+      url: docID ? getDocUrl('app', docID, databaseName) : null,
       isDeletable: true,
       isEditable: true
     };
@@ -29,7 +43,7 @@ export const getJsonViewData = (docs, { databaseName, typeOfIndex }) => {
 
   return {
     displayedFields: null,
-    hasBulkDeletableDoc: hasBulkDeletableDoc(docs, typeOfIndex),
+    hasBulkDeletableDoc: hasBulkDeletableDoc(docs, docType),
     results: stagedResults
   };
 };
diff --git a/app/addons/documents/index-results/helpers/shared-helpers.js b/app/addons/documents/index-results/helpers/shared-helpers.js
index b21142d..d1e8e63 100644
--- a/app/addons/documents/index-results/helpers/shared-helpers.js
+++ b/app/addons/documents/index-results/helpers/shared-helpers.js
@@ -12,6 +12,7 @@
 
 import app from "../../../../app";
 import FauxtonAPI from "../../../../core/api";
+import Constants from '../../constants';
 
 const getDocUrl = (context, id, databaseName) => {
   if (context === undefined) {
@@ -34,7 +35,7 @@ const isJSONDocEditable = (doc, docType) => {
     return;
   }
 
-  if (docType === 'MangoIndex') {
+  if (docType === Constants.INDEX_RESULTS_DOC_TYPE.MANGO_INDEX) {
     return false;
   }
 
@@ -50,7 +51,7 @@ const isJSONDocEditable = (doc, docType) => {
 };
 
 const isJSONDocBulkDeletable = (doc, docType) => {
-  if (docType === 'MangoIndex') {
+  if (docType === Constants.INDEX_RESULTS_DOC_TYPE.MANGO_INDEX) {
     return doc.type !== 'special';
   }
   const result = (doc._id || doc.id) && (doc._rev || (doc.value && doc.value.rev));
@@ -76,10 +77,46 @@ const getDefaultPerPage = () => {
   return FauxtonAPI.constants.MISC.DEFAULT_PAGE_SIZE;
 };
 
+const isGhostDoc = (doc) => {
+  // ghost docs are empty results where all properties were
+  // filtered away by mango
+  return !doc || !doc.attributes || !Object.keys(doc.attributes).length;
+};
+
+const getDocId = (doc, docType = Constants.INDEX_RESULTS_DOC_TYPE.VIEW) => {
+  if (docType === Constants.INDEX_RESULTS_DOC_TYPE.VIEW) {
+    return doc.id;
+  } else if (docType === Constants.INDEX_RESULTS_DOC_TYPE.MANGO_INDEX) {
+    return doc.type === 'special' ? '_all_docs' : doc.ddoc;
+  } else if (docType === Constants.INDEX_RESULTS_DOC_TYPE.MANGO_QUERY) {
+    return doc._id;
+  }
+  return doc.id || doc._id;
+};
+
+const getDocRev = (doc, docType = Constants.INDEX_RESULTS_DOC_TYPE.VIEW) => {
+  if (docType === Constants.INDEX_RESULTS_DOC_TYPE.VIEW) {
+    return doc.value && doc.value.rev;
+  } else if (docType === Constants.INDEX_RESULTS_DOC_TYPE.MANGO_INDEX) {
+    return undefined;
+  } else if (docType === Constants.INDEX_RESULTS_DOC_TYPE.MANGO_QUERY) {
+    return doc._rev;
+  }
+  return undefined;
+};
+
+const errorReason = (error) => {
+  return 'Reason: ' + ((error && error.message) || 'n/a');
+};
+
 export {
   getDocUrl,
+  isGhostDoc,
   isJSONDocEditable,
   isJSONDocBulkDeletable,
   hasBulkDeletableDoc,
-  getDefaultPerPage
+  getDefaultPerPage,
+  getDocId,
+  getDocRev,
+  errorReason
 };
diff --git a/app/addons/documents/index-results/helpers/table-view.js b/app/addons/documents/index-results/helpers/table-view.js
index 2bce347..c91adea 100644
--- a/app/addons/documents/index-results/helpers/table-view.js
+++ b/app/addons/documents/index-results/helpers/table-view.js
@@ -15,7 +15,9 @@ import {
     isJSONDocBulkDeletable,
     isJSONDocEditable,
     hasBulkDeletableDoc,
-    getDocUrl
+    getDocUrl,
+    getDocId,
+    getDocRev
  } from "./shared-helpers";
 
 export const getPseudoSchema = (docs) => {
@@ -155,20 +157,20 @@ export const getTableViewData = (docs, options) => {
   const res = normalizedDocs.map(function (doc) {
     return {
       content: doc,
-      id: doc._id || doc.id, // inconsistent apis for GET between mango and views
-      _rev: doc._rev || doc.value.rev,
+      id: getDocId(doc, options.docType),
+      _rev: getDocRev(doc, options.docType),
       header: '',
       keylabel: '',
       url: doc._id || doc.id ? getDocUrl('app', doc._id || doc.id, options.databaseName) : null,
-      isDeletable: isJSONDocBulkDeletable(doc, options.typeOfIndex),
-      isEditable: isJSONDocEditable(doc, options.typeOfIndex)
+      isDeletable: isJSONDocBulkDeletable(doc, options.docType),
+      isEditable: isJSONDocEditable(doc, options.docType)
     };
   });
 
   return {
     notSelectedFields: notSelectedFieldsTableView,
     selectedFields: selectedFieldsTableView,
-    hasBulkDeletableDoc: hasBulkDeletableDoc(normalizedDocs),
+    hasBulkDeletableDoc: hasBulkDeletableDoc(normalizedDocs, options.docType),
     schema: schema,
     results: res,
     displayedFields: isMetaData ? null : {
diff --git a/app/addons/documents/index-results/reducers.js b/app/addons/documents/index-results/reducers.js
index 8e86efa..d8ab896 100644
--- a/app/addons/documents/index-results/reducers.js
+++ b/app/addons/documents/index-results/reducers.js
@@ -15,7 +15,7 @@ import ActionTypes from './actiontypes';
 import Constants from '../constants';
 import { getJsonViewData } from './helpers/json-view';
 import { getTableViewData } from './helpers/table-view';
-import { getDefaultPerPage } from './helpers/shared-helpers';
+import { getDefaultPerPage, getDocId, isJSONDocBulkDeletable } from './helpers/shared-helpers';
 
 const initialState = {
   docs: [],  // raw documents returned from couch
@@ -28,7 +28,7 @@ const initialState = {
   isEditable: true,  // can the user manipulate the results returned?
   selectedLayout: Constants.LAYOUT_ORIENTATION.METADATA,
   textEmptyIndex: 'No Documents Found',
-  typeOfIndex: 'view',
+  docType: Constants.INDEX_RESULTS_DOC_TYPE.VIEW,
   fetchParams: {
     limit: getDefaultPerPage() + 1,
     skip: 0
@@ -64,6 +64,7 @@ export default function resultsState (state = initialState, action) {
 
     case ActionTypes.INDEX_RESULTS_REDUX_RESET_STATE:
       return Object.assign({}, initialState, {
+        selectedLayout: state.selectedLayout,
         selectedDocs: [],
         fetchParams: {
           limit: getDefaultPerPage() + 1,
@@ -71,7 +72,8 @@ export default function resultsState (state = initialState, action) {
         },
         pagination: Object.assign({}, initialState.pagination, {
           perPage: state.pagination.perPage
-        })
+        }),
+        isLoading: false
       });
 
     case ActionTypes.INDEX_RESULTS_REDUX_IS_LOADING:
@@ -85,6 +87,13 @@ export default function resultsState (state = initialState, action) {
       });
 
     case ActionTypes.INDEX_RESULTS_REDUX_NEW_RESULTS:
+      let selectedLayout = state.selectedLayout;
+      // Change layout if it's set to METADATA because this option is not available for Mango queries
+      if (action.docType === Constants.INDEX_RESULTS_DOC_TYPE.MANGO_QUERY) {
+        if (state.selectedLayout === Constants.LAYOUT_ORIENTATION.METADATA) {
+          selectedLayout = Constants.LAYOUT_ORIENTATION.TABLE;
+        }
+      }
       return Object.assign({}, state, {
         docs: action.docs,
         isLoading: false,
@@ -92,7 +101,9 @@ export default function resultsState (state = initialState, action) {
         fetchParams: Object.assign({}, state.fetchParams, action.params),
         pagination: Object.assign({}, state.pagination, {
           canShowNext: action.canShowNext
-        })
+        }),
+        docType: action.docType,
+        selectedLayout: selectedLayout
       });
 
     case ActionTypes.INDEX_RESULTS_REDUX_CHANGE_LAYOUT:
@@ -162,7 +173,7 @@ export const getDataForRendering = (state, databaseName) => {
     selectedLayout: state.selectedLayout,
     selectedFieldsTableView: state.tableView.selectedFieldsTableView,
     showAllFieldsTableView: state.tableView.showAllFieldsTableView,
-    typeOfIndex: state.typeOfIndex
+    docType: state.docType
   };
 
   const docsWithoutGeneratedMangoDocs = docs.filter(removeGeneratedMangoDocs);
@@ -193,7 +204,7 @@ export const getHasResults = (state) => {
   return !state.isLoading && state.docs.length > 0;
 };
 
-// helper function to determine if all the docs on the current page are selected.
+// helper function to determine if all selectable docs on the current page are selected.
 export const getAllDocsSelected = (state) => {
   if (state.docs.length === 0 || state.selectedDocs.length === 0) {
     return false;
@@ -211,11 +222,14 @@ export const getAllDocsSelected = (state) => {
 
   for (let i = 0; i < state.docs.length; i++) {
     const doc = state.docs[i];
-
+    if (!isJSONDocBulkDeletable(doc, state.docType)) {
+      //Only check selectable docs
+      continue;
+    }
     // Helper function for finding index of a doc in the current
     // selected docs list.
     const exists = (selectedDoc) => {
-      return doc._id || doc.id === selectedDoc._id;
+      return getDocId(doc, state.docType) === selectedDoc._id;
     };
 
     if (!state.selectedDocs.some(exists)) {
@@ -298,7 +312,7 @@ export const getIsLoading = state => state.isLoading;
 export const getIsEditable = state => state.isEditable;
 export const getSelectedLayout = state => state.selectedLayout;
 export const getTextEmptyIndex = state => state.textEmptyIndex;
-export const getTypeOfIndex = state => state.typeOfIndex;
+export const getDocType = state => state.docType;
 export const getPageStart = state => state.pagination.pageStart;
 export const getPrioritizedEnabled = state => state.tableView.showAllFieldsTableView;
 export const getCanShowNext = state => state.pagination.canShowNext;
diff --git a/app/addons/documents/index-results/stores.js b/app/addons/documents/index-results/stores.js
index 6d4932e..f0c0c42 100644
--- a/app/addons/documents/index-results/stores.js
+++ b/app/addons/documents/index-results/stores.js
@@ -40,7 +40,7 @@ Stores.IndexResultsStore = FauxtonAPI.Store.extend({
     this.clearSelectedItems();
     this._isLoading = false;
     this._textEmptyIndex = 'No Documents Found';
-    this._typeOfIndex = 'view';
+    this._typeOfIndex = Constants.INDEX_RESULTS_DOC_TYPE.VIEW;
 
     this._tableViewSelectedFields = [];
     this._isPrioritizedEnabled = false;
@@ -313,7 +313,10 @@ Stores.IndexResultsStore = FauxtonAPI.Store.extend({
 
     if (doc.get('def') && doc.get('def').fields) {
 
-      header = MangoHelper.getIndexName(doc);
+      header = MangoHelper.getIndexName({
+        def: doc.get('def'),
+        type: doc.get('type')
+      });
 
       return {
         content: this.getMangoDocContent(doc),
@@ -498,7 +501,7 @@ Stores.IndexResultsStore = FauxtonAPI.Store.extend({
     });
 
     function fixDocIdForMango (doc, docType) {
-      if (docType !== 'MangoIndex') {
+      if (docType !== Constants.INDEX_RESULTS_DOC_TYPE.MANGO_INDEX) {
         return doc;
       }
 
@@ -512,7 +515,7 @@ Stores.IndexResultsStore = FauxtonAPI.Store.extend({
         return;
       }
 
-      if (docType === 'MangoIndex') {
+      if (docType === Constants.INDEX_RESULTS_DOC_TYPE.MANGO_INDEX) {
         return false;
       }
 
@@ -528,7 +531,7 @@ Stores.IndexResultsStore = FauxtonAPI.Store.extend({
     }
 
     function isJSONDocBulkDeletable (doc, docType) {
-      if (docType === 'MangoIndex') {
+      if (docType === Constants.INDEX_RESULTS_DOC_TYPE.MANGO_INDEX) {
         return doc.type !== 'special';
       }
 
@@ -753,11 +756,14 @@ Stores.IndexResultsStore = FauxtonAPI.Store.extend({
   },
 
   getIsMangoResults: function () {
-    return this._typeOfIndex === 'mango' || this._typeOfIndex === 'mango-index';
+    return this._typeOfIndex === 'mango'
+      || this._typeOfIndex === 'mango-index'
+      || this._typeOfIndex === Constants.INDEX_RESULTS_DOC_TYPE.MANGO_INDEX; // MangoIndex is the value used by Redux components
   },
 
   getIsMangoIndexResults: function () {
-    return this._typeOfIndex === 'mango-index';
+    return this._typeOfIndex === 'mango-index'
+    || this._typeOfIndex === Constants.INDEX_RESULTS_DOC_TYPE.MANGO_INDEX; // MangoIndex is the value used by Redux components;
   },
 
   getIsPrioritizedEnabled: function () {
diff --git a/app/addons/documents/index-results/tests/index-results.componentsSpec.js b/app/addons/documents/index-results/tests/index-results.componentsSpec.js
index 08713de..359143d 100644
--- a/app/addons/documents/index-results/tests/index-results.componentsSpec.js
+++ b/app/addons/documents/index-results/tests/index-results.componentsSpec.js
@@ -150,7 +150,7 @@ describe('Index Results', function () {
           type: 'special',
           def: {fields: [{_id: 'desc'}]}
         }]),
-        typeOfIndex: 'view',
+        typeOfIndex: Constants.INDEX_RESULTS_DOC_TYPE.VIEW,
         bulkCollection: new Documents.BulkDeleteDocCollection([], {databaseId: '1'}),
       });
 
@@ -171,7 +171,7 @@ describe('Index Results', function () {
     it('does not render checkboxes for elements with no rev in a table (usual docs)', function () {
       IndexResultsActions.sendMessageNewResultList({
         collection: createDocColumn([{id: '1', foo: 'testId1'}, {id: '1', bar: 'testId1'}]),
-        typeOfIndex: 'view',
+        typeOfIndex: Constants.INDEX_RESULTS_DOC_TYPE.VIEW,
         bulkCollection: new Documents.BulkDeleteDocCollection([], {databaseId: '1'}),
       });
 
@@ -192,7 +192,7 @@ describe('Index Results', function () {
     it('renders checkboxes for elements with an id and rev in a table (usual docs)', function () {
       IndexResultsActions.sendMessageNewResultList({
         collection: createDocColumn([{id: '1', foo: 'testId1', rev: 'foo'}, {bar: 'testId1', rev: 'foo'}]),
-        typeOfIndex: 'view',
+        typeOfIndex: Constants.INDEX_RESULTS_DOC_TYPE.VIEW,
         bulkCollection: new Documents.BulkDeleteDocCollection([], {databaseId: '1'}),
       });
 
@@ -213,7 +213,7 @@ describe('Index Results', function () {
     it('renders checkboxes for elements with an id and rev in a json view (usual docs)', function () {
       IndexResultsActions.sendMessageNewResultList({
         collection: createDocColumn([{id: '1', emma: 'testId1', rev: 'foo'}, {bar: 'testId1'}]),
-        typeOfIndex: 'view',
+        typeOfIndex: Constants.INDEX_RESULTS_DOC_TYPE.VIEW,
         bulkCollection: new Documents.BulkDeleteDocCollection([], {databaseId: '1'}),
       });
 
@@ -233,7 +233,7 @@ describe('Index Results', function () {
     it('does not render checkboxes for elements with that are not deletable in a json view (usual docs)', function () {
       IndexResultsActions.sendMessageNewResultList({
         collection: createDocColumn([{foo: 'testId1', rev: 'foo'}, {bar: 'testId1'}]),
-        typeOfIndex: 'view',
+        typeOfIndex: Constants.INDEX_RESULTS_DOC_TYPE.VIEW,
         bulkCollection: new Documents.BulkDeleteDocCollection([], {databaseId: '1'}),
       });
 
@@ -269,7 +269,7 @@ describe('Index Results', function () {
 
       IndexResultsActions.sendMessageNewResultList({
         collection: createDocColumn([doc]),
-        typeOfIndex: 'view',
+        typeOfIndex: Constants.INDEX_RESULTS_DOC_TYPE.VIEW,
         bulkCollection: new Documents.BulkDeleteDocCollection([], {databaseId: '1'}),
       });
 
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 fd8a5cd..9827184 100644
--- a/app/addons/documents/index-results/tests/index-results.storesSpec.js
+++ b/app/addons/documents/index-results/tests/index-results.storesSpec.js
@@ -262,7 +262,7 @@ describe('Index Results Store', function () {
         {a: '1', 'value': 'one', b: '1'},
         {a: '1', 'value': 'one', b: '1'}
       ]),
-      typeOfIndex: 'view'
+      typeOfIndex: Constants.INDEX_RESULTS_DOC_TYPE.VIEW
     });
 
     store.toggleLayout({layout: Constants.LAYOUT_ORIENTATION.TABLE});
diff --git a/app/addons/documents/layouts.js b/app/addons/documents/layouts.js
index 3b83b58..d807842 100644
--- a/app/addons/documents/layouts.js
+++ b/app/addons/documents/layouts.js
@@ -24,6 +24,8 @@ import RightAllDocsHeader from './components/header-docs-right';
 import IndexResultsContainer from './index-results/containers/IndexResultsContainer';
 import PaginationContainer from './index-results/containers/PaginationContainer';
 import ApiBarContainer from './index-results/containers/ApiBarContainer';
+import { queryAllDocs } from './index-results/api';
+import Constants from './constants';
 
 export const TabsSidebarHeader = ({
   hideQueryOptions,
@@ -52,7 +54,8 @@ export const TabsSidebarHeader = ({
               database={database}
               isRedux={isRedux}
               fetchUrl={fetchUrl}
-              ddocsOnly={ddocsOnly} />
+              ddocsOnly={ddocsOnly}
+              queryDocs={ (params) => { return queryAllDocs(fetchUrl, params); } } />
           </div>
           { isRedux ? <ApiBarContainer databaseName={dbName} /> :
                       <ApiBarWrapper docURL={docURL} endpoint={endpoint} /> }
@@ -102,7 +105,8 @@ export const TabsSidebarContent = ({
         <div id="footer">
           {isRedux && !hideFooter ? <PaginationContainer
                                       databaseName={databaseName}
-                                      fetchUrl={fetchUrl} /> : null}
+                                      fetchUrl={fetchUrl}
+                                      queryDocs={(params) => { return queryAllDocs(fetchUrl, params); }}/> : null}
           {!isRedux && !hideFooter ? <ReactPagination.Footer /> : null}
         </div>
       </section>
@@ -136,7 +140,10 @@ export const DocsTabsSidebarLayout = ({
                       fetchUrl={fetchUrl}
                       designDocs={designDocs}
                       ddocsOnly={ddocsOnly}
-                      databaseName={dbName} />;
+                      databaseName={dbName}
+                      fetchAtStartup={true}
+                      queryDocs={ (params) => { return queryAllDocs(fetchUrl, params); } }
+                      docType={Constants.INDEX_RESULTS_DOC_TYPE.VIEW} />;
   } else {
     lowerContent = <IndexResultsComponents.List designDocs={designDocs} />;
   }
diff --git a/app/addons/documents/mango/__tests__/mango.api.test.js b/app/addons/documents/mango/__tests__/mango.api.test.js
new file mode 100644
index 0000000..523757d
--- /dev/null
+++ b/app/addons/documents/mango/__tests__/mango.api.test.js
@@ -0,0 +1,122 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+import sinon from "sinon";
+import utils from "../../../../../test/mocha/testUtils";
+import FauxtonAPI from "../../../../core/api";
+import * as MangoAPI from '../mango.api';
+import Constants from '../../constants';
+
+const fetchMock = require('fetch-mock');
+const assert = utils.assert;
+const restore = utils.restore;
+
+describe('Mango API', () => {
+
+  const paginationLimit = 6;
+
+  beforeEach(() => {
+    sinon.stub(FauxtonAPI, 'urls').returns('mock-url');
+  });
+
+  afterEach(() => {
+    restore(FauxtonAPI.urls);
+  });
+
+  describe('mangoQueryDocs', () => {
+    it('returns document type INDEX_RESULTS_DOC_TYPE.MANGO_QUERY', (done) => {
+      fetchMock.once("*", {});
+      MangoAPI.mangoQueryDocs('myDB', {}, {}).then((res) => {
+        assert.equal(res.docType, Constants.INDEX_RESULTS_DOC_TYPE.MANGO_QUERY);
+        done();
+      });
+    });
+  });
+
+  describe('mergeFetchParams', () => {
+    it('adjusts the query "skip" field based on pagination fetch params', () => {
+      // 1st page and query's skip is zero
+      let mergedParams = MangoAPI.mergeFetchParams({skip: 0}, {skip: 0, limit: paginationLimit});
+      assert.equal(mergedParams.skip, 0);
+
+      // 1st page and query's skip is non-zero
+      mergedParams = MangoAPI.mergeFetchParams({skip: 3}, {skip: 0, limit: paginationLimit});
+      assert.equal(mergedParams.skip, 3);
+
+      // non-1st page and query's skip is zero
+      mergedParams = MangoAPI.mergeFetchParams({skip: 0}, {skip: 5, limit: paginationLimit});
+      assert.equal(mergedParams.skip, 5);
+
+      // non-1st page and query's skip is non-zero
+      mergedParams = MangoAPI.mergeFetchParams({skip: 3}, {skip: 5, limit: paginationLimit});
+      assert.equal(mergedParams.skip, 8);
+
+    });
+
+    it('uses ZERO when query limit is ZERO', () => {
+      const mergedParams = MangoAPI.mergeFetchParams({limit: 0}, {skip: 0, limit: paginationLimit});
+      assert.equal(mergedParams.limit, 0);
+    });
+
+    it('uses pagination limit when query limit is not provided', () => {
+      const mergedParams = MangoAPI.mergeFetchParams({}, {skip: 5, limit: paginationLimit});
+      assert.equal(mergedParams.limit, paginationLimit);
+    });
+
+    it('uses pagination limit if query limit has not been reached', () => {
+      let mergedParams = MangoAPI.mergeFetchParams({limit: 50}, {skip: 5, limit: paginationLimit});
+      assert.equal(mergedParams.limit, paginationLimit);
+
+      mergedParams = MangoAPI.mergeFetchParams({limit: 50}, {skip: 15, limit: paginationLimit});
+      assert.equal(mergedParams.limit, paginationLimit);
+    });
+
+    it('respects query limit when value is lower than docs per page', () => {
+      const mergedParams = MangoAPI.mergeFetchParams({limit: 3}, {skip: 0, limit: paginationLimit});
+      assert.equal(mergedParams.limit, 3);
+    });
+
+    it('respects query limit when value is greater than docs per page', () => {
+      // Simulates loading of 3rd page
+      const mergedParams = MangoAPI.mergeFetchParams({limit: 17}, {skip: 15, limit: paginationLimit});
+      assert.equal(mergedParams.limit, 2);
+    });
+
+    it('respects query limit when value is a multipe of docs per page', () => {
+      // 1st page
+      let mergedParams = MangoAPI.mergeFetchParams({limit: 5}, {skip: 0, limit: paginationLimit});
+      assert.equal(mergedParams.limit, 5);
+
+      // non-1st page
+      mergedParams = MangoAPI.mergeFetchParams({limit: 15}, {skip: 10, limit: paginationLimit});
+      assert.equal(mergedParams.limit, 5);
+    });
+
+    it('works correctly with both skip and limit query params', () => {
+      // Simulates loading of 3rd page
+      const mergedParams = MangoAPI.mergeFetchParams({skip:10, limit: 17}, {skip: 15, limit: paginationLimit});
+      assert.equal(mergedParams.limit, 2);
+      assert.equal(mergedParams.skip, 25);
+    });
+
+  });
+
+  describe('fetchIndexes', () => {
+    it('returns document type INDEX_RESULTS_DOC_TYPE.MANGO_INDEX', (done) => {
+      fetchMock.once("*", {});
+      MangoAPI.fetchIndexes('myDB', {}).then((res) => {
+        assert.equal(res.docType, Constants.INDEX_RESULTS_DOC_TYPE.MANGO_INDEX);
+        done();
+      });
+    });
+  });
+});
diff --git a/app/addons/documents/mango/__tests__/mango.components.test.js b/app/addons/documents/mango/__tests__/mango.components.test.js
new file mode 100644
index 0000000..c83b954
--- /dev/null
+++ b/app/addons/documents/mango/__tests__/mango.components.test.js
@@ -0,0 +1,100 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+import FauxtonAPI from "../../../../core/api";
+import Views from "../mango.components";
+import MangoQueryEditor from "../components/MangoQueryEditor";
+import MangoIndexEditor from "../components/MangoIndexEditor";
+import utils from "../../../../../test/mocha/testUtils";
+import React from "react";
+import ReactDOM from "react-dom";
+import sinon from "sinon";
+import { mount } from 'enzyme';
+
+import thunk from 'redux-thunk';
+import { Provider } from 'react-redux';
+import { createStore, applyMiddleware, combineReducers } from 'redux';
+import mangoReducer from '../mango.reducers';
+import indexResultsReducer from '../../index-results/reducers';
+
+const assert = utils.assert;
+const restore = utils.restore;
+const databaseName = 'testdb';
+
+describe('Mango IndexEditor', function () {
+
+  const middlewares = [thunk];
+  const store = createStore(
+    combineReducers({ mangoQuery: mangoReducer, indexResults: indexResultsReducer }),
+    applyMiddleware(...middlewares)
+  );
+
+  beforeEach(() => {
+    sinon.stub(FauxtonAPI, 'urls').withArgs('mango').returns('mock-url');
+  });
+
+  afterEach(() => {
+    restore(FauxtonAPI.urls);
+  });
+
+  it('has a default index definition', function () {
+    const wrapper = mount(
+      <Provider store={store}>
+        <Views.MangoIndexEditorContainer
+          description="foo"
+          databaseName={databaseName} />
+      </Provider>
+    );
+
+    const indexEditor = wrapper.find(MangoIndexEditor);
+    assert.ok(indexEditor.exists());
+    if (indexEditor.exists()) {
+      const json = JSON.parse(indexEditor.props().queryIndexCode);
+      assert.equal(json.index.fields[0], 'foo');
+    }
+  });
+
+});
+
+describe('Mango QueryEditor', function () {
+
+  const middlewares = [thunk];
+  const store = createStore(
+    combineReducers({ mangoQuery: mangoReducer, indexResults: indexResultsReducer }),
+    applyMiddleware(...middlewares)
+  );
+
+  beforeEach(() => {
+    sinon.stub(FauxtonAPI, 'urls').returns('mock-url');
+  });
+
+  afterEach(() => {
+    restore(FauxtonAPI.urls);
+  });
+
+  it('has a default query', function () {
+    const wrapper = mount(
+      <Provider store={store}>
+        <Views.MangoQueryEditorContainer
+          description="foo"
+          editorTitle="mock-title"
+          databaseName={databaseName} />
+      </Provider>
+    );
+    const queryEditor = wrapper.find(MangoQueryEditor);
+    assert.ok(queryEditor.exists());
+    if (queryEditor.exists()) {
+      const query = JSON.parse(queryEditor.props().queryFindCode);
+      assert.property(query.selector, '_id');
+    }
+  });
+});
diff --git a/app/addons/documents/mango/__tests__/mango.store.test.js b/app/addons/documents/mango/__tests__/mango.store.test.js
deleted file mode 100644
index 3b9e69b..0000000
--- a/app/addons/documents/mango/__tests__/mango.store.test.js
+++ /dev/null
@@ -1,128 +0,0 @@
-// Licensed under the Apache License, Version 2.0 (the "License"); you may not
-// use this file except in compliance with the License. You may obtain a copy of
-// the License at
-//
-//   http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-// License for the specific language governing permissions and limitations under
-// the License.
-
-import Stores from "../mango.stores";
-import testUtils from "../../../../../test/mocha/testUtils";
-var assert = testUtils.assert;
-var store;
-
-describe('Mango Store', () => {
-  describe('getQueryCode', () => {
-
-    beforeEach(() => {
-      window.localStorage.clear();
-      store = new Stores.MangoStore();
-    });
-
-    it('returns a default query', () => {
-      assert.ok(store.getQueryFindCode());
-    });
-
-    it('can store query in history', () => {
-      store.addQueryHistory({selector: 'foo'});
-      const history = store.getHistory();
-      assert.equal(history[0].label, '{"selector":"foo"}');
-      assert.equal(history[0].value, '{\n   "selector": "foo"\n}');
-    });
-
-    it('does not add duplicate history', () => {
-      store.addQueryHistory({selector: 'foo'});
-      store.addQueryHistory({selector: 'foo'});
-
-      const history = store.getHistory();
-
-      assert.equal(history[0].label, '{"selector":"foo"}');
-      assert.equal(history.length, 2);
-    });
-
-    it('does not add duplicate history for selector with different formatting', () => {
-      store.addQueryHistory({selector: 'foo'});
-      store.addQueryHistory('{"selector": "foo"}');
-      store.addQueryHistory('{"selector":"foo"\n}');
-
-      const history = store.getHistory();
-
-      assert.equal(history[0].label, '{"selector":"foo"}');
-      assert.equal(history.length, 2);
-    });
-
-     it('promotes existing history entry to top when used', () => {
-      store.addQueryHistory({selector: 'foo'});
-      store.addQueryHistory({selector: 'bar'});
-      var history = store.getHistory();
-      assert.equal(history[0].label, '{"selector":"bar"}');
-
-      store.addQueryHistory({selector: 'foo'});
-      history = store.getHistory();
-      assert.equal(history[0].label, '{"selector":"foo"}');
-      assert.equal(history.length, 3);
-    });
-
-    it('limits the number of history items to 5', () => {
-      store.addQueryHistory({selector: '1'});
-      store.addQueryHistory({selector: '2'});
-      store.addQueryHistory({selector: '3'});
-      store.addQueryHistory({selector: '4'});
-      store.addQueryHistory({selector: '5'});
-      store.addQueryHistory({selector: '6'});
-
-      const history = store.getHistory();
-      assert.equal(history.length, 5);
-    });
-
-    it('can store query in history with custom label', () => {
-      store.addQueryHistory({selector: 'foo'}, 'demo');
-      const history = store.getHistory();
-      assert.equal(history[0].label, 'demo');
-      assert.equal(history[0].value, '{\n   "selector": "foo"\n}');
-    });
-
-    it('history is persisted by key', () => {
-      store.setHistoryKey('test');
-      store.addQueryHistory({selector: 'foo'}, 'demo');
-      var history = store.getHistory();
-      assert.equal(history[0].label, 'demo');
-
-      const store2 = new Stores.MangoStore();
-      store2.setHistoryKey('test');
-      history = store2.getHistory();
-      assert.equal(history[0].label, 'demo');
-    });
-
-    it('different history for different keys', () => {
-      store.setHistoryKey('test');
-      store.addQueryHistory({selector: 'foo'}, 'demo');
-      var history = store.getHistory();
-      assert.equal(history[0].label, 'demo');
-      assert.equal(history.length, 2);
-
-      const store2 = new Stores.MangoStore();
-      store2.setHistoryKey('test2');
-      store2.addQueryHistory({selector: 'bar'}, 'demo2');
-      history = store2.getHistory();
-      assert.equal(history[0].label, 'demo2');
-      assert.equal(history.length, 2);
-    });
-
-    it('initializes default query code with most recent history', () => {
-      store.addQueryHistory({selector: 'foo'}, 'demo');
-      const history = store.getHistory();
-      const code = store.getQueryFindCode();
-      assert.equal(history[0].value, code);
-    });
-
-    it('initializes default index code with template', () => {
-      const templates = store.getQueryIndexTemplates();
-      assert.equal(2, templates.length);
-    });
-  });
-});
diff --git a/app/addons/documents/mango/mango.actiontypes.js b/app/addons/documents/mango/components/ExplainPage.js
similarity index 56%
copy from app/addons/documents/mango/mango.actiontypes.js
copy to app/addons/documents/mango/components/ExplainPage.js
index 40c186b..f59e5b9 100644
--- a/app/addons/documents/mango/mango.actiontypes.js
+++ b/app/addons/documents/mango/components/ExplainPage.js
@@ -10,10 +10,26 @@
 // License for the specific language governing permissions and limitations under
 // the License.
 
-export default {
-  MANGO_SET_DB: 'MANGO_SET_DB',
-  MANGO_NEW_QUERY_FIND_CODE: 'MANGO_NEW_QUERY_FIND_CODE',
-  MANGO_NEW_QUERY_CREATE_INDEX_TEMPLATE: 'MANGO_NEW_QUERY_CREATE_INDEX_TEMPLATE',
-  MANGO_NEW_AVAILABLE_INDEXES: 'MANGO_NEW_AVAILABLE_INDEXES',
-  MANGO_EXPLAIN_RESULTS: 'MANGO_EXPLAIN_RESULTS'
+import React, { Component } from "react";
+
+export default class ExplainPage extends Component {
+  componentDidMount () {
+    prettyPrint();
+  };
+
+  componentDidUpdate () {
+    prettyPrint();
+  };
+
+  render () {
+    return (
+      <div>
+        <pre className="prettyprint">{JSON.stringify(this.props.explainPlan, null, ' ')}</pre>
+      </div>
+    );
+  };
+}
+
+ExplainPage.propTypes = {
+  explainPlan: React.PropTypes.object.isRequired
 };
diff --git a/app/addons/documents/mango/components/MangoIndexEditor.js b/app/addons/documents/mango/components/MangoIndexEditor.js
new file mode 100644
index 0000000..cccc1e1
--- /dev/null
+++ b/app/addons/documents/mango/components/MangoIndexEditor.js
@@ -0,0 +1,128 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+import React, { Component } from "react";
+import ReactSelect from "react-select";
+import "../../../../../assets/js/plugins/prettify";
+import app from "../../../../app";
+import FauxtonAPI from "../../../../core/api";
+import ReactComponents from "../../../components/react-components";
+
+const PaddedBorderedBox = ReactComponents.PaddedBorderedBox;
+const CodeEditorPanel = ReactComponents.CodeEditorPanel;
+const ConfirmButton = ReactComponents.ConfirmButton;
+const getDocUrl = app.helpers.getDocUrl;
+
+export default class MangoIndexEditor extends Component {
+
+  constructor(props) {
+    super(props);
+  }
+
+  componentDidMount() {
+    prettyPrint();
+    this.props.loadIndexTemplates();
+    this.props.clearResults();
+    this.props.loadIndexList({
+      fetchParams: { ...this.props.fetchParams, skip: 0 }
+    });
+  }
+
+  componentDidUpdate(prevProps) {
+    prettyPrint();
+    if (prevProps.templates != this.props.templates) {
+      // Explicitly set value because updating 'CodeEditorPanel.defaultCode' won't change the editor once it's already loaded.
+      this.setEditorValue(this.props.templates[0].value);
+    }
+  }
+
+  setEditorValue(newValue = '') {
+    return this.refs.codeEditor.getEditor().setValue(newValue);
+  }
+
+  getEditorValue() {
+    return this.refs.codeEditor.getValue();
+  }
+
+  editorHasErrors() {
+    return this.refs.codeEditor.getEditor().hasErrors();
+  }
+
+  onTemplateSelected(selectedItem) {
+    this.setEditorValue(selectedItem.value);
+  }
+
+  editor() {
+    const editQueryURL = '#' + FauxtonAPI.urls('mango', 'query-app', encodeURIComponent(this.props.databaseName));
+    return (
+      <div className="mango-editor-wrapper">
+        <form className="form-horizontal" onSubmit={(ev) => { this.saveIndex(ev); }}>
+          <div className="padded-box">
+            <ReactSelect
+              className="mango-select"
+              options={this.props.templates}
+              ref="templates"
+              placeholder="Examples"
+              searchable={false}
+              clearable={false}
+              autosize={false}
+              onChange={(item) => { this.onTemplateSelected(item); }}
+            />
+          </div>
+          <PaddedBorderedBox>
+            <CodeEditorPanel
+              id="query-field"
+              ref="codeEditor"
+              title="Index"
+              docLink={getDocUrl('MANGO_INDEX')}
+              defaultCode={this.props.queryIndexCode} />
+          </PaddedBorderedBox>
+          <div className="padded-box">
+            <div className="control-group">
+              <ConfirmButton text="Create index" id="create-index-btn" showIcon={false} />
+              <a className="edit-link" href={editQueryURL}>edit query</a>
+            </div>
+          </div>
+        </form>
+      </div>
+    );
+  }
+
+  render() {
+    return this.editor();
+  }
+
+  saveIndex(event) {
+    event.preventDefault();
+
+    if (this.editorHasErrors()) {
+      FauxtonAPI.addNotification({
+        msg: 'Please fix the Javascript errors and try again.',
+        type: 'error',
+        clear: true
+      });
+      return;
+    }
+
+    this.props.saveIndex({
+      databaseName: this.props.databaseName,
+      indexCode: this.getEditorValue(),
+      fetchParams: this.props.fetchParams
+    });
+  }
+}
+
+MangoIndexEditor.propTypes = {
+  databaseName: React.PropTypes.string.isRequired,
+  saveIndex: React.PropTypes.func.isRequired,
+  queryIndexCode: React.PropTypes.string.isRequired
+};
diff --git a/app/addons/documents/mango/components/MangoIndexEditorContainer.js b/app/addons/documents/mango/components/MangoIndexEditorContainer.js
new file mode 100644
index 0000000..93e84ad
--- /dev/null
+++ b/app/addons/documents/mango/components/MangoIndexEditorContainer.js
@@ -0,0 +1,54 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+import { connect } from 'react-redux';
+import * as IndexResultActions from '../../index-results/actions/fetch';
+import * as IndexResultBaseActions from '../../index-results/actions/base';
+import MangoIndexEditor from './MangoIndexEditor';
+import Helpers from '../mango.helper';
+import Actions from '../mango.actions';
+import * as MangoAPI from '../mango.api';
+
+const mapStateToProps = ({ mangoQuery, indexResults }, ownProps) => {
+  return {
+    description: ownProps.description,
+    databaseName: ownProps.databaseName,
+    queryIndexCode: Helpers.formatCode(mangoQuery.queryIndexCode),
+    templates: mangoQuery.queryIndexTemplates,
+    fetchParams: indexResults.fetchParams,
+  };
+};
+
+const mapDispatchToProps = (dispatch, ownProps) => {
+  return {
+    saveIndex: (options) => {
+      dispatch(Actions.saveIndex(options));
+    },
+    loadIndexList: (options) => {
+      const queryIndexes = (params) => { return MangoAPI.fetchIndexes(ownProps.databaseName, params); };
+      dispatch(IndexResultActions.fetchDocs(queryIndexes, options.fetchParams, {}));
+    },
+    clearResults: () => {
+      dispatch(IndexResultBaseActions.resetState());
+    },
+    loadIndexTemplates: () => {
+      dispatch(Actions.loadIndexTemplates());
+    }
+  };
+};
+
+const MangoIndexEditorContainer = connect(
+  mapStateToProps,
+  mapDispatchToProps
+)(MangoIndexEditor);
+
+export default MangoIndexEditorContainer;
diff --git a/app/addons/documents/mango/components/MangoQueryEditor.js b/app/addons/documents/mango/components/MangoQueryEditor.js
new file mode 100644
index 0000000..604f706
--- /dev/null
+++ b/app/addons/documents/mango/components/MangoQueryEditor.js
@@ -0,0 +1,168 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+import React, { Component } from "react";
+import ReactSelect from "react-select";
+import "../../../../../assets/js/plugins/prettify";
+import app from "../../../../app";
+import FauxtonAPI from "../../../../core/api";
+import ReactComponents from "../../../components/react-components";
+
+const PaddedBorderedBox = ReactComponents.PaddedBorderedBox;
+const CodeEditorPanel = ReactComponents.CodeEditorPanel;
+const getDocUrl = app.helpers.getDocUrl;
+
+export default class MangoQueryEditor extends Component {
+
+  constructor(props) {
+    super(props);
+  }
+
+  componentDidMount() {
+    prettyPrint();
+    this.props.loadQueryHistory({ databaseName: this.props.databaseName });
+    // Clear results list in case it was populated by other pages
+    this.props.clearResults();
+  }
+
+  componentDidUpdate (prevProps) {
+    prettyPrint();
+    if (prevProps.history != this.props.history) {
+      // Explicitly set value because updating 'CodeEditorPanel.defaultCode' won't change the editor once it's already loaded.
+      this.setEditorValue(this.props.history[0].value);
+    }
+  }
+
+  setEditorValue (newValue = '') {
+    return this.refs.codeEditor.getEditor().setValue(newValue);
+  }
+
+  getEditorValue () {
+    return this.refs.codeEditor.getValue();
+  }
+
+  editorHasErrors () {
+    return this.refs.codeEditor.getEditor().hasErrors();
+  }
+
+  onHistorySelected(selectedItem) {
+    this.setEditorValue(selectedItem.value);
+  }
+
+  editor() {
+    return (
+      <div className="mango-editor-wrapper">
+        <form className="form-horizontal" onSubmit={(ev) => {this.runQuery(ev);}}>
+          <div className="padded-box">
+            <ReactSelect
+              className="mango-select"
+              options={this.props.history}
+              ref="history"
+              placeholder="Query history"
+              searchable={false}
+              clearable={false}
+              autosize={false}
+              onChange={(elem) => {this.onHistorySelected(elem);}}
+            />
+          </div>
+          <PaddedBorderedBox>
+            <CodeEditorPanel
+              id="query-field"
+              ref="codeEditor"
+              title={this.props.editorTitle}
+              docLink={getDocUrl('MANGO_SEARCH')}
+              defaultCode={this.props.queryFindCode} />
+          </PaddedBorderedBox>
+          <div className="padded-box">
+            <div className="control-group">
+              <button type="submit" id="create-index-btn" className="btn btn-primary btn-space">Run Query</button>
+              <button type="button" id="explain-btn" className="btn btn-secondary btn-space"
+                onClick={(ev) => {this.runExplain(ev);} }>Explain</button>
+              <a className="edit-link" style={{} } onClick={(ev) => {this.manageIndexes(ev);}}>manage indexes</a>
+            </div>
+          </div>
+        </form>
+      </div>
+    );
+  }
+
+  render () {
+    if (this.props.isLoading) {
+      return (
+        <div className="mango-editor-wrapper">
+          <ReactComponents.LoadLines />
+        </div>
+      );
+    }
+
+    return this.editor();
+  }
+
+  notifyOnQueryError() {
+    if (this.editorHasErrors()) {
+      FauxtonAPI.addNotification({
+        msg:  'Please fix the Javascript errors and try again.',
+        type: 'error',
+        clear: true
+      });
+
+      return true;
+    }
+    return false;
+  }
+
+  manageIndexes(event) {
+    event.preventDefault();
+
+    this.props.manageIndexes();
+
+    const manageIndexURL = '#' + FauxtonAPI.urls('mango', 'index-app', encodeURIComponent(this.props.databaseName));
+    FauxtonAPI.navigate(manageIndexURL);
+  }
+
+  runExplain(event) {
+    event.preventDefault();
+
+    if (this.notifyOnQueryError()) {
+      return;
+    }
+
+    this.props.runExplainQuery({
+      databaseName: this.props.databaseName,
+      queryCode: this.getEditorValue()
+    });
+  }
+
+  runQuery (event) {
+    event.preventDefault();
+
+    if (this.notifyOnQueryError()) {
+      return;
+    }
+    this.props.clearResults();
+    this.props.runQuery({
+      databaseName: this.props.databaseName,
+      queryCode: JSON.parse(this.getEditorValue()),
+      fetchParams: {...this.props.fetchParams, skip: 0}
+    });
+  }
+}
+
+MangoQueryEditor.propTypes = {
+  description: React.PropTypes.string.isRequired,
+  editorTitle: React.PropTypes.string.isRequired,
+  queryFindCode: React.PropTypes.string.isRequired,
+  queryFindCodeChanged: React.PropTypes.bool,
+  databaseName: React.PropTypes.string.isRequired,
+  runExplainQuery: React.PropTypes.func.isRequired,
+  manageIndexes: React.PropTypes.func.isRequired,
+};
diff --git a/app/addons/documents/mango/components/MangoQueryEditorContainer.js b/app/addons/documents/mango/components/MangoQueryEditorContainer.js
new file mode 100644
index 0000000..c9ad1d0
--- /dev/null
+++ b/app/addons/documents/mango/components/MangoQueryEditorContainer.js
@@ -0,0 +1,99 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+import { connect } from 'react-redux';
+import FauxtonAPI from "../../../../core/api";
+import * as IndexResultActions from '../../index-results/actions/fetch';
+import * as IndexResultBaseActions from '../../index-results/actions/base';
+import MangoQueryEditor from './MangoQueryEditor';
+import Helpers from '../mango.helper';
+import Actions from '../mango.actions';
+import * as MangoAPI from '../mango.api';
+
+const getAvailableQueryIndexes = ({ availableIndexes }) => {
+  if (!availableIndexes) {
+    return [];
+  }
+  return availableIndexes.filter(({ type }) => {
+      return ['json', 'special'].includes(type);
+  });
+};
+
+const getAvailableAdditionalIndexes = ({ additionalIndexes }) => {
+  if (!additionalIndexes) {
+    return [];
+  }
+  const indexes = FauxtonAPI.getExtensions('mango:additionalIndexes')[0];
+  if (!indexes) {
+    return;
+  }
+
+  return additionalIndexes.filter((el) => {
+    return el.get('type').indexOf(indexes.type) !== -1;
+  });
+};
+
+const mapStateToProps = (state, ownProps) => {
+  const mangoQuery = state.mangoQuery;
+  const indexResults = state.indexResults;
+
+  return {
+    databaseName: ownProps.databaseName,
+    queryFindCode: Helpers.formatCode(mangoQuery.queryFindCode),
+    queryFindCodeChanged: mangoQuery.queryFindCodeChanged,
+    availableIndexes: getAvailableQueryIndexes(mangoQuery),
+    additionalIndexes: getAvailableAdditionalIndexes(mangoQuery),
+    isLoading: mangoQuery.isLoading,
+    history: mangoQuery.history,
+    description: ownProps.description,
+    editorTitle: ownProps.editorTitle,
+    additionalIndexesText: ownProps.additionalIndexesText,
+
+    fetchParams: indexResults.fetchParams
+  };
+};
+
+const mapDispatchToProps = (dispatch/*, ownProps*/) => {
+  return {
+    loadQueryHistory: (options) => {
+      dispatch(Actions.loadQueryHistory(options));
+    },
+
+    runExplainQuery: (options) => {
+      dispatch(Actions.runExplainQuery(options));
+    },
+
+    runQuery: (options) => {
+      const queryDocs = (params) => { return MangoAPI.mangoQueryDocs(options.databaseName, options.queryCode, params); };
+
+      dispatch(Actions.hideQueryExplain());
+      dispatch(Actions.newQueryFindCode(options));
+      dispatch(IndexResultActions.fetchDocs(queryDocs, options.fetchParams, {}));
+    },
+
+    clearResults: () => {
+      dispatch(IndexResultBaseActions.resetState());
+    },
+
+    manageIndexes: () => {
+      dispatch(Actions.hideQueryExplain());
+    }
+
+  };
+};
+
+const MangoQueryEditorContainer = connect(
+  mapStateToProps,
+  mapDispatchToProps
+)(MangoQueryEditor);
+
+export default MangoQueryEditorContainer;
diff --git a/app/addons/documents/mango/mango.actions.js b/app/addons/documents/mango/mango.actions.js
index e2a8186..58de757 100644
--- a/app/addons/documents/mango/mango.actions.js
+++ b/app/addons/documents/mango/mango.actions.js
@@ -10,91 +10,114 @@
 // License for the specific language governing permissions and limitations under
 // the License.
 
+import app from "../../../app";
 import FauxtonAPI from "../../../core/api";
-import Documents from "../resources";
 import ActionTypes from "./mango.actiontypes";
-import IndexResultActions from "../index-results/actions";
+import * as IndexResultActions from "../index-results/actions/fetch";
+import * as MangoAPI from "./mango.api";
 
 export default {
 
-  setDatabase: function (options) {
-    FauxtonAPI.dispatch({
-      type: ActionTypes.MANGO_SET_DB,
+  loadQueryHistory: function (options) {
+    return {
+      type: ActionTypes.MANGO_LOAD_QUERY_HISTORY,
       options: options
-    });
+    };
   },
 
   newQueryFindCode: function (options) {
-    FauxtonAPI.dispatch({
+    return {
       type: ActionTypes.MANGO_NEW_QUERY_FIND_CODE,
       options: options
-    });
+    };
+  },
+
+  showQueryExplain: function (options) {
+    return {
+      type: ActionTypes.MANGO_SHOW_EXPLAIN_RESULTS,
+      options: options
+    };
+  },
+
+  hideQueryExplain: function () {
+    return {
+      type: ActionTypes.MANGO_HIDE_EXPLAIN_RESULTS
+    };
   },
 
   newQueryCreateIndexTemplate: function (options) {
-    FauxtonAPI.dispatch({
+    return {
       type: ActionTypes.MANGO_NEW_QUERY_CREATE_INDEX_TEMPLATE,
       options: options
-    });
+    };
+  },
+
+  loadIndexTemplates: function (options) {
+    return {
+      type: ActionTypes.MANGO_LOAD_INDEX_TEMPLATES,
+      options: options
+    };
   },
 
-  saveIndex: function ({database, queryCode}) {
-    const query = JSON.parse(queryCode),
-        mangoIndex = new Documents.MangoIndex(query, {database: database});
+  requestSaveIndex: function () {
+    return {
+      type: ActionTypes.MANGO_SAVE_INDEX_REQUEST
+    };
+  },
 
+  saveIndex: function ({ databaseName, indexCode, fetchParams }) {
     FauxtonAPI.addNotification({
-      msg:  'Saving Index for Query...',
+      msg: 'Saving index for query...',
       type: 'info',
       clear: true
     });
 
-    mangoIndex
-      .save()
-      .then(function () {
-        var url = '#' + FauxtonAPI.urls('mango', 'query-app', database.safeID());
+    return (dispatch) => {
+      // Notifies index save operation was requested
+      dispatch(this.requestSaveIndex());
 
-        // force mango index list to reload
-        IndexResultActions.reloadResultsList();
+      return MangoAPI.createIndex(databaseName, indexCode)
+        .then(() => {
+          const runQueryURL = '#' + FauxtonAPI.urls('mango', 'query-app',
+            app.utils.safeURLName(databaseName));
 
-        FauxtonAPI.addNotification({
-          msg: 'Index is ready for querying. <a href="' + url + '">Run a Query.</a>',
-          type: 'success',
-          clear: true,
-          escape: false
-        });
-      })
-      .fail(function (res) {
-        FauxtonAPI.addNotification({
-          msg: res.responseJSON.reason,
-          type: 'error',
-          clear: true
-        });
-      });
+          const queryIndexes = (params) => { return MangoAPI.fetchIndexes(databaseName, params); };
+          dispatch(IndexResultActions.fetchDocs(queryIndexes, fetchParams, {}));
+
+          FauxtonAPI.addNotification({
+            msg: 'Index is ready for querying. <a href="' + runQueryURL + '">Run a Query.</a>',
+            type: 'success',
+            clear: true,
+            escape: false
+          });
+        })
+        .catch((error) => {
+            FauxtonAPI.addNotification({
+              msg: 'Failed to create index. ' + this.errorReason(error),
+              type: 'error',
+              clear: true
+            });
+          });
+    };
   },
 
-  runExplainQuery: function ({database, queryCode}) {
-    const url = FauxtonAPI.urls('mango', 'explain-server', database.safeID()),
-        query = JSON.parse(queryCode);
-
-    $.ajax({
-      type: 'POST',
-      url: url,
-      contentType: 'application/json; charset=utf-8',
-      dataType: 'json',
-      data: JSON.stringify(query)
-    }).then(function (explainPlan) {
-      FauxtonAPI.dispatch({
-        type: ActionTypes.MANGO_EXPLAIN_RESULTS,
-        options: {
-          explainPlan: explainPlan
-        }
-      });
-    }).fail(function () {
-      FauxtonAPI.addNotification({
-        msg: 'There was an error fetching the query plan.',
-        type: 'error',
-        clear: true
-      });
-    });
+  errorReason: function (error) {
+    return 'Reason: ' + ((error && error.message) || 'n/a');
+  },
+
+  runExplainQuery: function ({ databaseName, queryCode }) {
+    return (dispatch) => {
+      return MangoAPI.fetchQueryExplain(databaseName, queryCode)
+        .then((explainPlan) => {
+          dispatch(this.showQueryExplain({ explainPlan }));
+        }).catch(() => {
+          FauxtonAPI.addNotification({
+            msg: 'There was an error fetching the query plan.',
+            type: 'error',
+            clear: true
+          });
+        });
+    };
   }
+
 };
diff --git a/app/addons/documents/mango/mango.actiontypes.js b/app/addons/documents/mango/mango.actiontypes.js
index 40c186b..12d9343 100644
--- a/app/addons/documents/mango/mango.actiontypes.js
+++ b/app/addons/documents/mango/mango.actiontypes.js
@@ -11,9 +11,11 @@
 // the License.
 
 export default {
-  MANGO_SET_DB: 'MANGO_SET_DB',
   MANGO_NEW_QUERY_FIND_CODE: 'MANGO_NEW_QUERY_FIND_CODE',
+  MANGO_LOAD_QUERY_HISTORY: 'MANGO_LOAD_QUERY_HISTORY',
+  MANGO_LOAD_INDEX_TEMPLATES: 'MANGO_LOAD_INDEX_TEMPLATES',
   MANGO_NEW_QUERY_CREATE_INDEX_TEMPLATE: 'MANGO_NEW_QUERY_CREATE_INDEX_TEMPLATE',
-  MANGO_NEW_AVAILABLE_INDEXES: 'MANGO_NEW_AVAILABLE_INDEXES',
-  MANGO_EXPLAIN_RESULTS: 'MANGO_EXPLAIN_RESULTS'
+  MANGO_SAVE_INDEX_REQUEST: 'MANGO_SAVE_INDEX_REQUEST',
+  MANGO_SHOW_EXPLAIN_RESULTS: 'MANGO_SHOW_EXPLAIN_RESULTS',
+  MANGO_HIDE_EXPLAIN_RESULTS: 'MANGO_HIDE_EXPLAIN_RESULTS',
 };
diff --git a/app/addons/documents/mango/mango.api.js b/app/addons/documents/mango/mango.api.js
new file mode 100644
index 0000000..61e9f54
--- /dev/null
+++ b/app/addons/documents/mango/mango.api.js
@@ -0,0 +1,129 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+import 'whatwg-fetch';
+import queryString from 'query-string';
+import app from "../../../app";
+import FauxtonAPI from "../../../core/api";
+import Constants from '../constants';
+
+export const fetchQueryExplain = (databaseName, queryCode) => {
+  const url = FauxtonAPI.urls('mango', 'explain-server', databaseName);
+
+  return fetch(url, {
+    headers: {
+      'Accept': 'application/json',
+      'Content-Type': 'application/json; charset=utf-8'
+    },
+    credentials: 'include',
+    method: 'POST',
+    body: queryCode
+  })
+    .then((res) => res.json())
+    .then((json) => {
+      if (json.error) {
+        throw new Error('(' + json.error + ') ' + json.reason);
+      }
+      return json;
+    });
+};
+
+export const createIndex = (databaseName, indexCode) => {
+  const url = FauxtonAPI.urls('mango', 'index-server',
+    app.utils.safeURLName(databaseName));
+
+  return fetch(url, {
+    headers: {
+      'Accept': 'application/json',
+      'Content-Type': 'application/json; charset=utf-8'
+    },
+    credentials: 'include',
+    method: 'POST',
+    body: indexCode
+  })
+    .then((res) => res.json())
+    .then((json) => {
+      if (json.error) {
+        throw new Error('(' + json.error + ') ' + json.reason);
+      }
+      return json;
+    });
+};
+
+export const fetchIndexes = (databaseName, params) => {
+  const query = queryString.stringify(params);
+  let url = FauxtonAPI.urls('mango', 'index-server', app.utils.safeURLName(databaseName));
+  url = `${url}${url.includes('?') ? '&' : '?'}${query}`;
+
+  return fetch(url, {
+    headers: {
+      'Accept': 'application/json',
+      'Content-Type': 'application/json; charset=utf-8'
+    },
+    credentials: 'include',
+    method: 'GET'
+  })
+    .then((res) => res.json())
+    .then((json) => {
+      if (json.error) {
+        throw new Error('(' + json.error + ') ' + json.reason);
+      }
+      return {
+        docs: json.indexes,
+        docType: Constants.INDEX_RESULTS_DOC_TYPE.MANGO_INDEX
+      };
+    });
+};
+
+// Determines what params need to be sent to couch based on the Mango query entered
+// by the user and what fauxton is using to emulate pagination (fetchParams).
+export const mergeFetchParams = (queryCode, fetchParams) => {
+  // Since Fauxton pagination's 'limit' is always (docs-per-page + 1), this ensures
+  // (page-number * docs-per-page) doesn't exceed the query's 'limit' value.
+  let limit = fetchParams.limit;
+  const docsPerPage = fetchParams.limit - 1;
+  const pageNumber = Math.floor(fetchParams.skip / docsPerPage) + 1;
+  const docsOverLimit = (pageNumber * docsPerPage) - queryCode.limit;
+  if (docsOverLimit >= 0) {
+    limit = docsPerPage - docsOverLimit;
+  }
+
+  return {
+    ...queryCode,
+    limit: limit,
+    skip: queryCode.skip ? (fetchParams.skip + queryCode.skip) : fetchParams.skip
+  };
+};
+
+export const mangoQueryDocs = (databaseName, queryCode, fetchParams) => {
+  const url = FauxtonAPI.urls('mango', 'query-server', databaseName);
+  const modifiedQuery = mergeFetchParams(queryCode, fetchParams);
+  return fetch(url, {
+    headers: {
+      'Accept': 'application/json',
+      'Content-Type': 'application/json; charset=utf-8'
+    },
+    credentials: 'include',
+    method: 'POST',
+    body: JSON.stringify(modifiedQuery)
+  })
+    .then((res) => res.json())
+    .then((json) => {
+      if (json.error) {
+        throw new Error('(' + json.error + ') ' + json.reason);
+      }
+      return {
+        docs: json.docs,
+        docType: Constants.INDEX_RESULTS_DOC_TYPE.MANGO_QUERY
+      };
+    });
+};
diff --git a/app/addons/documents/mango/mango.components.js b/app/addons/documents/mango/mango.components.js
index c78cf74..94dce28 100644
--- a/app/addons/documents/mango/mango.components.js
+++ b/app/addons/documents/mango/mango.components.js
@@ -10,325 +10,12 @@
 // License for the specific language governing permissions and limitations under
 // the License.
 
-import app from "../../../app";
-import FauxtonAPI from "../../../core/api";
-import React, { Component } from 'react';
-import Stores from "./mango.stores";
-import Actions from "./mango.actions";
-import ReactComponents from "../../components/react-components";
-import IndexResultActions from "../index-results/actions";
-import "../../../../assets/js/plugins/prettify";
-import ReactSelect from "react-select";
-
-var mangoStore = Stores.mangoStore;
-var getDocUrl = app.helpers.getDocUrl;
-
-var PaddedBorderedBox = ReactComponents.PaddedBorderedBox;
-var CodeEditorPanel = ReactComponents.CodeEditorPanel;
-var ConfirmButton = ReactComponents.ConfirmButton;
-
-var MangoQueryEditorController = React.createClass({
-  getInitialState: function () {
-    return this.getStoreState();
-  },
-
-  getStoreState: function () {
-    return {
-      queryCode: mangoStore.getQueryFindCode(),
-      database: mangoStore.getDatabase(),
-      history: mangoStore.getHistory()
-    };
-  },
-
-  onChange: function () {
-    this.setState(this.getStoreState());
-  },
-
-  componentDidUpdate: function () {
-    prettyPrint();
-  },
-
-  componentDidMount: function () {
-    prettyPrint();
-    mangoStore.on('change', this.onChange, this);
-  },
-
-  componentWillUnmount: function () {
-    mangoStore.off('change', this.onChange);
-  },
-
-  getMangoEditor: function () {
-    return this.refs.mangoEditor;
-  },
-
-  render: function () {
-    if (this.state.isLoading) {
-      return (
-        <div className="mango-editor-wrapper">
-          <ReactComponents.LoadLines />
-        </div>
-      );
-    }
-
-    return (
-      <MangoEditor
-        ref="mangoEditor"
-        description={this.props.description}
-        dbName={this.state.database.id}
-        onSubmit={this.runQuery}
-        title={this.props.editorTitle}
-        docs={getDocUrl('MANGO_SEARCH')}
-        exampleCode={this.state.queryCode}
-        onExplainQuery={this.runExplain}
-        history={this.state.history}
-        onHistorySelected={this.historySelected}
-        />
-    );
-  },
-
-  notifyOnQueryError: function() {
-    if (this.getMangoEditor().hasErrors()) {
-      FauxtonAPI.addNotification({
-        msg:  'Please fix the Javascript errors and try again.',
-        type: 'error',
-        clear: true
-      });
-
-      return true;
-    }
-    return false;
-  },
-
-  runExplain: function(event) {
-    event.preventDefault();
-
-    if (this.notifyOnQueryError()) {
-      return;
-    }
-
-    Actions.runExplainQuery({
-      database: this.state.database,
-      queryCode: this.getMangoEditor().getEditorValue()
-    });
-  },
-
-  runQuery: function (event) {
-    event.preventDefault();
-
-    if (this.notifyOnQueryError()) {
-      return;
-    }
-
-    IndexResultActions.runMangoFindQuery({
-      database: this.state.database,
-      queryCode: this.getMangoEditor().getEditorValue()
-    });
-  },
-
-  historySelected: function(selectedItem) {
-    this.getMangoEditor().setEditorValue(selectedItem.value);
-  }
-});
-
-var MangoEditor = React.createClass({
-  render: function () {
-    return (
-      <div className="mango-editor-wrapper">
-        <form className="form-horizontal" onSubmit={this.props.onSubmit}>
-          <div className="padded-box">
-            <ReactSelect
-                className="mango-select"
-                options={this.props.history}
-                ref="history"
-                placeholder="Query history"
-                searchable={false}
-                clearable={false}
-                autosize={false}
-                onChange={this.props.onHistorySelected}
-                />
-          </div>
-          <PaddedBorderedBox>
-            <CodeEditorPanel
-              id="query-field"
-              ref="field"
-              title={this.props.title}
-              docLink={this.props.docs}
-              defaultCode={this.props.exampleCode} />
-          </PaddedBorderedBox>
-          <div className="padded-box">
-            <div className="controls-group">
-              <button type="submit" id="create-index-btn" className="btn btn-primary btn-space">Run Query</button>
-              <button type="button" id="explain-btn" className="btn btn-secondary btn-space" onClick={this.props.onExplainQuery}>Explain</button>
-              <a className="edit-link" href={'#' + FauxtonAPI.urls('mango', 'index-app', encodeURIComponent(this.props.dbName))}>manage indexes</a>
-            </div>
-          </div>
-        </form>
-      </div>
-    );
-  },
-
-  setEditorValue: function (value) {
-    return this.getEditor().setValue(value);
-  },
-
-  getEditorValue: function () {
-    return this.refs.field.getValue();
-  },
-
-  getEditor: function () {
-    return this.refs.field.getEditor();
-  },
-
-  hasErrors: function () {
-    return this.getEditor().hasErrors();
-  }
-});
-
-var MangoIndexEditor = React.createClass({
-  render: function () {
-    return (
-      <div className="mango-editor-wrapper">
-        <form className="form-horizontal" onSubmit={this.props.onSubmit}>
-          <div className="padded-box">
-            <ReactSelect
-                className="mango-select"
-                options={this.props.templates}
-                ref="templates"
-                placeholder="Examples"
-                searchable={false}
-                clearable={false}
-                autosize={false}
-                onChange={this.props.onTemplateSelected}
-                />
-          </div>
-          <PaddedBorderedBox>
-            <CodeEditorPanel
-              id="query-field"
-              ref="field"
-              title={this.props.title}
-              docLink={this.props.docs}
-              defaultCode={this.props.exampleCode} />
-          </PaddedBorderedBox>
-          <div className="padded-box">
-            <div className="control-group">
-              <ConfirmButton text="Create index" id="create-index-btn" showIcon={false} />
-              <a className="edit-link" href={'#' + FauxtonAPI.urls('mango', 'query-app', encodeURIComponent(this.props.dbName))}>edit query</a>
-            </div>
-          </div>
-        </form>
-      </div>
-    );
-  },
-
-  setEditorValue: function (value) {
-    return this.getEditor().setValue(value);
-  },
-
-  getEditorValue: function () {
-    return this.refs.field.getValue();
-  },
-
-  getEditor: function () {
-    return this.refs.field.getEditor();
-  },
-
-  hasErrors: function () {
-    return this.getEditor().hasErrors();
-  }
-});
-
-var MangoIndexEditorController = React.createClass({
-  getInitialState: function () {
-    return this.getStoreState();
-  },
-
-  getStoreState: function () {
-    return {
-      queryIndexCode: mangoStore.getQueryIndexCode(),
-      database: mangoStore.getDatabase(),
-      templates: mangoStore.getQueryIndexTemplates()
-    };
-  },
-
-  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.mangoIndexEditor;
-  },
-
-  templateSelected: function(selectedItem) {
-    this.getMangoEditor().setEditorValue(selectedItem.value);
-  },
-
-  render: function () {
-    return (
-      <MangoIndexEditor
-        ref="mangoIndexEditor"
-        description={this.props.description}
-        dbName={this.state.database.id}
-        onSubmit={this.saveIndex}
-        title="Index"
-        docs={getDocUrl('MANGO_INDEX')}
-        templates={this.state.templates}
-        onTemplateSelected={this.templateSelected}
-        exampleCode={this.state.queryIndexCode} />
-    );
-  },
-
-  saveIndex: function (event) {
-    event.preventDefault();
-
-    if (this.getMangoEditor().hasErrors()) {
-      FauxtonAPI.addNotification({
-        msg:  'Please fix the Javascript errors and try again.',
-        type: 'error',
-        clear: true
-      });
-      return;
-    }
-
-    Actions.saveIndex({
-      database: this.state.database,
-      queryCode: this.getMangoEditor().getEditorValue()
-    });
-  }
-});
-
-class ExplainPage extends Component {
-  componentDidMount () {
-    prettyPrint();
-  };
-
-  componentDidUpdate () {
-    prettyPrint();
-  };
-
-  render () {
-
-    return (
-      <div>
-        <pre className="prettyprint">{JSON.stringify(this.props.explainPlan, null, ' ')}</pre>
-      </div>
-    );
-  };
-}
-
-ExplainPage.propTypes = {
-  explainPlan: React.PropTypes.object.isRequired
-};
+import MangoQueryEditorContainer from "./components/MangoQueryEditorContainer";
+import MangoIndexEditorContainer from "./components/MangoIndexEditorContainer";
+import ExplainPage from "./components/ExplainPage";
 
 export default {
-  MangoIndexEditorController: MangoIndexEditorController,
-  MangoQueryEditorController: MangoQueryEditorController,
-  ExplainPage: ExplainPage
+  MangoIndexEditorContainer,
+  MangoQueryEditorContainer,
+  ExplainPage
 };
diff --git a/app/addons/documents/mango/mango.helper.js b/app/addons/documents/mango/mango.helper.js
index 4634adb..09586c0 100644
--- a/app/addons/documents/mango/mango.helper.js
+++ b/app/addons/documents/mango/mango.helper.js
@@ -12,13 +12,13 @@
 
 import FauxtonAPI from "../../../core/api";
 
-function getIndexName (doc) {
-  var nameArray = [],
+const getIndexName = ({def, type}) => {
+  let nameArray = [],
       indexes;
 
-  nameArray = doc.get('def').fields.reduce(function (acc, el, i) {
+  nameArray = def.fields.reduce(function (acc, el, i) {
     if (i === 0) {
-      acc.push(doc.get('type') + ': ' + Object.keys(el)[0]);
+      acc.push(type + ': ' + Object.keys(el)[0]);
     } else {
       acc.push(Object.keys(el)[0]);
     }
@@ -28,12 +28,33 @@ function getIndexName (doc) {
 
   if (!nameArray.length) {
     indexes = FauxtonAPI.getExtensions('mango:additionalIndexes')[0];
-    nameArray = indexes.createHeader(doc);
+    if (indexes) {
+      nameArray = indexes.createHeader({def, type});
+    } else {
+      nameArray = [type + ': ' + (def.selector ? JSON.stringify(def.selector) : '{}')];
+    }
   }
 
   return nameArray.join(', ');
-}
+};
+
+const formatCode = (code) => {
+  return JSON.stringify(code, null, 3);
+};
+
+const getIndexContent = (doc) => {
+  const content = {
+    ...doc
+  };
+
+  delete content.ddoc;
+  delete content.name;
+
+  return JSON.stringify(content, null, ' ');
+};
 
 export default {
-  getIndexName: getIndexName
+  getIndexName,
+  formatCode,
+  getIndexContent
 };
diff --git a/app/addons/documents/mango/mango.reducers.js b/app/addons/documents/mango/mango.reducers.js
new file mode 100644
index 0000000..06fd8be
--- /dev/null
+++ b/app/addons/documents/mango/mango.reducers.js
@@ -0,0 +1,205 @@
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+import app from "../../../app";
+import FauxtonAPI from "../../../core/api";
+import ActionTypes from "./mango.actiontypes";
+import MangoHelper from "./mango.helper";
+import constants from "./mango.constants";
+
+const defaultQueryIndexCode = {
+  "index": {
+    "fields": ["_id"]
+  },
+  "type": "json"
+};
+
+const defaultQueryFindCode = {
+  "selector": {
+    "_id": { "$gt": null }
+  }
+};
+
+const createSelectItem = (code) => {
+  // ensure we're working with a deserialized query object
+  const object = typeof code === "string" ? JSON.parse(code) : code;
+
+  const singleLineValue = JSON.stringify(object);
+  const multiLineValue = MangoHelper.formatCode(object);
+
+  return {
+    label: singleLineValue,
+    value: multiLineValue,
+    className: 'mango-select-entry'
+  };
+};
+
+const getDefaultHistory = () => {
+  return [createSelectItem(defaultQueryFindCode)];
+};
+
+const getDefaultQueryIndexTemplates = () => {
+  return constants.INDEX_TEMPLATES.map((el) => {
+    const item = createSelectItem(el.code);
+    item.label = el.label || item.label;
+    return item;
+  });
+};
+
+const loadIndexTemplates = () => {
+  const extTemplates = FauxtonAPI.getExtensions('mango:indexTemplates')[0];
+  if (extTemplates) {
+    return extTemplates.filter((elem) => {
+      return !!elem.code;
+    }).map((elem) => {
+      const item = createSelectItem(elem.code);
+      item.label = elem.label || item.label;
+      return item;
+    });
+  }
+
+  return getDefaultQueryIndexTemplates();
+};
+
+const HISTORY_LIMIT = 5;
+
+const initialState = {
+  queryFindCode: defaultQueryFindCode,
+  queryIndexCode: defaultQueryIndexCode,
+  queryFindCodeChanged: false,
+  explainPlan: undefined,
+  history: getDefaultHistory(),
+  historyKey: 'default',
+  queryIndexTemplates: getDefaultQueryIndexTemplates()
+};
+
+const loadQueryHistory = (databaseName) => {
+  const historyKey = databaseName + '_queryhistory';
+  let history = app.utils.localStorageGet(historyKey);
+  if (history) {
+    return history;
+  }
+
+  history = FauxtonAPI.getExtensions('mango:queryHistory')[0];
+  if (history) {
+    return history.filter((elem) => {
+      return !!elem.code;
+    }).map((elem) => {
+      const item = createSelectItem(elem.code);
+      item.label = elem.label || item.label;
+      return item;
+    });
+  }
+
+  return getDefaultHistory();
+};
+
+const updateQueryHistory = ({ historyKey, history }, queryCode, label) => {
+  const newHistory = history.slice();
+
+  const historyEntry = createSelectItem(queryCode);
+  historyEntry.label = label || historyEntry.label;
+
+  // remove existing entry if it exists
+  const indexOfExisting = newHistory.findIndex(el => el.value === historyEntry.value);
+  if (indexOfExisting > -1) {
+    newHistory.splice(indexOfExisting, 1);
+  }
+
+  // insert item at head of array
+  newHistory.unshift(historyEntry);
+
+  // limit array length
+  if (newHistory.length > HISTORY_LIMIT) {
+    newHistory.splice(HISTORY_LIMIT, 1);
+  }
+
+  app.utils.localStorageSet(historyKey, newHistory);
+
+  return newHistory;
+};
+
+const updateQueryIndexTemplates = ({ queryIndexTemplates }, value, label) => {
+  const newTemplates = queryIndexTemplates.slice();
+  const templateItem = createSelectItem(value);
+  templateItem.label = label || templateItem.label;
+
+  const existing = newTemplates.find(i => i.value === templateItem.value);
+  if (!existing) {
+    newTemplates.push(templateItem);
+  }
+};
+
+export default function mangoquery(state = initialState, action) {
+  const { options } = action;
+  switch (action.type) {
+
+    case ActionTypes.MANGO_SET_DB:
+      return {
+        ...state,
+        database: options.database,
+        historyKey: options.database ? options.database.id : 'default'
+      };
+
+    case ActionTypes.MANGO_LOAD_QUERY_HISTORY:
+      const hist = loadQueryHistory(options.databaseName);
+      return {
+        ...state,
+        history: hist,
+        historyKey: options.databaseName + '_queryhistory',
+        queryFindCode: JSON.parse(hist[0].value),
+      };
+
+    case ActionTypes.MANGO_NEW_QUERY_FIND_CODE:
+      return {
+        ...state,
+        queryFindCode: options.queryCode,
+        history: updateQueryHistory(state, options.queryCode)
+      };
+
+    case ActionTypes.MANGO_RESET:
+      return {
+        ...state,
+        getLoadingIndexes: options.isLoading
+      };
+
+    case ActionTypes.MANGO_LOAD_INDEX_TEMPLATES:
+      const templates = loadIndexTemplates();
+      return {
+        ...state,
+        queryIndexTemplates: templates,
+        queryIndexCode: JSON.parse(templates[0].value),
+      };
+
+    case ActionTypes.MANGO_NEW_QUERY_CREATE_INDEX_TEMPLATE:
+      return {
+        ...state,
+        queryIndexCode: options.code,
+        queryIndexTemplates: updateQueryIndexTemplates(state, options.code, options.label)
+      };
+
+    case ActionTypes.MANGO_SHOW_EXPLAIN_RESULTS:
+      return {
+        ...state,
+        explainPlan: options.explainPlan
+      };
+
+    case ActionTypes.MANGO_HIDE_EXPLAIN_RESULTS:
+      return {
+        ...state,
+        explainPlan: false
+      };
+
+    default:
+      return state;
+  }
+};
diff --git a/app/addons/documents/mango/mango.stores.js b/app/addons/documents/mango/mango.stores.js
deleted file mode 100644
index c1885ad..0000000
--- a/app/addons/documents/mango/mango.stores.js
+++ /dev/null
@@ -1,168 +0,0 @@
-// Licensed under the Apache License, Version 2.0 (the "License"); you may not
-// use this file except in compliance with the License. You may obtain a copy of
-// the License at
-//
-//   http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-// License for the specific language governing permissions and limitations under
-// the License.
-
-import app from "../../../app";
-import FauxtonAPI from "../../../core/api";
-import ActionTypes from "./mango.actiontypes";
-import IndexActionTypes from "../index-results/actiontypes";
-import constants from "./mango.constants";
-
-var defaultQueryFindCode = {
-  "selector": {
-    "_id": {"$gt": null}
-  }
-};
-
-var Stores = {};
-
-const HISTORY_LIMIT = 5;
-
-Stores.MangoStore = FauxtonAPI.Store.extend({
-
-  initialize: function () {
-    this.setHistoryKey('default');
-    this.queryIndexTemplates = [];
-  },
-
-  getQueryIndexCode: function () {
-    return this.getQueryIndexTemplates()[0].value;
-  },
-
-  getQueryFindCode: function () {
-    return this.getHistory()[0].value;
-  },
-
-  formatCode: function (code) {
-    return JSON.stringify(code, null, 3);
-  },
-
-  setDatabase: function (options) {
-    this._database = options.database;
-    this.setHistoryKey(options.database.id);
-  },
-
-  getDatabase: function () {
-    return this._database;
-  },
-
-  setExplainPlan: function (options) {
-    this._explainPlan = options && options.explainPlan;
-  },
-
-  getExplainPlan: function() {
-    return this._explainPlan;
-  },
-
-  getHistoryKey: function() {
-    return this._historyKey;
-  },
-
-  setHistoryKey: function(key) {
-    this._historyKey = key + '_queryhistory';
-  },
-
-  createSelectItem: function(queryObject) {
-    // ensure we're working with a deserialized query object
-    const object = typeof queryObject === "string" ? JSON.parse(queryObject) : queryObject;
-
-    const singleLineValue = JSON.stringify(object);
-    const multiLineValue = this.formatCode(object);
-
-    return {
-      label: singleLineValue,
-      value: multiLineValue,
-      className: 'mango-select-entry'
-    };
-  },
-
-  addQueryHistory: function (value, label) {
-    var history = this.getHistory();
-
-    const historyEntry = this.createSelectItem(value);
-    historyEntry.label = label || historyEntry.label;
-
-    // remove existing entry if it exists
-    var indexOfExisting = history.findIndex(i => i.value === historyEntry.value);
-    if (indexOfExisting > -1) {
-      history.splice(indexOfExisting, 1);
-  }
-
-    // insert item at head of array
-    history.splice(0, 0, historyEntry);
-
-    // limit array length
-    if (history.length > HISTORY_LIMIT) {
-      history.splice(HISTORY_LIMIT, 1);
-    }
-
-    app.utils.localStorageSet(this.getHistoryKey(), history);
-  },
-
-  getDefaultHistory: function () {
-    return [this.createSelectItem(defaultQueryFindCode)];
-  },
-
-  getHistory: function () {
-    return app.utils.localStorageGet(this.getHistoryKey()) || this.getDefaultHistory();
-  },
-
-  addQueryIndexTemplate: function (value, label) {
-    const templateItem = this.createSelectItem(value);
-    templateItem.label = label || templateItem.label;
-
-    var existing = this.queryIndexTemplates.find(i => i.value === templateItem.value);
-    if (!existing) {
-      this.queryIndexTemplates.push(templateItem);
-    }
-  },
-
-  getQueryIndexTemplates: function () {
-    if (!this.queryIndexTemplates.length) {
-      constants.INDEX_TEMPLATES.forEach(i => this.addQueryIndexTemplate(i.code, i.label));
-    }
-
-    return this.queryIndexTemplates;
-  },
-
-  dispatch: function (action) {
-    switch (action.type) {
-
-      case ActionTypes.MANGO_SET_DB:
-        this.setDatabase(action.options);
-      break;
-
-      case ActionTypes.MANGO_NEW_QUERY_CREATE_INDEX_TEMPLATE:
-        this.addQueryIndexTemplate(action.options.code, action.options.label);
-      break;
-
-      case ActionTypes.MANGO_NEW_QUERY_FIND_CODE:
-        this.addQueryHistory(action.options.code);
-      break;
-
-      case ActionTypes.MANGO_EXPLAIN_RESULTS:
-        this.setExplainPlan(action.options);
-        break;
-
-      case IndexActionTypes.INDEX_RESULTS_CLEAR_RESULTS:
-        this.setExplainPlan(false);
-        break;
-    }
-
-    this.triggerChange();
-  }
-});
-
-Stores.mangoStore = new Stores.MangoStore();
-
-Stores.mangoStore.dispatchToken = FauxtonAPI.dispatcher.register(Stores.mangoStore.dispatch);
-
-export default Stores;
diff --git a/app/addons/documents/mango/tests/mango.componentsSpec.js b/app/addons/documents/mango/tests/mango.componentsSpec.js
deleted file mode 100644
index 599c794..0000000
--- a/app/addons/documents/mango/tests/mango.componentsSpec.js
+++ /dev/null
@@ -1,83 +0,0 @@
-// Licensed under the Apache License, Version 2.0 (the "License"); you may not
-// use this file except in compliance with the License. You may obtain a copy of
-// the License at
-//
-//   http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-// License for the specific language governing permissions and limitations under
-// the License.
-
-import FauxtonAPI from "../../../../core/api";
-import Views from "../mango.components";
-import MangoActions from "../mango.actions";
-import ActionTypes from "../mango.actiontypes";
-import Resources from "../../resources";
-import Databases from "../../../databases/resources";
-import utils from "../../../../../test/mocha/testUtils";
-import React from "react";
-import ReactDOM from "react-dom";
-
-var assert = utils.assert;
-
-describe('Mango QueryEditor', function () {
-  var database = new Databases.Model({id: 'testdb'}),
-      container,
-      editor,
-      mangoCollection;
-
-  beforeEach(function () {
-    container = document.createElement('div');
-    MangoActions.setDatabase({
-      database: database
-    });
-
-    mangoCollection = new Resources.MangoIndexCollection([{
-      ddoc: '_design/e4d338e5d6f047749f5399ab998b4fa04ba0c816',
-      def: {
-        fields: [
-          {'_id': 'asc'},
-          {'foo': 'bar'},
-          {'ente': 'gans'}
-        ]
-      },
-      name: 'e4d338e5d6f047749f5399ab998b4fa04ba0c816',
-      type: 'json'
-    }, {
-      ddoc: null,
-      def: {
-        fields: [{
-          '_id': 'asc'
-        }]
-      },
-      name: '_all_docs',
-      type: 'special'
-    }], {
-      params: {},
-      database: {
-        safeID: function () { return '1'; }
-      }
-    });
-
-    FauxtonAPI.dispatch({
-      type: ActionTypes.MANGO_NEW_AVAILABLE_INDEXES,
-      options: {indexList: mangoCollection}
-    });
-
-  });
-
-  afterEach(function () {
-    ReactDOM.unmountComponentAtNode(container);
-  });
-
-  it('has a default query', function () {
-    editor = ReactDOM.render(
-      <Views.MangoQueryEditorController description="foo" />,
-      container
-    );
-    var json = JSON.parse(editor.getMangoEditor().getEditorValue());
-    assert.equal(Object.keys(json.selector)[0], '_id');
-  });
-});
diff --git a/app/addons/documents/mangolayout.js b/app/addons/documents/mangolayout.js
index 460e069..4973f7a 100644
--- a/app/addons/documents/mangolayout.js
+++ b/app/addons/documents/mangolayout.js
@@ -11,24 +11,25 @@
 // the License.
 
 import React, { Component } from 'react';
+import { connect } from 'react-redux';
 import app from "../../app";
-import ReactPagination from "./pagination/pagination";
-import {Breadcrumbs} from '../components/header-breadcrumbs';
-import {NotificationCenterButton} from '../fauxton/notifications/notifications';
-import {ApiBarWrapper} from '../components/layouts';
+import { Breadcrumbs } from '../components/header-breadcrumbs';
+import { NotificationCenterButton } from '../fauxton/notifications/notifications';
 import MangoComponents from "./mango/mango.components";
-import IndexResultsComponents from "./index-results/index-results.components";
-import Stores from "./mango/mango.stores";
+import * as MangoAPI from "./mango/mango.api";
+import IndexResultsContainer from './index-results/containers/IndexResultsContainer';
+import PaginationContainer from './index-results/containers/PaginationContainer';
+import ApiBarContainer from './index-results/containers/ApiBarContainer';
 import FauxtonAPI from "../../core/api";
+import Constants from './constants';
 
-const mangoStore = Stores.mangoStore;
-
-export const RightHeader = ({docURL, endpoint}) => {
+export const RightHeader = ({ docURL, endpoint }) => {
+  const apiBar = <ApiBarContainer docURL={docURL} endpoint={endpoint} />;
   return (
     <div className="right-header-wrapper flex-layout flex-row flex-body">
       <div id="right-header" className="flex-body">
       </div>
-      <ApiBarWrapper docURL={docURL} endpoint={endpoint} />
+      {apiBar}
       <div id='notification-center-btn'>
         <NotificationCenterButton />
       </div>
@@ -36,19 +37,22 @@ export const RightHeader = ({docURL, endpoint}) => {
   );
 };
 
-export const MangoFooter = () => {
+export const MangoFooter = ({databaseName, fetchUrl, queryDocs}) => {
   return (
     <div id="footer">
-        <ReactPagination.Footer />
+      <PaginationContainer
+        databaseName={databaseName}
+        fetchUrl={fetchUrl}
+        queryDocs={queryDocs} />
     </div>
   );
 };
 
-export const MangoHeader = ({crumbs, docURL, endpoint}) => {
+export const MangoHeader = ({ crumbs, docURL, endpoint }) => {
   return (
     <div className="header-wrapper flex-layout flex-row">
       <div className='flex-body faux__breadcrumbs-mango-header'>
-        <Breadcrumbs crumbs={crumbs}/>
+        <Breadcrumbs crumbs={crumbs} />
       </div>
       <RightHeader
         docURL={docURL}
@@ -62,17 +66,32 @@ MangoHeader.defaultProps = {
   crumbs: []
 };
 
-export const MangoContent = ({edit, designDocs, explainPlan}) => {
-  const leftContent = edit ? <MangoComponents.MangoIndexEditorController
+export const MangoContent = ({ edit, designDocs, explainPlan, databaseName, fetchUrl, queryDocs, docType }) => {
+  const leftContent = edit ?
+    <MangoComponents.MangoIndexEditorContainer
       description={app.i18n.en_US['mango-descripton-index-editor']}
-    /> : <MangoComponents.MangoQueryEditorController
+      databaseName={databaseName}
+    /> :
+    <MangoComponents.MangoQueryEditorContainer
       description={app.i18n.en_US['mango-descripton']}
       editorTitle={app.i18n.en_US['mango-title-editor']}
       additionalIndexesText={app.i18n.en_US['mango-additional-indexes-heading']}
+      databaseName={databaseName}
     />;
 
-  let resultsPage = <IndexResultsComponents.List designDocs={designDocs} />;
-  let mangoFooter = <MangoFooter />;
+  let resultsPage = <IndexResultsContainer
+                      fetchUrl={fetchUrl}
+                      designDocs={designDocs}
+                      ddocsOnly={false}
+                      databaseName={databaseName}
+                      fetchAtStartup={false}
+                      queryDocs={queryDocs}
+                      docType={docType} />;
+
+  let mangoFooter = <MangoFooter
+                      databaseName={databaseName}
+                      fetchUrl={fetchUrl}
+                      queryDocs={queryDocs} />;
 
   if (explainPlan) {
     resultsPage = <MangoComponents.ExplainPage explainPlan={explainPlan} />;
@@ -82,7 +101,7 @@ export const MangoContent = ({edit, designDocs, explainPlan}) => {
   return (
     <div id="two-pane-content" className="flex-layout flex-row flex-body">
       <div id="left-content" className="flex-body">
-          {leftContent}
+        {leftContent}
       </div>
       <div id="right-content" className="flex-body flex-layout flex-col">
         <div id="dashboard-lower-content" className="flex-body">
@@ -94,38 +113,24 @@ export const MangoContent = ({edit, designDocs, explainPlan}) => {
   );
 };
 
-export class MangoLayout extends Component {
-  constructor (props) {
+class MangoLayout extends Component {
+  constructor(props) {
     super(props);
-    this.state = this.getStoreState();
-  };
-
-  getStoreState () {
-    return {
-      explainPlan: mangoStore.getExplainPlan()
-    };
-  };
-
-  componentDidMount () {
-    mangoStore.on('change', this.onChange, this);
-  };
-
-  componentWillUnmount () {
-    mangoStore.off('change', this.onChange, this);
   };
 
-  onChange () {
-    this.setState(this.getStoreState());
-  };
-
-  render () {
-    const {database, edit, docURL, crumbs, designDocs} = this.props;
+  render() {
+    const { database, edit, docURL, crumbs, designDocs, fetchUrl, databaseName, queryFindCode } = this.props;
     let endpoint = this.props.endpoint;
 
-    if (this.state.explainPlan) {
+    if (this.props.explainPlan) {
       endpoint = FauxtonAPI.urls('mango', 'explain-apiurl', database);
     }
-
+    let queryFunction = (params) => { return MangoAPI.mangoQueryDocs(databaseName, queryFindCode, params); };
+    let docType = Constants.INDEX_RESULTS_DOC_TYPE.MANGO_QUERY;
+    if (edit) {
+      queryFunction = (params) => { return MangoAPI.fetchIndexes(databaseName, params); };
+      docType = Constants.INDEX_RESULTS_DOC_TYPE.MANGO_INDEX;
+    }
     return (
       <div id="dashboard" className="two-pane flex-layout flex-col">
         <MangoHeader
@@ -133,11 +138,27 @@ export class MangoLayout extends Component {
           endpoint={endpoint}
           crumbs={crumbs}
         />
-      <MangoContent
-        edit={edit}
-        designDocs={designDocs}
-        explainPlan={this.state.explainPlan} />
+        <MangoContent
+          edit={edit}
+          designDocs={designDocs}
+          explainPlan={this.props.explainPlan}
+          databaseName={databaseName}
+          fetchUrl={fetchUrl}
+          queryDocs={queryFunction}
+          docType={docType}
+          />
       </div>
     );
   }
 };
+
+const mapStateToProps = ({ mangoQuery }) => {
+  return {
+    explainPlan: mangoQuery.explainPlan,
+    queryFindCode: mangoQuery.queryFindCode
+  };
+};
+
+export const MangoLayoutContainer = connect(
+  mapStateToProps
+)(MangoLayout);
diff --git a/app/addons/documents/resources.js b/app/addons/documents/resources.js
index 6323711..8a19d09 100644
--- a/app/addons/documents/resources.js
+++ b/app/addons/documents/resources.js
@@ -14,6 +14,7 @@ import app from "../../app";
 import FauxtonAPI from "../../core/api";
 import Documents from "./shared-resources";
 import PagingCollection from "../../../assets/js/plugins/cloudant.pagingcollection";
+import Constants from './constants';
 
 
 Documents.UUID = FauxtonAPI.Model.extend({
@@ -133,7 +134,7 @@ Documents.MangoIndexCollection = PagingCollection.extend({
     this.params = _.extend({limit: defaultLimit}, options.params);
   },
 
-  collectionType: 'MangoIndex',
+  collectionType: Constants.INDEX_RESULTS_DOC_TYPE.MANGO_INDEX,
 
   url: function () {
     return this.urlRef.apply(this, arguments);
diff --git a/app/addons/documents/routes-index-editor.js b/app/addons/documents/routes-index-editor.js
index 3c6fbd3..84485e2 100644
--- a/app/addons/documents/routes-index-editor.js
+++ b/app/addons/documents/routes-index-editor.js
@@ -20,6 +20,7 @@ import IndexResultsStores from "./index-results/stores";
 import IndexResultsActions from "./index-results/actions";
 import SidebarActions from "./sidebar/actions";
 import {DocsTabsSidebarLayout, ViewsTabsSidebarLayout} from './layouts';
+import Constants from './constants';
 
 const IndexEditorAndResults = BaseRoute.extend({
   routes: {
@@ -79,7 +80,7 @@ const IndexEditorAndResults = BaseRoute.extend({
 
     IndexResultsActions.newResultsList({
       collection: this.indexedDocs,
-      typeOfIndex: 'view',
+      typeOfIndex: Constants.INDEX_RESULTS_DOC_TYPE.VIEW,
       bulkCollection: new Documents.BulkDeleteDocCollection([], { databaseId: this.database.safeID() }),
     });
 
diff --git a/app/addons/documents/routes-mango.js b/app/addons/documents/routes-mango.js
index 16995bf..8205c6e 100644
--- a/app/addons/documents/routes-mango.js
+++ b/app/addons/documents/routes-mango.js
@@ -14,14 +14,9 @@ import React from 'react';
 import app from "../../app";
 import FauxtonAPI from "../../core/api";
 import Databases from "../databases/resources";
-import Resources from "./resources";
-import IndexResultsActions from "./index-results/actions";
-import IndexResultStores from "./index-results/stores";
-import PaginationActions from "./pagination/actions";
 import Documents from "./shared-resources";
-import MangoActions from "./mango/mango.actions";
 import SidebarActions from "./sidebar/actions";
-import {MangoLayout} from './mangolayout';
+import {MangoLayoutContainer} from './mangolayout';
 
 const MangoIndexEditorAndQueryEditor = FauxtonAPI.RouteObject.extend({
   selectedHeader: 'Databases',
@@ -42,50 +37,33 @@ const MangoIndexEditorAndQueryEditor = FauxtonAPI.RouteObject.extend({
     var databaseName = options[0];
     this.databaseName = databaseName;
     this.database = new Databases.Model({id: databaseName});
-
-    MangoActions.setDatabase({
-      database: this.database
-    });
   },
 
   findUsingIndex: function (database) {
-    PaginationActions.resetPagination();
-
-    const pageSize = IndexResultStores.indexResultsStore.getPerPage();
-    const mangoResultCollection = new Resources.MangoDocumentCollection(null, {
-      database: this.database,
-      params: {
-        limit: pageSize
-      },
-      paging: {
-        pageSize: pageSize
-      }
-    });
-
     SidebarActions.selectNavItem('mango-query');
 
-    IndexResultsActions.newMangoResultsList({
-      collection: mangoResultCollection,
-      textEmptyIndex: 'No Results',
-      typeOfIndex: 'mango',
-      bulkCollection: new Resources.BulkDeleteDocCollection([], { databaseId: this.database.safeID() }),
-    });
-
     const url = FauxtonAPI.urls(
-      'allDocs', 'app', this.database.safeID(), '?limit=' + FauxtonAPI.constants.DATABASES.DOCUMENT_LIMIT
+      'allDocs', 'app', encodeURIComponent(this.databaseName), '?limit=' + FauxtonAPI.constants.DATABASES.DOCUMENT_LIMIT
     );
 
+    const fetchUrl = '/' + encodeURIComponent(this.databaseName) + '/_find';
+
     const crumbs = [
       {name: database, link: url},
       {name: app.i18n.en_US['mango-title-editor']}
     ];
 
-    return <MangoLayout
+    const endpoint = FauxtonAPI.urls('mango', 'query-apiurl', this.databaseName);
+
+    return <MangoLayoutContainer
       database={database}
       crumbs={crumbs}
       docURL={FauxtonAPI.constants.DOC_URLS.MANGO_SEARCH}
-      endpoint={mangoResultCollection.urlRef('query-apiurl', '')}
+      endpoint={endpoint}
       edit={false}
+
+      databaseName={this.databaseName}
+      fetchUrl={fetchUrl}
     />;
   },
 
@@ -103,36 +81,25 @@ const MangoIndexEditorAndQueryEditor = FauxtonAPI.RouteObject.extend({
       }
     });
 
-    const mangoIndexCollection = new Resources.MangoIndexCollection(null, {
-      database: this.database,
-      params: null,
-      paging: {
-        pageSize: IndexResultStores.indexResultsStore.getPerPage()
-      }
-    });
-
-    IndexResultsActions.newResultsList({
-      collection: mangoIndexCollection,
-      bulkCollection: new Resources.MangoBulkDeleteDocCollection([], { databaseId: this.database.safeID() }),
-      typeOfIndex: 'mango-index'
-    });
-
     const url = FauxtonAPI.urls(
-      'allDocs', 'app', this.database.safeID(), '?limit=' + FauxtonAPI.constants.DATABASES.DOCUMENT_LIMIT
+      'allDocs', 'app', encodeURIComponent(this.databaseName), '?limit=' + FauxtonAPI.constants.DATABASES.DOCUMENT_LIMIT
     );
+    const endpoint = FauxtonAPI.urls('mango', 'index-apiurl', this.databaseName);
 
     const crumbs = [
       {name: database, link: url},
       {name: app.i18n.en_US['mango-indexeditor-title']}
     ];
 
-    return <MangoLayout
+    return <MangoLayoutContainer
       showIncludeAllDocs={false}
       crumbs={crumbs}
       docURL={FauxtonAPI.constants.DOC_URLS.MANGO_INDEX}
-      endpoint={mangoIndexCollection.urlRef('index-apiurl', '')}
+      endpoint={endpoint}
       edit={true}
       designDocs={designDocs}
+
+      databaseName={this.databaseName}
     />;
   }
 });
diff --git a/app/addons/documents/tests/nightwatch/mangoIndex.js b/app/addons/documents/tests/nightwatch/mangoIndex.js
index 424466b..0de1ffc 100644
--- a/app/addons/documents/tests/nightwatch/mangoIndex.js
+++ b/app/addons/documents/tests/nightwatch/mangoIndex.js
@@ -56,6 +56,7 @@ module.exports = {
       .populateDatabase(newDatabaseName)
       .loginToGUI()
       .url(baseUrl + '/#/database/' + newDatabaseName + '/_index')
+      .waitForElementPresent('#doc-list', waitTime, false)
       .assert.containsText('#dashboard-lower-content', 'ente_ente_mango_ananas')
       .clickWhenVisible('.bulk-action-component-panel input[type="checkbox"]')
       .clickWhenVisible('.bulk-action-component-selector-group button.fonticon-trash', waitTime, false)
diff --git a/app/addons/documents/tests/nightwatch/mangoQuery.js b/app/addons/documents/tests/nightwatch/mangoQuery.js
index 596e810..75f7cdd 100644
--- a/app/addons/documents/tests/nightwatch/mangoQuery.js
+++ b/app/addons/documents/tests/nightwatch/mangoQuery.js
@@ -14,7 +14,7 @@
 
 module.exports = {
 
-  'Finding things with with mango': function (client) {
+  'Finding things with mango': function (client) {
     /*jshint multistr: true */
     var waitTime = 10000,
         newDatabaseName = client.globals.testDatabaseName,
@@ -23,7 +23,7 @@ module.exports = {
     client
       .populateDatabase(newDatabaseName)
       .loginToGUI()
-      .url(baseUrl + '/#/database/' + newDatabaseName + '/_find')
+      .url(baseUrl + '/#database/' + newDatabaseName + '/_find')
       .waitForElementPresent('.watermark-logo', waitTime, false)
       .execute('\
         var json = \'{\
diff --git a/app/core/base.js b/app/core/base.js
index 8e49be3..4b1871a 100644
--- a/app/core/base.js
+++ b/app/core/base.js
@@ -167,4 +167,12 @@ FauxtonAPI.addReducers = (reducers) => {
   };
 };
 
+FauxtonAPI.middlewares = [];
+FauxtonAPI.addMiddleware = (middleware) => {
+  // Basic validation
+  if (middleware && typeof middleware === 'function') {
+    FauxtonAPI.middlewares.push(middleware);
+  }
+};
+
 export default FauxtonAPI;
diff --git a/app/main.js b/app/main.js
index 27bad1e..acf1f21 100644
--- a/app/main.js
+++ b/app/main.js
@@ -23,11 +23,10 @@ import { createStore, applyMiddleware, combineReducers } from 'redux';
 import thunk from 'redux-thunk';
 import { Provider } from 'react-redux';
 
-const middlewares = [thunk];
-
+FauxtonAPI.addMiddleware(thunk);
 const store = createStore(
   combineReducers(FauxtonAPI.reducers),
-  applyMiddleware(...middlewares)
+  applyMiddleware(...FauxtonAPI.middlewares)
 );
 
 app.addons = LoadAddons;

-- 
To stop receiving notification emails like this one, please contact
['"commits@couchdb.apache.org" <co...@couchdb.apache.org>'].