Update documents/index-editor to use Redux (#1151)
* Use redux
* Update tests
diff --git a/app/addons/documents/base.js b/app/addons/documents/base.js
index cff9a82..21515eb 100644
--- a/app/addons/documents/base.js
+++ b/app/addons/documents/base.js
@@ -22,6 +22,7 @@
import revisionBrowserReducers from './rev-browser/reducers';
import docEditorReducers from './doc-editor/reducers';
import changesReducers from './changes/reducers';
+import indexEditorReducers from './index-editor/reducers';
import "./assets/less/documents.less";
FauxtonAPI.addReducers({
@@ -32,7 +33,8 @@
partitionKey: partitionKeyReducers,
docEditor: docEditorReducers,
changes: changesReducers,
- designDocInfo: designDocInfoReducers
+ designDocInfo: designDocInfoReducers,
+ indexEditor: indexEditorReducers
});
function getQueryParam (query) {
diff --git a/app/addons/documents/index-editor/__tests__/actions.test.js b/app/addons/documents/index-editor/__tests__/actions.test.js
index cba84f5..c01e469 100644
--- a/app/addons/documents/index-editor/__tests__/actions.test.js
+++ b/app/addons/documents/index-editor/__tests__/actions.test.js
@@ -10,22 +10,20 @@
// License for the specific language governing permissions and limitations under
// the License.
-import FauxtonAPI from "../../../../core/api";
-import Actions from "../actions";
-import Documents from "../../resources";
-import testUtils from "../../../../../test/mocha/testUtils";
-import sinon from "sinon";
-import "../../../documents/base";
-var assert = testUtils.assert;
-var restore = testUtils.restore;
+import FauxtonAPI from '../../../../core/api';
+import Actions from '../actions';
+import Documents from '../../resources';
+import testUtils from '../../../../../test/mocha/testUtils';
+import sinon from 'sinon';
+import '../../../documents/base';
+const restore = testUtils.restore;
FauxtonAPI.router = new FauxtonAPI.Router([]);
-
describe('Index Editor Actions', function () {
describe('delete view', function () {
- var designDocs, database, designDoc, designDocCollection, designDocId, viewName;
+ let designDocs, database, designDoc, designDocCollection, designDocId, viewName;
beforeEach(function () {
FauxtonAPI.reduxDispatch = sinon.stub();
database = {
@@ -61,7 +59,7 @@
designDoc.save = function () {
return FauxtonAPI.Promise.resolve();
};
- var saveSpy = sinon.spy(designDoc, 'save');
+ const saveSpy = sinon.spy(designDoc, 'save');
designDocs.fetch = function () {
return FauxtonAPI.Promise.resolve();
};
@@ -73,7 +71,7 @@
designDoc: designDoc
});
- assert.ok(saveSpy.calledOnce);
+ sinon.assert.calledOnce(saveSpy);
});
it('deletes design doc if has no other views', function () {
@@ -82,7 +80,7 @@
designDoc.destroy = function () {
return FauxtonAPI.Promise.resolve();
};
- var destroySpy = sinon.spy(designDoc, 'destroy');
+ const destroySpy = sinon.spy(designDoc, 'destroy');
designDocs.remove = function () {};
designDocs.fetch = function () {
return FauxtonAPI.Promise.resolve();
@@ -95,11 +93,11 @@
designDoc: designDoc
});
- assert.ok(destroySpy.calledOnce);
+ sinon.assert.calledOnce(destroySpy);
});
it('navigates to all docs if was on view', function () {
- var spy = sinon.spy(FauxtonAPI, 'navigate');
+ const spy = sinon.spy(FauxtonAPI, 'navigate');
designDoc.save = function () {
return FauxtonAPI.Promise.resolve();
@@ -114,8 +112,8 @@
designDoc: designDoc,
isOnIndex: true
}).then(() => {
- assert.ok(spy.getCall(0).args[0].match(/_all_docs/));
- assert.ok(spy.calledOnce);
+ sinon.assert.calledWithMatch(spy, /_all_docs/);
+ sinon.assert.calledOnce(spy);
});
});
@@ -124,27 +122,27 @@
const ddocModel = new Documents.Doc(ddoc, { database: database });
ddocModel.setDdocView('testview', '() => {}', '() => {}');
- assert.deepEqual(ddocModel.get('views'), {
+ expect(ddocModel.get('views')).toEqual({
testview: {
map: '() => {}',
reduce: '() => {}'
}
});
- assert.equal(ddocModel.get('language'), 'javascript');
+ expect(ddocModel.get('language')).toBe('javascript');
});
it('removes old view only when editting', function () {
const viewInfo = {
- newView: false,
+ isNewView: false,
originalDesignDocName: 'test',
designDocId: 'test',
originalViewName: 'foo',
viewName: 'bar'
};
- assert.isTrue(Actions.shouldRemoveDdocView(viewInfo));
+ expect(Actions.shouldRemoveDdocView(viewInfo)).toBe(true);
- viewInfo.newView = true;
- assert.isFalse(Actions.shouldRemoveDdocView(viewInfo));
+ viewInfo.isNewView = true;
+ expect(Actions.shouldRemoveDdocView(viewInfo)).toBe(false);
});
});
});
diff --git a/app/addons/documents/index-editor/__tests__/viewIndex.components.test.js b/app/addons/documents/index-editor/__tests__/components.test.js
similarity index 62%
rename from app/addons/documents/index-editor/__tests__/viewIndex.components.test.js
rename to app/addons/documents/index-editor/__tests__/components.test.js
index 0c07d41..fde338c 100644
--- a/app/addons/documents/index-editor/__tests__/viewIndex.components.test.js
+++ b/app/addons/documents/index-editor/__tests__/components.test.js
@@ -9,81 +9,42 @@
// 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 Resources from "../../resources";
-import Views from "../components";
-import Actions from "../actions";
-import utils from "../../../../../test/mocha/testUtils";
-import React from "react";
-import ReactDOM from "react-dom";
+
+import React from 'react';
import {mount} from 'enzyme';
-import sinon from "sinon";
+import sinon from 'sinon';
+import FauxtonAPI from '../../../../core/api';
+import Views from '../components';
import '../../base';
+
FauxtonAPI.router = new FauxtonAPI.Router([]);
-const { assert } = utils;
-
-
-const resetStore = (designDocs) => {
- Actions.editIndex({
- database: { id: 'rockos-db' },
- newView: false,
- viewName: 'test-view',
- designDocs: getDesignDocsCollection(designDocs),
- designDocId: designDocs[0]._id
- });
-};
-
-const getDesignDocsCollection = (designDocs) => {
- designDocs = designDocs.map(function (doc) {
- return Resources.Doc.prototype.parse(doc);
- });
-
- return new Resources.AllDocs(designDocs, {
- params: { limit: 10 },
- database: {
- safeID: () => { return 'id'; }
- }
- });
-};
-
-
-describe('reduce editor', () => {
- let reduceEl;
-
+describe('ReduceEditor', () => {
describe('getReduceValue', () => {
+ const defaultProps = {
+ reduceOptions: [],
+ hasReduce: false,
+ hasCustomReduce: false,
+ reduce: null,
+ reduceSelectedOption: 'NONE',
+ updateReduceCode: () => {},
+ selectReduceChanged: () => {}
+ };
it('returns null for none', () => {
- const designDoc = {
- _id: '_design/test-doc',
- views: {
- 'test-view': {
- map: '() => {};'
- }
- }
- };
-
- resetStore([designDoc]);
-
- reduceEl = mount(<Views.ReduceEditor/>);
- assert.ok(_.isNull(reduceEl.instance().getReduceValue()));
+ const reduceEl = mount(<Views.ReduceEditor
+ {...defaultProps}
+ />);
+ expect(reduceEl.instance().getReduceValue()).toBeNull();
});
it('returns built in for built in reduce', () => {
- const designDoc = {
- _id: '_design/test-doc',
- views: {
- 'test-view': {
- map: '() => {};',
- reduce: '_sum'
- }
- }
- };
-
- resetStore([designDoc]);
-
- reduceEl = mount(<Views.ReduceEditor/>);
- assert.equal(reduceEl.instance().getReduceValue(), '_sum');
+ const reduceEl = mount(<Views.ReduceEditor
+ {...defaultProps}
+ reduce='_sum'
+ hasReduce={true}
+ />);
+ expect(reduceEl.instance().getReduceValue()).toBe('_sum');
});
});
@@ -107,7 +68,7 @@
value: '_design/test-doc'
}
});
- assert.ok(spy.calledOnce);
+ sinon.assert.calledOnce(spy);
});
it('shows new design doc field when set to new-doc', () => {
@@ -119,7 +80,7 @@
onChangeNewDesignDocName={() => {}}
/>);
- assert.equal(selectorEl.find('#new-ddoc-section').length, 1);
+ expect(selectorEl.find('#new-ddoc-section').length).toBe(1);
});
it('hides new design doc field when design doc selected', () => {
@@ -131,7 +92,7 @@
onChangeNewDesignDocName={() => {}}
/>);
- assert.equal(selectorEl.find('#new-ddoc-section').length, 0);
+ expect(selectorEl.find('#new-ddoc-section').length).toBe(0);
});
it('always passes validation when design doc selected', () => {
@@ -143,7 +104,7 @@
onChangeNewDesignDocName={() => {}}
/>);
- assert.equal(selectorEl.instance().validate(), true);
+ expect(selectorEl.instance().validate()).toBe(true);
});
it('fails validation if new doc name entered/not entered', () => {
@@ -157,7 +118,7 @@
/>);
// it shouldn't validate at this point: no new design doc name has been entered
- assert.equal(selectorEl.instance().validate(), false);
+ expect(selectorEl.instance().validate()).toBe(false);
});
it('passes validation if new doc name entered/not entered', () => {
@@ -169,7 +130,7 @@
onSelectDesignDoc={() => { }}
onChangeNewDesignDocName={() => {}}
/>);
- assert.equal(selectorEl.instance().validate(), true);
+ expect(selectorEl.instance().validate()).toBe(true);
});
@@ -181,7 +142,7 @@
onSelectDesignDoc={() => { }}
onChangeNewDesignDocName={() => {}}
/>);
- assert.equal(selectorEl.find('.help-link').length, 0);
+ expect(selectorEl.find('.help-link').length).toBe(0);
});
it('includes help doc link when supplied', () => {
@@ -194,27 +155,51 @@
docLink={docLink}
onChangeNewDesignDocName={() => {}}
/>);
- assert.equal(selectorEl.find('.help-link').length, 1);
- assert.equal(selectorEl.find('.help-link').prop('href'), docLink);
+ expect(selectorEl.find('.help-link').length).toBe(1);
+ expect(selectorEl.find('.help-link').prop('href')).toBe(docLink);
});
});
-
-describe('Editor', () => {
- let editorEl;
-
- beforeEach(() => {
- editorEl = mount(<Views.EditorController />);
- });
+describe('IndexEditor', () => {
+ const defaultProps = {
+ isLoading: false,
+ isNewView: false,
+ isNewDesignDoc: false,
+ viewName: '',
+ database: {},
+ originalViewName: '',
+ originalDesignDocName: '',
+ designDoc: {},
+ designDocId: '',
+ designDocList: [],
+ map: '',
+ reduce: '',
+ designDocs: {},
+ updateNewDesignDocName: () => {},
+ updateMapCode: () => {},
+ selectDesignDoc: () => {},
+ onChangeNewDesignDocName: () => {},
+ reduceOptions: [],
+ reduceSelectedOption: 'NONE',
+ hasReduce: false,
+ hasCustomReduce: false,
+ updateReduceCode: () => {},
+ selectReduceChanged: () => {}
+ };
it('calls changeViewName on view name change', () => {
- const viewName = 'new-name';
- const spy = sinon.spy(Actions, 'changeViewName');
+ const spy = sinon.spy();
+ const editorEl = mount(<Views.IndexEditor
+ {...defaultProps}
+ viewName='new-name'
+ changeViewName={spy}
+ />);
+
editorEl.find('#index-name').simulate('change', {
target: {
- value: viewName
+ value: 'newViewName'
}
});
- assert.ok(spy.calledWith(viewName));
+ sinon.assert.calledWith(spy, 'newViewName');
});
});
diff --git a/app/addons/documents/index-editor/__tests__/reducers.test.js b/app/addons/documents/index-editor/__tests__/reducers.test.js
new file mode 100644
index 0000000..ee60b4e
--- /dev/null
+++ b/app/addons/documents/index-editor/__tests__/reducers.test.js
@@ -0,0 +1,299 @@
+// 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 Documents from '../../../documents/resources';
+import reducer, { hasCustomReduce, getDesignDocIds } from '../reducers';
+import ActionTypes from '../actiontypes';
+import '../../base';
+
+describe('IndexEditor Reducer', () => {
+ describe('map editor', () => {
+ it('new view is assigned the default map code', () => {
+ const action = {
+ type: ActionTypes.EDIT_NEW_INDEX,
+ options: {
+ isNewView: true,
+ //designDocs: {find: () => { return { dDocModel: () => {}}; }}
+ }
+ };
+ const newState = reducer(undefined, action);
+ expect(newState.view.map).toBe('function (doc) {\n emit(doc._id, 1);\n}');
+ });
+ });
+
+ describe('reduce editor', () => {
+ describe('hasCustomReduce', () => {
+ it('is false for no reduce', () => {
+ const designDoc = {
+ _id: '_design/test-doc',
+ views: {
+ 'test-view': {
+ map: '() => {};'
+ }
+ }
+ };
+ const designDocs = new Documents.AllDocs([designDoc], {
+ params: { limit: 10 },
+ database: {
+ safeID: () => { return 'id';}
+ }
+ });
+ const action = {
+ type: ActionTypes.EDIT_NEW_INDEX,
+ options: {
+ isNewView: false,
+ viewName: 'test-view',
+ designDocs: designDocs,
+ designDocId: designDoc._id
+ }
+ };
+ const newState = reducer(undefined, action);
+ expect(hasCustomReduce(newState)).toBe(false);
+ });
+
+ it('is false for built in reduce', () => {
+ const designDoc = {
+ _id: '_design/test-doc',
+ views: {
+ 'test-view': {
+ map: '() => {};',
+ reduce: '_sum'
+ }
+ }
+ };
+ const designDocs = new Documents.AllDocs([designDoc], {
+ params: { limit: 10 },
+ database: {
+ safeID: () => { return 'id';}
+ }
+ });
+ const action = {
+ type: ActionTypes.EDIT_NEW_INDEX,
+ options: {
+ isNewView: false,
+ viewName: 'test-view',
+ designDocs: designDocs,
+ designDocId: designDoc._id
+ }
+ };
+ const newState = reducer(undefined, action);
+ expect(hasCustomReduce(newState)).toBe(false);
+ });
+
+ it('is true for custom reduce', () => {
+ const designDoc = {
+ _id: '_design/test-doc',
+ views: {
+ 'test-view': {
+ map: '() => {};',
+ reduce: 'function (reduce) { reduce(); }'
+ }
+ }
+ };
+ const designDocs = new Documents.AllDocs([designDoc], {
+ params: { limit: 10 },
+ database: {
+ safeID: () => { return 'id';}
+ }
+ });
+ const action = {
+ type: ActionTypes.EDIT_NEW_INDEX,
+ options: {
+ isNewView: false,
+ viewName: 'test-view',
+ designDocs: designDocs,
+ designDocId: designDoc._id
+ }
+ };
+
+ const newState = reducer(undefined, action);
+ expect(hasCustomReduce(newState)).toBe(true);
+ });
+ });
+
+ describe('SELECT_REDUCE_CHANGE', () => {
+ const designDoc = {
+ _id: '_design/test-doc',
+ views: {
+ 'test-view': {
+ map: '() => {};'
+ }
+ }
+ };
+ const designDocs = new Documents.AllDocs([designDoc], {
+ params: { limit: 10 },
+ database: {
+ safeID: () => { return 'id';}
+ }
+ });
+ const editAction = {
+ type: ActionTypes.EDIT_NEW_INDEX,
+ options: {
+ isNewView: false,
+ viewName: 'test-view',
+ designDocs: designDocs,
+ designDocId: designDoc._id
+ }
+ };
+
+ it('NONE returns null reduce', () => {
+ let newState = reducer(undefined, editAction);
+ const selectReduceaction = {
+ type: ActionTypes.SELECT_REDUCE_CHANGE,
+ reduceSelectedOption: 'NONE'
+ };
+ newState = reducer(newState, selectReduceaction);
+ expect(newState.view.reduce).toBe('');
+ });
+
+ it('builtin returns builtin reduce', () => {
+ let newState = reducer(undefined, editAction);
+ const selectReduceAction = {
+ type: ActionTypes.SELECT_REDUCE_CHANGE,
+ reduceSelectedOption: '_sum'
+ };
+ newState = reducer(newState, selectReduceAction);
+ expect(newState.view.reduce).toBe('_sum');
+ });
+
+ it('custom returns custom reduce', () => {
+ let newState = reducer(undefined, editAction);
+ const selectReduceAction = {
+ type: ActionTypes.SELECT_REDUCE_CHANGE,
+ reduceSelectedOption: 'CUSTOM'
+ };
+ newState = reducer(newState, selectReduceAction);
+ expect(newState.view.reduce).toBe('function (keys, values, rereduce) {\n if (rereduce) {\n return sum(values);\n } else {\n return values.length;\n }\n}');
+ });
+ });
+ });
+
+ describe('design doc selector', () => {
+ const designDoc = {
+ _id: '_design/test-doc',
+ views: {
+ 'test-view': {
+ map: 'boom'
+ }
+ }
+ };
+
+ const mangoDoc = {
+ "_id": "_design/123mango",
+ "id": "_design/123mango",
+ "key": "_design/123mango",
+ "value": {
+ "rev": "20-9e4bc8b76fd7d752d620bbe6e0ea9a80"
+ },
+ "doc": {
+ "_id": "_design/123mango",
+ "_rev": "20-9e4bc8b76fd7d752d620bbe6e0ea9a80",
+ "views": {
+ "test-view": {
+ "map": "function(doc) {\n emit(doc._id, 2);\n}"
+ },
+ "new-view": {
+ "map": "function(doc) {\n if (doc.class === \"mammal\" && doc.diet === \"herbivore\")\n emit(doc._id, 1);\n}",
+ "reduce": "_sum"
+ }
+ },
+ "language": "query",
+ "indexes": {
+ "newSearch": {
+ "analyzer": "standard",
+ "index": "function(doc){\n index(\"default\", doc._id);\n}"
+ }
+ }
+ }
+ };
+
+ const designDocArray = _.map([designDoc, mangoDoc], (doc) => {
+ return Documents.Doc.prototype.parse(doc);
+ });
+
+ const designDocs = new Documents.AllDocs(designDocArray, {
+ params: { limit: 10 },
+ database: {
+ safeID: () => { return 'id'; }
+ }
+ });
+
+ const editAction = {
+ type: ActionTypes.EDIT_INDEX,
+ options: {
+ isNewView: false,
+ viewName: 'test-view',
+ designDocs: designDocs,
+ designDocId: designDoc._id
+ }
+ };
+
+ it('DESIGN_DOC_CHANGE changes design doc id', () => {
+ let newState = reducer(undefined, editAction);
+ const designDocId = 'another-one';
+ const ddocChangeAction = {
+ type: ActionTypes.DESIGN_DOC_CHANGE,
+ options: {
+ value: designDocId
+ }
+ };
+ newState = reducer(newState, ddocChangeAction);
+ expect(newState.designDocId).toBe(designDocId);
+ });
+
+ it('only filters mango docs', () => {
+ const newState = reducer(undefined, editAction);
+ const designDocs = getDesignDocIds(newState);
+
+ expect(designDocs.length).toBe(1);
+ expect(designDocs[0]).toBe('_design/test-doc');
+ });
+ });
+
+ describe('EDIT_INDEX', () => {
+ const designDoc = {
+ _id: '_design/test-doc',
+ views: {
+ 'test-view': {
+ map: 'boom'
+ }
+ }
+ };
+ const designDocs = new Documents.AllDocs([designDoc], {
+ params: { limit: 10 },
+ database: {
+ safeID: () => { return 'id';}
+ }
+ });
+
+ it('can set reduce for new design doc', () => {
+ const editAction = {
+ type: ActionTypes.EDIT_INDEX,
+ options: {
+ isNewView: true,
+ isNewDesignDoc: true,
+ viewName: 'test-view',
+ designDocs: designDocs,
+ designDocId: undefined
+ }
+ };
+ let newState = reducer(undefined, editAction);
+
+ const selectReduceAction = {
+ type: ActionTypes.SELECT_REDUCE_CHANGE,
+ reduceSelectedOption: '_sum'
+ };
+ newState = reducer(newState, selectReduceAction);
+ expect(newState.view.reduce).toBe('_sum');
+ });
+ });
+});
diff --git a/app/addons/documents/index-editor/__tests__/stores.test.js b/app/addons/documents/index-editor/__tests__/stores.test.js
deleted file mode 100644
index 2e1e6e7..0000000
--- a/app/addons/documents/index-editor/__tests__/stores.test.js
+++ /dev/null
@@ -1,337 +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 Stores from "../stores";
-import ActionTypes from "../actiontypes";
-import Documents from "../../../documents/resources";
-import testUtils from "../../../../../test/mocha/testUtils";
-import '../../base';
-const assert = testUtils.assert;
-let store;
-let dispatchToken;
-
-describe('IndexEditorStore', () => {
-
- beforeEach(() => {
- store = new Stores.IndexEditorStore();
- dispatchToken = FauxtonAPI.dispatcher.register(store.dispatch.bind(store));
- });
-
- afterEach(() => {
- FauxtonAPI.dispatcher.unregister(dispatchToken);
- });
-
- describe('map editor', () => {
-
- describe('new view', () => {
-
- beforeEach(() => {
- FauxtonAPI.dispatch({
- type: ActionTypes.EDIT_NEW_INDEX,
- options: {
- newView: true
- }
- });
- });
-
- it('returns default map', () => {
- assert.equal(store.getMap(), 'function (doc) {\n emit(doc._id, 1);\n}');
- });
- });
-
- });
-
- describe('reduce editor', () => {
- describe('has custom reduce', () => {
- it('is false for no reduce', () => {
- const designDoc = {
- _id: '_design/test-doc',
- views: {
- 'test-view': {
- map: '() => {};'
- }
- }
- };
-
- const designDocs = new Documents.AllDocs([designDoc], {
- params: { limit: 10 },
- database: {
- safeID: () => { return 'id';}
- }
- });
-
- FauxtonAPI.dispatch({
- type: ActionTypes.EDIT_NEW_INDEX,
- options: {
- newView: false,
- viewName: 'test-view',
- designDocs: designDocs,
- designDocId: designDoc._id
- }
- });
-
- assert.notOk(store.hasCustomReduce());
- });
-
- it('is false for built in reduce', () => {
- const designDoc = {
- _id: '_design/test-doc',
- views: {
- 'test-view': {
- map: '() => {};',
- reduce: '_sum'
- }
- }
- };
-
- const designDocs = new Documents.AllDocs([designDoc], {
- params: { limit: 10 },
- database: {
- safeID: () => { return 'id';}
- }
- });
- FauxtonAPI.dispatch({
- type: ActionTypes.EDIT_NEW_INDEX,
- options: {
- newView: false,
- viewName: 'test-view',
- designDocs: designDocs,
- designDocId: designDoc._id
- }
- });
-
- assert.notOk(store.hasCustomReduce());
- });
-
- it('is true for custom reduce', () => {
- const designDoc = {
- _id: '_design/test-doc',
- views: {
- 'test-view': {
- map: '() => {};',
- reduce: 'function (reduce) { reduce(); }'
- }
- }
- };
-
- const designDocs = new Documents.AllDocs([designDoc], {
- params: { limit: 10 },
- database: {
- safeID: () => { return 'id';}
- }
- });
-
- FauxtonAPI.dispatch({
- type: ActionTypes.EDIT_NEW_INDEX,
- options: {
- newView: false,
- viewName: 'test-view',
- designDocs: designDocs,
- designDocId: designDoc._id
- }
- });
-
- assert.ok(store.hasCustomReduce());
- });
-
- });
-
- //show default reduce
- describe('SELECT_REDUCE_CHANGE', () => {
-
- beforeEach(() => {
- const designDoc = {
- _id: '_design/test-doc',
- views: {
- 'test-view': {
- map: '() => {};'
- }
- }
- };
-
- const designDocs = new Documents.AllDocs([designDoc], {
- params: { limit: 10 },
- database: {
- safeID: () => { return 'id';}
- }
- });
-
- FauxtonAPI.dispatch({
- type: ActionTypes.EDIT_NEW_INDEX,
- options: {
- newView: false,
- viewName: 'test-view',
- designDocs: designDocs,
- designDocId: designDoc._id
- }
- });
- });
-
- it('NONE returns null reduce', () => {
- FauxtonAPI.dispatch({
- type: ActionTypes.SELECT_REDUCE_CHANGE,
- reduceSelectedOption: 'NONE'
- });
- assert.ok(_.isNull(store.getReduce()));
- });
-
- it('builtin returns bultin reduce', () => {
- FauxtonAPI.dispatch({
- type: ActionTypes.SELECT_REDUCE_CHANGE,
- reduceSelectedOption: '_sum'
- });
- assert.equal(store.getReduce(), '_sum');
- });
-
- it('custom returns custom reduce', () => {
- FauxtonAPI.dispatch({
- type: ActionTypes.SELECT_REDUCE_CHANGE,
- reduceSelectedOption: 'CUSTOM'
- });
- assert.equal(store.getReduce(), 'function (keys, values, rereduce) {\n if (rereduce) {\n return sum(values);\n } else {\n return values.length;\n }\n}');
- });
- });
- });
-
-
- describe('design doc selector', () => {
- let designDoc;
-
- beforeEach(() => {
- designDoc = {
- _id: '_design/test-doc',
- views: {
- 'test-view': {
- map: 'boom'
- }
- }
- };
-
- const mangoDoc = {
- "_id": "_design/123mango",
- "id": "_design/123mango",
- "key": "_design/123mango",
- "value": {
- "rev": "20-9e4bc8b76fd7d752d620bbe6e0ea9a80"
- },
- "doc": {
- "_id": "_design/123mango",
- "_rev": "20-9e4bc8b76fd7d752d620bbe6e0ea9a80",
- "views": {
- "test-view": {
- "map": "function(doc) {\n emit(doc._id, 2);\n}"
- },
- "new-view": {
- "map": "function(doc) {\n if (doc.class === \"mammal\" && doc.diet === \"herbivore\")\n emit(doc._id, 1);\n}",
- "reduce": "_sum"
- }
- },
- "language": "query",
- "indexes": {
- "newSearch": {
- "analyzer": "standard",
- "index": "function(doc){\n index(\"default\", doc._id);\n}"
- }
- }
- }
- };
-
- const designDocArray = _.map([designDoc, mangoDoc], (doc) => {
- return Documents.Doc.prototype.parse(doc);
- });
-
- var designDocs = new Documents.AllDocs(designDocArray, {
- params: { limit: 10 },
- database: {
- safeID: () => { return 'id'; }
- }
- });
-
- FauxtonAPI.dispatch({
- type: ActionTypes.EDIT_INDEX,
- options: {
- newView: false,
- viewName: 'test-view',
- designDocs: designDocs,
- designDocId: designDoc._id
- }
- });
- });
-
- afterEach(() => {
- store.reset();
- });
-
- it('DESIGN_DOC_CHANGE changes design doc id', () => {
- const designDocId = 'another-one';
- FauxtonAPI.dispatch({
- type: ActionTypes.DESIGN_DOC_CHANGE,
- options: {
- value: designDocId
- }
- });
- assert.equal(store.getDesignDocId(), designDocId);
- });
-
- it('only filters mango docs', () => {
- const designDocs = store.getAvailableDesignDocs();
- assert.equal(designDocs.length, 1);
- assert.equal(designDocs[0], '_design/test-doc');
- });
- });
-
- describe('EDIT_INDEX', () => {
- let designDoc, designDocs;
-
- beforeEach(() => {
- designDoc = {
- _id: '_design/test-doc',
- views: {
- 'test-view': {
- map: 'boom'
- }
- }
- };
-
- designDocs = new Documents.AllDocs([designDoc], {
- params: { limit: 10 },
- database: {
- safeID: () => { return 'id';}
- }
- });
-
- });
-
- it('can set reduce for new design doc', () => {
- FauxtonAPI.dispatch({
- type: ActionTypes.EDIT_INDEX,
- options: {
- newView: true,
- newDesignDoc: true,
- viewName: 'test-view',
- designDocs: designDocs,
- designDocId: undefined
- }
- });
-
- FauxtonAPI.dispatch({
- type: ActionTypes.SELECT_REDUCE_CHANGE,
- reduceSelectedOption: '_sum'
- });
-
- assert.equal(store.getReduce(), '_sum');
- });
-
- });
-
-});
diff --git a/app/addons/documents/index-editor/actions.js b/app/addons/documents/index-editor/actions.js
index c25fa99..ad261e0 100644
--- a/app/addons/documents/index-editor/actions.js
+++ b/app/addons/documents/index-editor/actions.js
@@ -10,40 +10,40 @@
// 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 "./actiontypes";
-import SidebarActions from "../sidebar/actions";
+import app from '../../../app';
+import FauxtonAPI from '../../../core/api';
+import Documents from '../resources';
+import ActionTypes from './actiontypes';
+import SidebarActions from '../sidebar/actions';
-function selectReduceChanged (reduceOption) {
- FauxtonAPI.dispatch({
+const selectReduceChanged = (reduceOption) => (dispatch) => {
+ dispatch({
type: ActionTypes.SELECT_REDUCE_CHANGE,
reduceSelectedOption: reduceOption
});
-}
+};
-function changeViewName (name) {
- FauxtonAPI.dispatch({
+const changeViewName = (name) => (dispatch) => {
+ dispatch({
type: ActionTypes.VIEW_NAME_CHANGE,
name: name
});
-}
+};
-function editIndex (options) {
- FauxtonAPI.dispatch({
+const editIndex = (options) => (dispatch) => {
+ dispatch({
type: ActionTypes.EDIT_INDEX,
options: options
});
-}
+};
-function clearIndex () {
- FauxtonAPI.dispatch({ type: ActionTypes.CLEAR_INDEX });
-}
+const dispatchClearIndex = () => {
+ FauxtonAPI.reduxDispatch({ type: ActionTypes.CLEAR_INDEX });
+};
-function fetchDesignDocsBeforeEdit (options) {
+const dispatchFetchDesignDocsBeforeEdit = (options) => {
options.designDocs.fetch({reset: true}).then(() => {
- this.editIndex(options);
+ editIndex(options)(FauxtonAPI.reduxDispatch);
}, xhr => {
let errorMsg = 'Error';
if (xhr.responseJSON && xhr.responseJSON.error === 'not_found') {
@@ -57,16 +57,16 @@
clear: true
});
});
-}
+};
-function shouldRemoveDdocView(viewInfo) {
- return !viewInfo.newView &&
+const shouldRemoveDdocView = (viewInfo) => {
+ return !viewInfo.isNewView &&
viewInfo.originalDesignDocName === viewInfo.designDocId &&
viewInfo.originalViewName !== viewInfo.viewName;
-}
+};
-function saveView (viewInfo) {
- var designDoc = viewInfo.designDoc;
+const saveView = (viewInfo) => (dispatch) => {
+ const designDoc = viewInfo.designDoc;
designDoc.setDdocView(viewInfo.viewName, viewInfo.map, viewInfo.reduce);
FauxtonAPI.addNotification({
@@ -89,8 +89,8 @@
// if the user just saved an existing view to a different design doc, remove the view
// from the old design doc and delete if it's empty
- if (!viewInfo.newView && viewInfo.originalDesignDocName !== viewInfo.designDocId) {
- var oldDesignDoc = findDesignDoc(viewInfo.designDocs, viewInfo.originalDesignDocName);
+ if (!viewInfo.isNewView && viewInfo.originalDesignDocName !== viewInfo.designDocId) {
+ const oldDesignDoc = findDesignDoc(viewInfo.designDocs, viewInfo.originalDesignDocName);
safeDeleteIndex(oldDesignDoc, viewInfo.designDocs, 'views', viewInfo.originalViewName, {
onSuccess: function () {
SidebarActions.dispatchUpdateDesignDocs(viewInfo.designDocs);
@@ -102,8 +102,8 @@
addDesignDoc(designDoc);
}
SidebarActions.dispatchUpdateDesignDocs(viewInfo.designDocs);
- FauxtonAPI.dispatch({ type: ActionTypes.VIEW_SAVED });
- var fragment = FauxtonAPI.urls('view', 'showView', viewInfo.database.safeID(), designDoc.safeID(), app.utils.safeURLName(viewInfo.viewName));
+ dispatch({ type: ActionTypes.VIEW_SAVED });
+ const fragment = FauxtonAPI.urls('view', 'showView', viewInfo.database.safeID(), designDoc.safeID(), app.utils.safeURLName(viewInfo.viewName));
FauxtonAPI.navigate(fragment, { trigger: true });
}, (xhr) => {
FauxtonAPI.addNotification({
@@ -112,16 +112,16 @@
clear: true
});
});
-}
+};
-function addDesignDoc (designDoc) {
- FauxtonAPI.dispatch({
+const addDesignDoc = (designDoc) => (dispatch) => {
+ dispatch({
type: ActionTypes.VIEW_ADD_DESIGN_DOC,
designDoc: designDoc.toJSON()
});
-}
+};
-function deleteView (options) {
+const deleteView = (options) => {
function onSuccess () {
@@ -143,11 +143,11 @@
}
return safeDeleteIndex(options.designDoc, options.designDocs, 'views', options.indexName, { onSuccess: onSuccess });
-}
+};
-function cloneView (params) {
- var targetDesignDoc = getDesignDoc(params.designDocs, params.targetDesignDocName, params.newDesignDocName, params.database);
- var indexes = targetDesignDoc.get('views');
+const cloneView = (params) => {
+ const targetDesignDoc = getDesignDoc(params.designDocs, params.targetDesignDocName, params.newDesignDocName, params.database);
+ let indexes = targetDesignDoc.get('views');
if (indexes && _.has(indexes, params.newIndexName)) {
FauxtonAPI.addNotification({
msg: 'That index name is already used in this design doc. Please enter a new name.',
@@ -159,14 +159,14 @@
if (!indexes) {
indexes = {};
}
- var sourceDesignDoc = findDesignDoc(params.designDocs, '_design/' + params.sourceDesignDocName);
- var sourceDesignDocJSON = sourceDesignDoc.toJSON();
+ const sourceDesignDoc = findDesignDoc(params.designDocs, '_design/' + params.sourceDesignDocName);
+ const sourceDesignDocJSON = sourceDesignDoc.toJSON();
// this sets whatever content is in the source index into the target design doc under the new index name
indexes[params.newIndexName] = sourceDesignDocJSON.views[params.sourceIndexName];
targetDesignDoc.set({ views: indexes });
- targetDesignDoc.save().then(function () {
+ targetDesignDoc.save().then(() => {
params.onComplete();
FauxtonAPI.addNotification({
msg: 'The index has been cloned.',
@@ -174,63 +174,62 @@
clear: true
});
SidebarActions.dispatchUpdateDesignDocs(params.designDocs);
- },
- function (xhr) {
+ }, (xhr) => {
params.onComplete();
- var responseText = JSON.parse(xhr.responseText).reason;
+ const responseText = JSON.parse(xhr.responseText).reason;
FauxtonAPI.addNotification({
msg: 'Clone failed: ' + responseText,
type: 'error',
clear: true
});
});
-}
+};
-function gotoEditViewPage (databaseName, partitionKey, designDocName, indexName) {
+const gotoEditViewPage = (databaseName, partitionKey, designDocName, indexName) => {
FauxtonAPI.navigate('#' + FauxtonAPI.urls('view', 'edit', encodeURIComponent(databaseName),
(partitionKey ? encodeURIComponent(partitionKey) : ''),
encodeURIComponent(designDocName), encodeURIComponent(indexName)));
-}
+};
-function updateMapCode (code) {
- FauxtonAPI.dispatch({
+const updateMapCode = (code) => (dispatch) => {
+ dispatch({
type: ActionTypes.VIEW_UPDATE_MAP_CODE,
code: code
});
-}
+};
-function updateReduceCode (code) {
- FauxtonAPI.dispatch({
+const updateReduceCode = (code) => (dispatch) => {
+ dispatch({
type: ActionTypes.VIEW_UPDATE_REDUCE_CODE,
code: code
});
-}
+};
-function selectDesignDoc (designDoc) {
- FauxtonAPI.dispatch({
+const selectDesignDoc = (designDoc) => (dispatch) => {
+ dispatch({
type: ActionTypes.DESIGN_DOC_CHANGE,
options: {
value: designDoc
}
});
-}
+};
-function updateNewDesignDocName (designDocName) {
- FauxtonAPI.dispatch({
+const updateNewDesignDocName = (designDocName) => (dispatch) => {
+ dispatch({
type: ActionTypes.DESIGN_DOC_NEW_NAME_UPDATED,
options: {
value: designDocName
}
});
-}
+};
// safely deletes an index of any type. It only deletes the actual design doc if there are no
// other indexes of any type left in the doc
-function safeDeleteIndex (designDoc, designDocs, indexPropName, indexName, options) {
- var opts = _.extend({
+const safeDeleteIndex = (designDoc, designDocs, indexPropName, indexName, options) => {
+ const opts = _.extend({
onSuccess: function () { },
onError: function (xhr) {
- var responseText = JSON.parse(xhr.responseText).reason;
+ const responseText = JSON.parse(xhr.responseText).reason;
FauxtonAPI.addNotification({
msg: 'Delete failed: ' + responseText,
type: 'error',
@@ -239,21 +238,21 @@
}
}, options);
- var indexes = designDoc.get(indexPropName) || {};
+ const indexes = designDoc.get(indexPropName) || {};
delete indexes[indexName];
- var newIndexes = {};
+ const newIndexes = {};
newIndexes[indexPropName] = indexes;
designDoc.set(newIndexes);
// we either save the design doc with the now-removed index, or we remove it altogether if there are no indexes
// of any type left in the design doc
- var indexTypePropNames = FauxtonAPI.getIndexTypePropNames();
- var hasRemainingIndex = _.some(indexTypePropNames, function (propName) {
+ const indexTypePropNames = FauxtonAPI.getIndexTypePropNames();
+ const hasRemainingIndex = _.some(indexTypePropNames, function (propName) {
return designDoc.get(propName) && _.keys(designDoc.get(propName)).length > 0;
});
- var promise;
- var deleteDesignDoc = false;
+ let promise;
+ let deleteDesignDoc = false;
if (hasRemainingIndex) {
promise = designDoc.save();
} else {
@@ -266,21 +265,21 @@
}
opts.onSuccess();
}, opts.onError);
-}
+};
// ---- helpers ----
-function findDesignDoc (designDocs, designDocName) {
+const findDesignDoc = (designDocs, designDocName) => {
return designDocs.find(function (doc) {
return doc.id === designDocName;
}).dDocModel();
-}
+};
-function getDesignDoc (designDocs, targetDesignDocName, newDesignDocName, database) {
+const getDesignDoc = (designDocs, targetDesignDocName, newDesignDocName, database) => {
if (targetDesignDocName === 'new-doc') {
- var doc = {
+ const doc = {
"_id": "_design/" + newDesignDocName,
"views": {},
"language": "javascript"
@@ -288,32 +287,32 @@
return new Documents.Doc(doc, { database: database });
}
- var foundDoc = designDocs.find(function (ddoc) {
+ const foundDoc = designDocs.find(function (ddoc) {
return ddoc.id === targetDesignDocName;
});
return (!foundDoc) ? null : foundDoc.dDocModel();
-}
+};
export default {
helpers: {
- findDesignDoc: findDesignDoc,
- getDesignDoc: getDesignDoc
+ findDesignDoc,
+ getDesignDoc
},
- safeDeleteIndex: safeDeleteIndex,
- selectReduceChanged: selectReduceChanged,
- changeViewName: changeViewName,
- editIndex: editIndex,
- clearIndex: clearIndex,
- fetchDesignDocsBeforeEdit: fetchDesignDocsBeforeEdit,
- shouldRemoveDdocView: shouldRemoveDdocView,
- saveView: saveView,
- addDesignDoc: addDesignDoc,
- deleteView: deleteView,
- cloneView: cloneView,
- gotoEditViewPage: gotoEditViewPage,
- updateMapCode: updateMapCode,
- updateReduceCode: updateReduceCode,
- selectDesignDoc: selectDesignDoc,
- updateNewDesignDocName: updateNewDesignDocName
+ safeDeleteIndex,
+ selectReduceChanged,
+ changeViewName,
+ editIndex,
+ dispatchClearIndex,
+ dispatchFetchDesignDocsBeforeEdit,
+ shouldRemoveDdocView,
+ saveView,
+ addDesignDoc,
+ deleteView,
+ cloneView,
+ gotoEditViewPage,
+ updateMapCode,
+ updateReduceCode,
+ selectDesignDoc,
+ updateNewDesignDocName
};
diff --git a/app/addons/documents/index-editor/components.js b/app/addons/documents/index-editor/components.js
index a96fab6..3cf69bd 100644
--- a/app/addons/documents/index-editor/components.js
+++ b/app/addons/documents/index-editor/components.js
@@ -10,15 +10,17 @@
// License for the specific language governing permissions and limitations under
// the License.
-import ReactComponents from "../../components/react-components";
+import ReactComponents from '../../components/react-components';
import DesignDocSelector from './components/DesignDocSelector';
import IndexEditor from './components/IndexEditor';
+import IndexEditorContainer from './components/IndexEditorContainer';
import ReduceEditor from './components/ReduceEditor';
const StyledSelect = ReactComponents.StyledSelect;
export default {
- EditorController: IndexEditor,
+ IndexEditorContainer,
+ IndexEditor,
ReduceEditor,
DesignDocSelector,
StyledSelect
diff --git a/app/addons/documents/index-editor/components/DesignDocSelector.js b/app/addons/documents/index-editor/components/DesignDocSelector.js
index 956978f..57c6bbc 100644
--- a/app/addons/documents/index-editor/components/DesignDocSelector.js
+++ b/app/addons/documents/index-editor/components/DesignDocSelector.js
@@ -11,11 +11,9 @@
// the License.
import PropTypes from 'prop-types';
-
-import React, { Component } from "react";
-import ReactDOM from "react-dom";
-import FauxtonAPI from "../../../../core/api";
-import ReactComponents from "../../../components/react-components";
+import React, { Component } from 'react';
+import FauxtonAPI from '../../../../core/api';
+import ReactComponents from '../../../components/react-components';
const { StyledSelect } = ReactComponents;
diff --git a/app/addons/documents/index-editor/components/IndexEditor.js b/app/addons/documents/index-editor/components/IndexEditor.js
index 884858d..332b749 100644
--- a/app/addons/documents/index-editor/components/IndexEditor.js
+++ b/app/addons/documents/index-editor/components/IndexEditor.js
@@ -10,61 +10,27 @@
// License for the specific language governing permissions and limitations under
// the License.
-import React, { Component } from "react";
-import ReactDOM from "react-dom";
-import app from "../../../../app";
-import FauxtonAPI from "../../../../core/api";
-import ReactComponents from "../../../components/react-components";
-import Stores from "../stores";
-import Actions from "../actions";
+import PropTypes from 'prop-types';
+import React, { Component } from 'react';
+import app from '../../../../app';
+import FauxtonAPI from '../../../../core/api';
+import ReactComponents from '../../../components/react-components';
import DesignDocSelector from './DesignDocSelector';
import ReduceEditor from './ReduceEditor';
const getDocUrl = app.helpers.getDocUrl;
-const store = Stores.indexEditorStore;
const {CodeEditorPanel, ConfirmButton, LoadLines} = ReactComponents;
export default class IndexEditor extends Component {
constructor(props) {
super(props);
- this.state = this.getStoreState();
- }
-
- getStoreState() {
- return {
- database: store.getDatabase(),
- isNewView: store.isNewView(),
- viewName: store.getViewName(),
- designDocs: store.getDesignDocs(),
- designDocList: store.getAvailableDesignDocs(),
- originalViewName: store.getOriginalViewName(),
- originalDesignDocName: store.getOriginalDesignDocName(),
- newDesignDoc: store.isNewDesignDoc(),
- designDocId: store.getDesignDocId(),
- newDesignDocName: store.getNewDesignDocName(),
- saveDesignDoc: store.getSaveDesignDoc(),
- map: store.getMap(),
- isLoading: store.isLoading()
- };
- }
-
- onChange() {
- this.setState(this.getStoreState());
- }
-
- componentDidMount() {
- store.on('change', this.onChange, this);
- }
-
- componentWillUnmount() {
- store.off('change', this.onChange);
}
// the code editor is a standalone component, so if the user goes from one edit view page to another, we need to
// force an update of the editor panel
- componentDidUpdate(prevProps, prevState) {
- if (this.state.map !== prevState.map && this.mapEditor) {
+ componentDidUpdate(prevProps) {
+ if (this.props.map !== prevProps.map && this.mapEditor) {
this.mapEditor.update();
}
}
@@ -76,31 +42,31 @@
return;
}
- Actions.saveView({
- database: this.state.database,
- newView: this.state.isNewView,
- viewName: this.state.viewName,
- designDoc: this.state.saveDesignDoc,
- designDocId: this.state.designDocId,
- newDesignDoc: this.state.newDesignDoc,
- originalViewName: this.state.originalViewName,
- originalDesignDocName: this.state.originalDesignDocName,
+ this.props.saveView({
+ database: this.props.database,
+ isNewView: this.props.isNewView,
+ viewName: this.props.viewName,
+ designDoc: this.props.saveDesignDoc,
+ designDocId: this.props.designDocId,
+ isNewDesignDoc: this.props.isNewDesignDoc,
+ originalViewName: this.props.originalViewName,
+ originalDesignDocName: this.props.originalDesignDocName,
map: this.mapEditor.getValue(),
reduce: this.reduceEditor.getReduceValue(),
- designDocs: this.state.designDocs
+ designDocs: this.props.designDocs
});
}
viewChange(el) {
- Actions.changeViewName(el.target.value);
+ this.props.changeViewName(el.target.value);
}
updateMapCode(code) {
- Actions.updateMapCode(code);
+ this.props.updateMapCode(code);
}
render() {
- if (this.state.isLoading) {
+ if (this.props.isLoading) {
return (
<div className="define-view">
<LoadLines />
@@ -108,9 +74,9 @@
);
}
- const pageHeader = (this.state.isNewView) ? 'New View' : 'Edit View';
- const btnLabel = (this.state.isNewView) ? 'Create Document and then Build Index' : 'Save Document and then Build Index';
- const cancelLink = '#' + FauxtonAPI.urls('view', 'showView', this.state.database.id, this.state.designDocId, this.state.viewName);
+ const pageHeader = (this.props.isNewView) ? 'New View' : 'Edit View';
+ const btnLabel = (this.props.isNewView) ? 'Create Document and then Build Index' : 'Save Document and then Build Index';
+ const cancelLink = '#' + FauxtonAPI.urls('view', 'showView', this.props.database.id, this.props.designDocId, this.props.viewName);
return (
<div className="define-view" >
<form className="form-horizontal view-query-save" onSubmit={this.saveView.bind(this)}>
@@ -119,11 +85,11 @@
<div className="new-ddoc-section">
<DesignDocSelector
ref={(el) => { this.designDocSelector = el; }}
- designDocList={this.state.designDocList}
- selectedDesignDocName={this.state.designDocId}
- newDesignDocName={this.state.newDesignDocName}
- onSelectDesignDoc={Actions.selectDesignDoc}
- onChangeNewDesignDocName={Actions.updateNewDesignDocName}
+ designDocList={this.props.designDocList}
+ selectedDesignDocName={this.props.designDocId}
+ newDesignDocName={this.props.newDesignDocName}
+ onSelectDesignDoc={this.props.selectDesignDoc}
+ onChangeNewDesignDocName={this.props.updateNewDesignDocName}
docLink={getDocUrl('DESIGN_DOCS')} />
</div>
@@ -142,7 +108,7 @@
<input
type="text"
id="index-name"
- value={this.state.viewName}
+ value={this.props.viewName}
onChange={this.viewChange.bind(this)}
placeholder="Index name" />
</div>
@@ -153,8 +119,8 @@
docLink={getDocUrl('MAP_FUNCS')}
blur={this.updateMapCode.bind(this)}
allowZenMode={false}
- defaultCode={this.state.map} />
- <ReduceEditor ref={(el) => { this.reduceEditor = el; }} />
+ defaultCode={this.props.map} />
+ <ReduceEditor ref={(el) => { this.reduceEditor = el; }} {...this.props} />
<div className="padded-box">
<div className="control-group">
<ConfirmButton id="save-view" text={btnLabel} />
@@ -166,3 +132,19 @@
);
}
}
+
+IndexEditor.propTypes = {
+ isLoading:PropTypes.bool.isRequired,
+ isNewView: PropTypes.bool.isRequired,
+ database: PropTypes.object.isRequired,
+ designDocId: PropTypes.string.isRequired,
+ viewName: PropTypes.string.isRequired,
+ isNewDesignDoc: PropTypes.bool.isRequired,
+ originalViewName: PropTypes.string,
+ originalDesignDocName: PropTypes.string,
+ designDocs: PropTypes.object,
+ saveDesignDoc: PropTypes.object,
+ updateNewDesignDocName: PropTypes.func.isRequired,
+ changeViewName: PropTypes.func.isRequired,
+ updateMapCode: PropTypes.func.isRequired
+};
diff --git a/app/addons/documents/index-editor/components/IndexEditorContainer.js b/app/addons/documents/index-editor/components/IndexEditorContainer.js
new file mode 100644
index 0000000..445b029
--- /dev/null
+++ b/app/addons/documents/index-editor/components/IndexEditorContainer.js
@@ -0,0 +1,78 @@
+// 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 IndexEditor from './IndexEditor';
+import Actions from '../actions';
+import { getSaveDesignDoc, getDesignDocIds, reduceSelectedOption, hasCustomReduce } from '../reducers';
+
+const mapStateToProps = ({ indexEditor }) => {
+ return {
+ database: indexEditor.database,
+ isNewView: indexEditor.isNewView,
+ viewName: indexEditor.viewName,
+ designDocs: indexEditor.designDocs,
+ designDocList: getDesignDocIds(indexEditor),
+ originalViewName: indexEditor.originalViewName,
+ originalDesignDocName: indexEditor.originalDesignDocName,
+ isNewDesignDoc: indexEditor.isNewDesignDoc,
+ designDocId: indexEditor.designDocId,
+ newDesignDocName: indexEditor.newDesignDocName,
+ saveDesignDoc: getSaveDesignDoc(indexEditor),
+ map: indexEditor.view.map,
+ isLoading: indexEditor.isLoading,
+ reduce: indexEditor.view.reduce,
+ reduceOptions: indexEditor.reduceOptions,
+ reduceSelectedOption: reduceSelectedOption(indexEditor),
+ hasCustomReduce: hasCustomReduce(indexEditor),
+ hasReduce: !!indexEditor.view.reduce
+ };
+};
+
+const mapDispatchToProps = (dispatch) => {
+ return {
+ saveView: (viewInfo) => {
+ dispatch(Actions.saveView(viewInfo));
+ },
+
+ changeViewName: (name) => {
+ dispatch(Actions.changeViewName(name));
+ },
+
+ updateMapCode: (code) => {
+ dispatch(Actions.updateMapCode(code));
+ },
+
+ selectDesignDoc: (designDoc) => {
+ dispatch(Actions.selectDesignDoc(designDoc));
+ },
+
+ updateNewDesignDocName: (designDocName) => {
+ dispatch(Actions.updateNewDesignDocName(designDocName));
+ },
+
+ updateReduceCode: (code) => {
+ dispatch(Actions.updateReduceCode(code));
+ },
+
+ selectReduceChanged: (reduceOption) => {
+ dispatch(Actions.selectReduceChanged(reduceOption));
+ }
+ };
+};
+
+const IndexEditorContainer = connect(
+ mapStateToProps,
+ mapDispatchToProps
+)(IndexEditor);
+
+export default IndexEditorContainer;
diff --git a/app/addons/documents/index-editor/components/ReduceEditor.js b/app/addons/documents/index-editor/components/ReduceEditor.js
index 7e0b181..7f1df52 100644
--- a/app/addons/documents/index-editor/components/ReduceEditor.js
+++ b/app/addons/documents/index-editor/components/ReduceEditor.js
@@ -10,59 +10,33 @@
// License for the specific language governing permissions and limitations under
// the License.
-import React, { Component } from "react";
-import ReactDOM from "react-dom";
-import app from "../../../../app";
-import ReactComponents from "../../../components/react-components";
-import Stores from "../stores";
-import Actions from "../actions";
+import PropTypes from 'prop-types';
+import React, { Component } from 'react';
+import app from '../../../../app';
+import ReactComponents from '../../../components/react-components';
const getDocUrl = app.helpers.getDocUrl;
-const store = Stores.indexEditorStore;
const {CodeEditorPanel, StyledSelect} = ReactComponents;
export default class ReduceEditor extends Component {
constructor(props) {
super(props);
- this.state = this.getStoreState();
- }
-
- onChange() {
- this.setState(this.getStoreState());
- }
-
- componentDidMount() {
- store.on('change', this.onChange, this);
- }
-
- componentWillUnmount() {
- store.off('change', this.onChange);
- }
-
- getStoreState() {
- return {
- reduce: store.getReduce(),
- reduceOptions: store.reduceOptions(),
- reduceSelectedOption: store.reduceSelectedOption(),
- hasCustomReduce: store.hasCustomReduce(),
- hasReduce: store.hasReduce()
- };
}
getOptionsList() {
- return _.map(this.state.reduceOptions, (reduce, i) => {
+ return this.props.reduceOptions.map((reduce, i) => {
return <option key={i} value={reduce}>{reduce}</option>;
});
}
getReduceValue() {
- if (!this.state.hasReduce) {
+ if (!this.props.hasReduce) {
return null;
}
- if (!this.state.hasCustomReduce) {
- return this.state.reduce;
+ if (!this.props.hasCustomReduce) {
+ return this.props.reduce;
}
return this.reduceEditor.getValue();
@@ -73,23 +47,23 @@
}
updateReduceCode(code) {
- Actions.updateReduceCode(code);
+ this.props.updateReduceCode(code);
}
selectChange(event) {
- Actions.selectReduceChanged(event.target.value);
+ this.props.selectReduceChanged(event.target.value);
}
render() {
const reduceOptions = this.getOptionsList();
let customReduceSection;
- if (this.state.hasCustomReduce) {
+ if (this.props.hasCustomReduce) {
customReduceSection = <CodeEditorPanel
ref={node => this.reduceEditor = node}
id='reduce-function'
title={'Custom Reduce function'}
- defaultCode={this.state.reduce}
+ defaultCode={this.props.reduce}
allowZenMode={false}
blur={this.updateReduceCode.bind(this)}
/>;
@@ -114,10 +88,20 @@
selectContent={reduceOptions}
selectChange={this.selectChange.bind(this)}
selectId="reduce-function-selector"
- selectValue={this.state.reduceSelectedOption} />
+ selectValue={this.props.reduceSelectedOption} />
</div>
{customReduceSection}
</div>
);
}
}
+
+ReduceEditor.propTypes = {
+ reduceOptions: PropTypes.array.isRequired,
+ hasReduce: PropTypes.bool.isRequired,
+ hasCustomReduce: PropTypes.bool.isRequired,
+ reduce: PropTypes.string,
+ reduceSelectedOption: PropTypes.string.isRequired,
+ updateReduceCode: PropTypes.func.isRequired,
+ selectReduceChanged: PropTypes.func.isRequired
+};
diff --git a/app/addons/documents/index-editor/reducers.js b/app/addons/documents/index-editor/reducers.js
new file mode 100644
index 0000000..663818f
--- /dev/null
+++ b/app/addons/documents/index-editor/reducers.js
@@ -0,0 +1,196 @@
+// 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 ActionTypes from './actiontypes';
+import Resources from "../resources";
+
+const defaultMap = 'function (doc) {\n emit(doc._id, 1);\n}';
+const defaultReduce = 'function (keys, values, rereduce) {\n if (rereduce) {\n return sum(values);\n } else {\n return values.length;\n }\n}';
+const builtInReducers = ['_sum', '_count', '_stats'];
+
+const initialState = {
+ designDocs: new Backbone.Collection(),
+ isLoading: true,
+ view: { reduce: '', map: defaultMap },
+ database: { id: '0' },
+ designDocId: '',
+ isNewDesignDoc: false,
+ newDesignDocName: '',
+ isNewView: false,
+ viewName: '',
+ originalViewName: '',
+ originalDesignDocName: '',
+ reduceOptions: builtInReducers.concat(['CUSTOM', 'NONE'])
+};
+
+function editIndex(state, options) {
+ const newState = {
+ ...state,
+ isLoading: false,
+ newDesignDocName: '',
+ isNewView: options.isNewView,
+ viewName: options.viewName || 'viewName',
+ isNewDesignDoc: options.isNewDesignDoc || false,
+ designDocs: options.designDocs,
+ designDocId: options.designDocId,
+ originalDesignDocName: options.designDocId,
+ database: options.database
+ };
+ newState.originalViewName = newState.viewName;
+ newState.view = getView(newState);
+ return newState;
+}
+
+function getView(state) {
+ if (state.isNewView || state.isNewDesignDoc) {
+ return { reduce: '', map: defaultMap };
+ }
+
+ const designDoc = state.designDocs.find(ddoc => {
+ return state.designDocId == ddoc.id;
+ }).dDocModel();
+ return designDoc.get('views')[state.viewName];
+}
+
+export function reduceSelectedOption(state) {
+ if (!state.view.reduce) {
+ return 'NONE';
+ }
+ if (hasCustomReduce(state)) {
+ return 'CUSTOM';
+ }
+ return state.view.reduce;
+}
+
+export function hasCustomReduce(state) {
+ if (state.view.reduce) {
+ return !builtInReducers.includes(state.view.reduce);
+ }
+ return false;
+}
+
+export function getSaveDesignDoc(state) {
+ if (state.designDocId === 'new-doc') {
+ const doc = {
+ _id: '_design/' + state.newDesignDocName,
+ views: {},
+ language: 'javascript'
+ };
+ return new Resources.Doc(doc, { database: state.database });
+ }
+
+ if (!state.designDocs) {
+ return null;
+ }
+
+ const foundDoc = state.designDocs.find(ddoc => {
+ return ddoc.id === state.designDocId;
+ });
+
+ return foundDoc ? foundDoc.dDocModel() : null;
+}
+
+// returns a simple array of design doc IDs. Omits mango docs
+export function getDesignDocIds(state) {
+ if (!state.designDocs) {
+ return [];
+ }
+ return state.designDocs.filter(doc => {
+ return !doc.isMangoDoc();
+ }).map(doc => {
+ return doc.id;
+ });
+}
+
+export default function indexEditor(state = initialState, action) {
+ const { options } = action;
+ switch (action.type) {
+
+ case ActionTypes.CLEAR_INDEX:
+ return {
+ ...initialState
+ };
+
+ case ActionTypes.EDIT_INDEX:
+ return editIndex(state, options);
+
+ case ActionTypes.EDIT_NEW_INDEX:
+ return editIndex(state, options);
+
+ case ActionTypes.VIEW_NAME_CHANGE:
+ return {
+ ...state,
+ viewName: action.name
+ };
+
+ case ActionTypes.SELECT_REDUCE_CHANGE:
+ let newReduce = action.reduceSelectedOption;
+ if (newReduce === 'NONE') {
+ newReduce = '';
+ }
+ if (newReduce === 'CUSTOM') {
+ newReduce = defaultReduce;
+ }
+ return {
+ ...state,
+ view: {
+ ...state.view,
+ reduce: newReduce
+ }
+ };
+
+ case ActionTypes.DESIGN_DOC_CHANGE:
+ return {
+ ...state,
+ designDocId: options.value
+ };
+
+ case ActionTypes.VIEW_SAVED:
+ return state;
+
+ case ActionTypes.VIEW_CREATED:
+ return state;
+
+ case ActionTypes.VIEW_ADD_DESIGN_DOC:
+ return {
+ ...state,
+ designDocId: action.designDoc._id
+ };
+
+ case ActionTypes.VIEW_UPDATE_MAP_CODE:
+ return {
+ ...state,
+ view: {
+ ...state.view,
+ map: action.code
+ }
+ };
+
+ case ActionTypes.VIEW_UPDATE_REDUCE_CODE:
+ return {
+ ...state,
+ view: {
+ ...state.view,
+ reduce: action.code
+ }
+ };
+
+ case ActionTypes.DESIGN_DOC_NEW_NAME_UPDATED:
+ return {
+ ...state,
+ newDesignDocName: options.value
+ };
+
+ default:
+ return state;
+ }
+}
diff --git a/app/addons/documents/index-editor/stores.js b/app/addons/documents/index-editor/stores.js
deleted file mode 100644
index c5b6205..0000000
--- a/app/addons/documents/index-editor/stores.js
+++ /dev/null
@@ -1,264 +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 ActionTypes from "./actiontypes";
-import Resources from "../resources";
-var Stores = {};
-
-Stores.IndexEditorStore = FauxtonAPI.Store.extend({
-
- defaultMap: 'function (doc) {\n emit(doc._id, 1);\n}',
- defaultReduce: 'function (keys, values, rereduce) {\n if (rereduce) {\n return sum(values);\n } else {\n return values.length;\n }\n}',
-
- initialize: function () {
- this.reset();
- },
-
- reset: function () {
- this._designDocs = [];
- this._isLoading = true;
- this._view = { reduce: '', map: this.defaultMap };
- this._database = { id: '0' };
- },
-
- editIndex: function (options) {
- this._database = options.database;
- this._newView = options.newView;
- this._viewName = options.viewName || 'viewName';
- this._newDesignDoc = options.newDesignDoc || false;
- this._newDesignDocName = '';
- this._designDocs = options.designDocs;
- this._designDocId = options.designDocId;
- this._originalViewName = this._viewName;
- this._originalDesignDocName = options.designDocId;
- this.setView();
-
- this._isLoading = false;
- },
-
- isLoading: function () {
- return this._isLoading;
- },
-
- setView: function () {
- if (this._newView || this._newDesignDoc) {
- this._view = { reduce: '', map: this.defaultMap };
- } else {
- this._view = this.getDesignDoc().get('views')[this._viewName];
- }
- },
-
- getDatabase: function () {
- return this._database;
- },
-
- getMap: function () {
- return this._view.map;
- },
-
- setMap: function (map) {
- this._view.map = map;
- },
-
- getReduce: function () {
- return this._view.reduce;
- },
-
- setReduce: function (reduce) {
- this._view.reduce = reduce;
- },
-
- getDesignDoc: function () {
- return this._designDocs.find((ddoc) => {
- return this._designDocId == ddoc.id;
- }).dDocModel();
- },
-
- getDesignDocs: function () {
- return this._designDocs;
- },
-
- // returns a simple array of design doc IDs. Omits mango docs
- getAvailableDesignDocs: function () {
- var availableDocs = this.getDesignDocs().filter(function (doc) {
- return !doc.isMangoDoc();
- });
- return _.map(availableDocs, function (doc) {
- return doc.id;
- });
- },
-
- getDesignDocId: function () {
- return this._designDocId;
- },
-
- setDesignDocId: function (designDocId) {
- this._designDocId = designDocId;
- },
-
- isNewDesignDoc: function () {
- return this._newDesignDoc;
- },
-
- isNewView: function () {
- return this._newView;
- },
-
- getViewName: function () {
- return this._viewName;
- },
-
- setViewName: function (name) {
- this._viewName = name;
- },
-
- hasCustomReduce: function () {
- if (!this.hasReduce()) {
- return false;
- }
- return !_.includes(this.builtInReduces(), this.getReduce());
- },
-
- hasReduce: function () {
- if (!this.getReduce()) {
- return false;
- }
- return true;
- },
-
- getOriginalViewName: function () {
- return this._originalViewName;
- },
-
- getOriginalDesignDocName: function () {
- return this._originalDesignDocName;
- },
-
- builtInReduces: function () {
- return ['_sum', '_count', '_stats'];
- },
-
- reduceSelectedOption: function () {
- if (!this.hasReduce()) {
- return 'NONE';
- }
- if (this.hasCustomReduce()) {
- return 'CUSTOM';
- }
- return this.getReduce();
- },
-
- reduceOptions: function () {
- return this.builtInReduces().concat(['CUSTOM', 'NONE']);
- },
-
- updateReduceFromSelect: function (selectedReduce) {
- if (selectedReduce === 'NONE') {
- this.setReduce(null);
- return;
- }
- if (selectedReduce === 'CUSTOM') {
- this.setReduce(this.defaultReduce);
- return;
- }
- this.setReduce(selectedReduce);
- },
-
- addDesignDoc: function (designDoc) {
- this._designDocs.add(designDoc, { merge: true });
- this._designDocId = designDoc._id;
- },
-
- getNewDesignDocName: function () {
- return this._newDesignDocName;
- },
-
- getSaveDesignDoc: function () {
- if (this._designDocId === 'new-doc') {
- var doc = {
- _id: '_design/' + this._newDesignDocName,
- views: {},
- language: 'javascript'
- };
- return new Resources.Doc(doc, { database: this._database });
- }
-
- var foundDoc = this._designDocs.find(function (ddoc) {
- return ddoc.id === this._designDocId;
- }.bind(this));
-
- return (!foundDoc) ? null : foundDoc.dDocModel();
- },
-
- dispatch: function (action) {
- switch (action.type) {
- case ActionTypes.CLEAR_INDEX:
- this.reset();
- break;
-
- case ActionTypes.EDIT_INDEX:
- this.editIndex(action.options);
- break;
-
- case ActionTypes.VIEW_NAME_CHANGE:
- this.setViewName(action.name);
- break;
-
- case ActionTypes.EDIT_NEW_INDEX:
- this.editIndex(action.options);
- break;
-
- case ActionTypes.SELECT_REDUCE_CHANGE:
- this.updateReduceFromSelect(action.reduceSelectedOption);
- break;
-
- case ActionTypes.DESIGN_DOC_CHANGE:
- this.setDesignDocId(action.options.value);
- break;
-
- case ActionTypes.VIEW_SAVED:
- break;
-
- case ActionTypes.VIEW_CREATED:
- break;
-
- case ActionTypes.VIEW_ADD_DESIGN_DOC:
- this.addDesignDoc(action.designDoc);
- this.setView();
- break;
-
- case ActionTypes.VIEW_UPDATE_MAP_CODE:
- this.setMap(action.code);
- break;
-
- case ActionTypes.VIEW_UPDATE_REDUCE_CODE:
- this.setReduce(action.code);
- break;
-
- case ActionTypes.DESIGN_DOC_NEW_NAME_UPDATED:
- this._newDesignDocName = action.options.value;
- break;
-
- default:
- return;
- }
-
- this.triggerChange();
- }
-
-});
-
-Stores.indexEditorStore = new Stores.IndexEditorStore();
-Stores.indexEditorStore.dispatchToken = FauxtonAPI.dispatcher.register(Stores.indexEditorStore.dispatch.bind(Stores.indexEditorStore));
-
-export default Stores;
diff --git a/app/addons/documents/layouts.js b/app/addons/documents/layouts.js
index 4be1d20..7f91c8e 100644
--- a/app/addons/documents/layouts.js
+++ b/app/addons/documents/layouts.js
@@ -247,7 +247,7 @@
dbName, dropDownLinks, selectedNavItem, designDocInfo, partitionKey }) => {
const content = showEditView ?
- <IndexEditorComponents.EditorController /> :
+ <IndexEditorComponents.IndexEditorContainer /> :
<DesignDocInfoContainer
designDocInfo={designDocInfo}
designDocName={selectedNavItem.designDocName}/>;
diff --git a/app/addons/documents/routes-index-editor.js b/app/addons/documents/routes-index-editor.js
index 8d357ba..bfe9c42 100644
--- a/app/addons/documents/routes-index-editor.js
+++ b/app/addons/documents/routes-index-editor.js
@@ -74,10 +74,10 @@
showView: function (databaseName, partitionKey, ddoc, viewName) {
viewName = viewName.replace(/\?.*$/, '');
- ActionsIndexEditor.clearIndex();
- ActionsIndexEditor.fetchDesignDocsBeforeEdit({
+ ActionsIndexEditor.dispatchClearIndex();
+ ActionsIndexEditor.dispatchFetchDesignDocsBeforeEdit({
viewName: viewName,
- newView: false,
+ isNewView: false,
database: this.database,
designDocs: this.designDocs,
designDocId: '_design/' + ddoc
@@ -127,21 +127,21 @@
},
createView: function (database, partitionKey, _designDoc) {
- let newDesignDoc = true;
+ let isNewDesignDoc = true;
let designDoc = 'new-doc';
if (_designDoc) {
designDoc = '_design/' + _designDoc;
- newDesignDoc = false;
+ isNewDesignDoc = false;
}
- ActionsIndexEditor.fetchDesignDocsBeforeEdit({
+ ActionsIndexEditor.dispatchFetchDesignDocsBeforeEdit({
viewName: 'new-view',
- newView: true,
+ isNewView: true,
database: this.database,
designDocs: this.designDocs,
designDocId: designDoc,
- newDesignDoc: newDesignDoc
+ isNewDesignDoc: isNewDesignDoc
});
const selectedNavItem = new SidebarItemSelection('');
@@ -163,9 +163,9 @@
},
editView: function (databaseName, partitionKey, ddocName, viewName) {
- ActionsIndexEditor.fetchDesignDocsBeforeEdit({
+ ActionsIndexEditor.dispatchFetchDesignDocsBeforeEdit({
viewName: viewName,
- newView: false,
+ isNewView: false,
database: this.database,
designDocs: this.designDocs,
designDocId: '_design/' + ddocName