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>'].