This is an automated email from the ASF dual-hosted git repository. amaranhao 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 c8d938f Disable stable query option for partitioned views (#1193) c8d938f is described below commit c8d938fc8c5ad1751f4b29286804acd821c57485 Author: Antonio Maranhao <30349380+antonio-maran...@users.noreply.github.com> AuthorDate: Thu Mar 14 14:28:55 2019 -0400 Disable stable query option for partitioned views (#1193) --- app/addons/documents/__tests__/helpers.test.js | 78 +++++++++++++++++++ .../documents/__tests__/main-fields-view.test.js | 4 +- .../documents/__tests__/query-options.test.js | 56 ++++++++++++- .../documents/assets/less/query-options.less | 3 +- app/addons/documents/helpers.js | 17 ++++ .../components/queryoptions/MainFieldsView.js | 91 ++++++++++++++-------- .../components/queryoptions/QueryOptions.js | 1 + .../containers/QueryOptionsContainer.js | 11 ++- 8 files changed, 222 insertions(+), 39 deletions(-) diff --git a/app/addons/documents/__tests__/helpers.test.js b/app/addons/documents/__tests__/helpers.test.js index 195584f..d586f34 100644 --- a/app/addons/documents/__tests__/helpers.test.js +++ b/app/addons/documents/__tests__/helpers.test.js @@ -42,4 +42,82 @@ describe('Helpers', () => { }); + describe('selectedItemIsPartitionedView', () => { + const ddocs = { + find: () => { return {_id: '_design/ddoc1' }; } + }; + const selectedView = { + navItem: 'designDoc', + designDocSection: 'Views', + indexName: 'view1' + }; + const selectedAllDocs = { + navItem: 'all-docs' + }; + + it('returns false if no item is selected', () => { + const isPartitionedView = Helpers.selectedItemIsPartitionedView(ddocs, null, true); + expect(isPartitionedView).toBe(false); + }); + + it('returns false if selected item is not a view', () => { + const isPartitionedView = Helpers.selectedItemIsPartitionedView(ddocs, selectedAllDocs, true); + expect(isPartitionedView).toBe(false); + }); + + it('returns false if selected item is a global view', () => { + const isPartitionedView = Helpers.selectedItemIsPartitionedView(ddocs, selectedView, false); + expect(isPartitionedView).toBe(false); + }); + + it('returns true if selected item is a partitioned view', () => { + const isPartitionedView = Helpers.selectedItemIsPartitionedView(ddocs, selectedView, true); + expect(isPartitionedView).toBe(true); + }); + + }); + + describe('isDDocPartitioned', () => { + const ddocNoOptions = { + _id: '_design/ddoc1' + }; + const ddocPartitionedTrue = { + _id: '_design/ddoc1', + options: { + partitioned: true + } + }; + const ddocPartitionedFalse = { + _id: '_design/ddoc1', + options: { + partitioned: false + } + }; + + it('returns false if database is not partitioned', () => { + let isPartitionedDdoc = Helpers.isDDocPartitioned(ddocNoOptions, false); + expect(isPartitionedDdoc).toBe(false); + + isPartitionedDdoc = Helpers.isDDocPartitioned(ddocPartitionedFalse, false); + expect(isPartitionedDdoc).toBe(false); + + isPartitionedDdoc = Helpers.isDDocPartitioned(ddocPartitionedTrue, false); + expect(isPartitionedDdoc).toBe(false); + }); + + it('returns true if database is partitioned and ddoc partitioned option is not set to false', () => { + let isPartitionedDdoc = Helpers.isDDocPartitioned(ddocNoOptions, true); + expect(isPartitionedDdoc).toBe(true); + + isPartitionedDdoc = Helpers.isDDocPartitioned(ddocPartitionedTrue, true); + expect(isPartitionedDdoc).toBe(true); + }); + + it('returns false if database is partitioned but ddoc is set as non-partitioned', () => { + const isPartitionedDdoc = Helpers.isDDocPartitioned(ddocPartitionedFalse, true); + expect(isPartitionedDdoc).toBe(false); + }); + + }); + }); diff --git a/app/addons/documents/__tests__/main-fields-view.test.js b/app/addons/documents/__tests__/main-fields-view.test.js index fe108ca..3a5b960 100644 --- a/app/addons/documents/__tests__/main-fields-view.test.js +++ b/app/addons/documents/__tests__/main-fields-view.test.js @@ -21,7 +21,8 @@ describe('MainFieldsView', () => { stable: false, toggleStable: () => {}, update: 'true', - changeUpdateField: () => {} + changeUpdateField: () => {}, + enableStable: false }; const docURL = 'http://foo.com'; it('does not render reduce when showReduce is false', () => { @@ -110,6 +111,7 @@ describe('MainFieldsView', () => { toggleIncludeDocs={() => {}} toggleStable={spy} docURL={docURL} + enableStable={true} />); wrapper.find('#qoStable').simulate('change'); diff --git a/app/addons/documents/__tests__/query-options.test.js b/app/addons/documents/__tests__/query-options.test.js index 90d5256..4459ba0 100644 --- a/app/addons/documents/__tests__/query-options.test.js +++ b/app/addons/documents/__tests__/query-options.test.js @@ -10,11 +10,10 @@ // License for the specific language governing permissions and limitations under // the License. +import { shallow, mount } from 'enzyme'; import React from 'react'; -import ReactDOM from 'react-dom'; -import { shallow } from 'enzyme'; -import QueryOptions from '../index-results/components/queryoptions/QueryOptions'; import sinon from 'sinon'; +import QueryOptions from '../index-results/components/queryoptions/QueryOptions'; import Constants from '../constants'; describe('QueryOptions', () => { @@ -27,7 +26,10 @@ describe('QueryOptions', () => { queryOptionsToggleStable: () => {}, queryOptionsChangeUpdate: () => {}, stable: false, - update: 'true' + update: 'true', + betweenKeys: {}, + showReduce: true, + enableStable: true }; it('calls resetPagination and queryOptionsExecute on submit', () => { @@ -300,4 +302,50 @@ describe('QueryOptions', () => { const isHighlighted = wrapper.find('ToggleHeaderButton').prop('active'); expect(isHighlighted).toBe(false); }); + + it('stable option is only enabled when enableStable is true', () => { + const wrapper = mount(<QueryOptions + {...props} + ddocsOnly={true} + update='true' + queryOptionsRemoveFilterOnlyDdocs={() => {}} + queryOptionsApplyFilterOnlyDdocs={() => {}} + queryOptionsExecute={() => {}} + resetPagination={() => {}} + queryOptionsToggleVisibility={() => {}} + queryOptionsParams={{}} + selectedLayout={Constants.LAYOUT_ORIENTATION.METADATA} + changeLayout={() => {}} + showReduce={true} + enableStable={true} + />); + + expect(wrapper.find('input#qoStable').prop("disabled")).toBe(false); + wrapper.setProps({enableStable: false}); + expect(wrapper.find('input#qoStable').prop("disabled")).toBe(true); + }); + + it('reduce option is only displayed when showReduce is true', () => { + const wrapper = mount(<QueryOptions + {...props} + ddocsOnly={true} + update='true' + queryOptionsRemoveFilterOnlyDdocs={() => {}} + queryOptionsApplyFilterOnlyDdocs={() => {}} + queryOptionsExecute={() => {}} + resetPagination={() => {}} + queryOptionsToggleVisibility={() => {}} + queryOptionsParams={{}} + selectedLayout={Constants.LAYOUT_ORIENTATION.METADATA} + changeLayout={() => {}} + showReduce={true} + enableStable={true} + />); + + expect(wrapper.find('input#qoReduce').exists()).toBe(true); + + wrapper.setProps({showReduce: false}); + expect(wrapper.find('input#qoReduce').exists()).toBe(false); + }); + }); diff --git a/app/addons/documents/assets/less/query-options.less b/app/addons/documents/assets/less/query-options.less index 27e9cc0..f2ace1a 100644 --- a/app/addons/documents/assets/less/query-options.less +++ b/app/addons/documents/assets/less/query-options.less @@ -130,7 +130,8 @@ -webkit-user-select: none; } label.disabled { - color: #777777; + cursor: not-allowed; + opacity: .5; } div.controls-group.well{ height: 156px; diff --git a/app/addons/documents/helpers.js b/app/addons/documents/helpers.js index c438969..27623a9 100644 --- a/app/addons/documents/helpers.js +++ b/app/addons/documents/helpers.js @@ -122,6 +122,22 @@ const selectedViewContainsReduceFunction = (designDocs, selectedNavItem) => { return showReduce; }; +const selectedItemIsPartitionedView = (designDocs, selectedNavItem, isDbPartitioned) => { + if (!selectedNavItem) { + return false; + } + + let isPartitioned = false; + if (designDocs && isViewSelected(selectedNavItem)) { + const ddocID = '_design/' + selectedNavItem.designDocName; + const ddoc = designDocs.find(ddoc => ddoc._id === ddocID); + if (ddoc) { + isPartitioned = isDDocPartitioned(ddoc, isDbPartitioned); + } + } + return isPartitioned; +}; + const isViewSelected = (selectedNavItem) => { return (selectedNavItem.navItem === 'designDoc' && selectedNavItem.designDocSection === 'Views' @@ -147,5 +163,6 @@ export default { parseJSON, truncateDoc, selectedViewContainsReduceFunction, + selectedItemIsPartitionedView, isViewSelected }; diff --git a/app/addons/documents/index-results/components/queryoptions/MainFieldsView.js b/app/addons/documents/index-results/components/queryoptions/MainFieldsView.js index f36f652..179acf7 100644 --- a/app/addons/documents/index-results/components/queryoptions/MainFieldsView.js +++ b/app/addons/documents/index-results/components/queryoptions/MainFieldsView.js @@ -18,6 +18,7 @@ export default class MainFieldsView extends React.Component { super(props); this.toggleStable = this.toggleStable.bind(this); this.onUpdateChange = this.onUpdateChange.bind(this); + this.toggleIncludeDocs = this.toggleIncludeDocs.bind(this); this.updateOptions = [ {value: 'true', label: 'true'}, @@ -72,8 +73,21 @@ export default class MainFieldsView extends React.Component { this.props.toggleStable(this.props.stable); } - reduce() { - if (!this.props.showReduce) { + includeDocsOption() { + const {includeDocs, reduce} = this.props; + return ( + <div className="checkbox inline"> + <input disabled={reduce} onChange={this.toggleIncludeDocs} id="qoIncludeDocs" + name="include_docs" type="checkbox" checked={includeDocs}/> + <label className={reduce ? 'disabled' : ''} htmlFor="qoIncludeDocs" id="qoIncludeDocsLabel">Include + Docs</label> + </div> + ); + } + + reduceOption() { + const {showReduce, reduce} = this.props; + if (!showReduce) { return null; } @@ -81,7 +95,7 @@ export default class MainFieldsView extends React.Component { <span> <div className="checkbox inline"> <input id="qoReduce" name="reduce" onChange={this.toggleReduce.bind(this)} type="checkbox" - checked={this.props.reduce}/> + checked={reduce}/> <label htmlFor="qoReduce">Reduce</label> </div> {this.groupLevel()} @@ -89,12 +103,41 @@ export default class MainFieldsView extends React.Component { ); } - getUpdateOptions() { - return this.updateOptions.map(option => <option key={option.value} value={option.value}>{option.label}</option>); + stableOption() { + let {enableStable, stable} = this.props; + + if (!enableStable) { + // makes sure Stable option always appears unchecked when disabled + stable = false; + } + + return ( + <div className="checkbox inline"> + <input onChange={this.toggleStable} id="qoStable" name="stable" + type="checkbox" checked={stable} disabled={!enableStable}/> + <label className={enableStable ? '' : 'disabled'} htmlFor="qoStable" id="qoStableLabel">Stable</label> + </div> + ); + } + + updateOption() { + const { update } = this.props; + const selectOptions = this.updateOptions.map(option => { + return <option key={option.value} value={option.value}>{option.label}</option>; + }); + return ( + <div className="dropdown inline"> + <label className="drop-down"> + Update + <select className="input-small" id="qoUpdate" value={update} onChange={this.onUpdateChange}> + {selectOptions} + </select> + </label> + </div> + ); } render() { - let {includeDocs, stable, update} = this.props; return ( <div className="query-group" id="query-options-main-fields"> <span className="add-on"> @@ -103,31 +146,13 @@ export default class MainFieldsView extends React.Component { <i className="icon-question-sign"/> </a> </span> - <div className="controls-group qo-main-fields-row"> - <div className="row-fluid fieldsets"> - <div className="checkbox inline"> - <input disabled={this.props.reduce} onChange={this.toggleIncludeDocs.bind(this)} id="qoIncludeDocs" - name="include_docs" type="checkbox" checked={includeDocs}/> - <label className={this.props.reduce ? 'disabled' : ''} htmlFor="qoIncludeDocs" id="qoIncludeDocsLabel">Include - Docs</label> - </div> - {this.reduce()} - </div> - <div className="row-fluid fieldsets"> - <div className="checkbox inline"> - <input onChange={this.toggleStable} id="qoStable" - name="include_docs" type="checkbox" checked={stable}/> - <label htmlFor="qoStable" id="qoStableLabel">Stable</label> - </div> - <div className="dropdown inline"> - <label className="drop-down"> - Update - <select className="input-small" id="qoUpdate" value={update} onChange={this.onUpdateChange}> - {this.getUpdateOptions()} - </select> - </label> - </div> - </div> + <div className="row-fluid fieldsets"> + {this.includeDocsOption()} + {this.reduceOption()} + </div> + <div className="row-fluid fieldsets"> + {this.stableOption()} + {this.updateOption()} </div> </div> ); @@ -145,5 +170,7 @@ MainFieldsView.propTypes = { stable: PropTypes.bool.isRequired, toggleStable: PropTypes.func.isRequired, update: PropTypes.string.isRequired, - changeUpdateField: PropTypes.func.isRequired + changeUpdateField: PropTypes.func.isRequired, + showReduce: PropTypes.bool.isRequired, + enableStable: PropTypes.bool.isRequired }; diff --git a/app/addons/documents/index-results/components/queryoptions/QueryOptions.js b/app/addons/documents/index-results/components/queryoptions/QueryOptions.js index bbcab32..9b36447 100644 --- a/app/addons/documents/index-results/components/queryoptions/QueryOptions.js +++ b/app/addons/documents/index-results/components/queryoptions/QueryOptions.js @@ -113,6 +113,7 @@ export default class QueryOptions extends React.Component { groupLevel={this.props.groupLevel} updateGroupLevel={this.props.queryOptionsUpdateGroupLevel} docURL={FauxtonAPI.constants.DOC_URLS.GENERAL} + enableStable={this.props.enableStable} stable={this.props.stable} toggleStable={this.props.queryOptionsToggleStable} update={this.props.update} diff --git a/app/addons/documents/index-results/containers/QueryOptionsContainer.js b/app/addons/documents/index-results/containers/QueryOptionsContainer.js index 344e9e4..2c1d4fa 100644 --- a/app/addons/documents/index-results/containers/QueryOptionsContainer.js +++ b/app/addons/documents/index-results/containers/QueryOptionsContainer.js @@ -46,7 +46,15 @@ const showReduce = (designDocs, selectedNavItem) => { return DocHelpers.selectedViewContainsReduceFunction(designDocs, selectedNavItem); }; -const mapStateToProps = ({indexResults, sidebar}, ownProps) => { +const enableStable = (designDocs, selectedNavItem, isDbPartitioned) => { + if (DocHelpers.isViewSelected(selectedNavItem)) { + const enableStable = !DocHelpers.selectedItemIsPartitionedView(designDocs, selectedNavItem, isDbPartitioned); + return enableStable; + } + return true; +}; + +const mapStateToProps = ({indexResults, sidebar, databases}, ownProps) => { const queryOptionsPanel = getQueryOptionsPanel(indexResults); return { contentVisible: queryOptionsPanel.isVisible, @@ -61,6 +69,7 @@ const mapStateToProps = ({indexResults, sidebar}, ownProps) => { descending: queryOptionsPanel.descending, skip: queryOptionsPanel.skip, limit: queryOptionsPanel.limit, + enableStable: enableStable(sidebar.designDocList, ownProps.selectedNavItem, databases.isDbPartitioned), stable: queryOptionsPanel.stable, update: queryOptionsPanel.update, fetchParams: getFetchParams(indexResults),