From 895f270d2ccb04738745ad8bdb1c951bb7c719b4 Mon Sep 17 00:00:00 2001 From: Thomas Rueckstiess Date: Tue, 1 Nov 2016 16:54:27 +1100 Subject: [PATCH 1/2] COMPASS-199 make JSON view editable and disable view switcher if validator doc cannot be expressed by rule builder. --- .../validation/lib/actions/index.js | 1 + .../lib/components/common/view-switcher.jsx | 13 +++- .../validation/lib/components/json-input.jsx | 70 +++++++++++++++++++ .../validation/lib/components/json-view.jsx | 11 ++- .../lib/components/rule-builder.jsx | 4 +- .../validation/lib/components/validation.jsx | 9 ++- .../validation/lib/stores/index.js | 57 ++++++++++++--- .../validation/styles/index.less | 1 + .../validation/styles/json-input.less | 7 ++ 9 files changed, 149 insertions(+), 24 deletions(-) create mode 100644 src/internal-packages/validation/lib/components/json-input.jsx create mode 100644 src/internal-packages/validation/styles/json-input.less diff --git a/src/internal-packages/validation/lib/actions/index.js b/src/internal-packages/validation/lib/actions/index.js index 32ebc613542..84a4b4101e5 100644 --- a/src/internal-packages/validation/lib/actions/index.js +++ b/src/internal-packages/validation/lib/actions/index.js @@ -13,6 +13,7 @@ const ValidationActions = Reflux.createActions([ 'setRuleNullable', 'setValidationLevel', 'setValidationAction', + 'setValidatorDocument', 'switchView', 'saveChanges', 'cancelChanges' diff --git a/src/internal-packages/validation/lib/components/common/view-switcher.jsx b/src/internal-packages/validation/lib/components/common/view-switcher.jsx index 005cac798e5..7dd39414a3d 100644 --- a/src/internal-packages/validation/lib/components/common/view-switcher.jsx +++ b/src/internal-packages/validation/lib/components/common/view-switcher.jsx @@ -15,7 +15,12 @@ class ViewSwitcher extends React.Component { return _.map(this.props.buttonLabels, (label) => { const active = this.props.activeButton === label; return ( - ); @@ -44,13 +49,15 @@ ViewSwitcher.propTypes = { label: React.PropTypes.string, buttonLabels: React.PropTypes.arrayOf(React.PropTypes.string).isRequired, activeButton: React.PropTypes.string, - onClick: React.PropTypes.func + onClick: React.PropTypes.func, + disabled: React.PropTypes.bool }; ViewSwitcher.defaultProps = { label: '', activeButton: '', - onClick: () => {} + onClick: () => {}, + disabled: false }; ViewSwitcher.displayName = 'ViewSwitcher'; diff --git a/src/internal-packages/validation/lib/components/json-input.jsx b/src/internal-packages/validation/lib/components/json-input.jsx new file mode 100644 index 00000000000..b130737e8ba --- /dev/null +++ b/src/internal-packages/validation/lib/components/json-input.jsx @@ -0,0 +1,70 @@ +const React = require('react'); +const { FormControl, FormGroup } = require('react-bootstrap'); +const ValidationActions = require('../actions'); + +// const debug = require('debug')('mongodb-compass:validation:json-input'); + +class JSONInput extends React.Component { + + constructor(props) { + super(props); + + this.state = { + errorState: '', + input: props.validatorDoc ? + JSON.stringify(props.validatorDoc, null, 2) : '{}' + }; + } + + componentWillReceiveProps(newProps) { + this.setState({ + input: JSON.stringify(newProps.validatorDoc, null, 2) + }); + } + + onInputChanged(evt) { + this.setState({ + input: evt.target.value + }); + } + + onBlur() { + const doc = this.validate(); + if (doc) { + ValidationActions.setValidatorDocument(doc); + } + } + + validate() { + try { + return JSON.parse(this.state.input); + } catch (e) { + this.setState({ + errorState: 'not valid JSON.' + }); + return false; + } + } + + render() { + return ( + + + + ); + } +} + +JSONInput.propTypes = { + validatorDoc: React.PropTypes.object.isRequired +}; + +JSONInput.displayName = 'JSONInput'; + +module.exports = JSONInput; diff --git a/src/internal-packages/validation/lib/components/json-view.jsx b/src/internal-packages/validation/lib/components/json-view.jsx index 2caac435d3f..7d82b38da63 100644 --- a/src/internal-packages/validation/lib/components/json-view.jsx +++ b/src/internal-packages/validation/lib/components/json-view.jsx @@ -2,6 +2,7 @@ const React = require('react'); const ValidationActions = require('../actions'); const OptionSelector = require('./common/option-selector'); const Editable = require('./common/editable'); +const JSONInput = require('./json-input'); const ReactBootstrap = require('react-bootstrap'); const Grid = ReactBootstrap.Grid; @@ -18,7 +19,7 @@ class JSONView extends React.Component { * @param {String} action the chosen action, one of `warn`, `error`. */ onActionSelect(action) { - ValidationActions.setValidationAction(action); + ValidationActions.setValidationAction(action, false); } /** @@ -27,7 +28,7 @@ class JSONView extends React.Component { * @param {String} level the chosen level, one of `off`, `moderate`, `strict` */ onLevelSelect(level) { - ValidationActions.setValidationLevel(level); + ValidationActions.setValidationLevel(level, false); } /** @@ -85,11 +86,7 @@ class JSONView extends React.Component {
-
{JSON.stringify(this.props.validatorDoc, null, 2)}
+
diff --git a/src/internal-packages/validation/lib/components/rule-builder.jsx b/src/internal-packages/validation/lib/components/rule-builder.jsx index 9fe06a9acb8..dd3f957120f 100644 --- a/src/internal-packages/validation/lib/components/rule-builder.jsx +++ b/src/internal-packages/validation/lib/components/rule-builder.jsx @@ -30,7 +30,7 @@ class RuleBuilder extends React.Component { * @param {String} action the chosen action, one of `warn`, `error`. */ onActionSelect(action) { - ValidationActions.setValidationAction(action); + ValidationActions.setValidationAction(action, true); } /** @@ -39,7 +39,7 @@ class RuleBuilder extends React.Component { * @param {String} level the chosen level, one of `off`, `moderate`, `strict` */ onLevelSelect(level) { - ValidationActions.setValidationLevel(level); + ValidationActions.setValidationLevel(level, true); } /** diff --git a/src/internal-packages/validation/lib/components/validation.jsx b/src/internal-packages/validation/lib/components/validation.jsx index a0cd5e3e8e9..b119c25d337 100644 --- a/src/internal-packages/validation/lib/components/validation.jsx +++ b/src/internal-packages/validation/lib/components/validation.jsx @@ -54,6 +54,10 @@ class Validation extends React.Component { editState={this.props.editState} /> ); + + const activeButton = this.props.isExpressibleByRules ? + this.props.viewMode : 'JSON'; + return (
@@ -66,8 +70,9 @@ class Validation extends React.Component { {view} @@ -80,6 +85,7 @@ class Validation extends React.Component { Validation.propTypes = { editState: React.PropTypes.oneOf(['unmodified', 'modified', 'updating', 'error', 'success']).isRequired, viewMode: React.PropTypes.oneOf(['Rule Builder', 'JSON']).isRequired, + isExpressibleByRules: React.PropTypes.bool.isRequired, validationAction: React.PropTypes.oneOf(['warn', 'error']).isRequired, validatorDoc: React.PropTypes.object.isRequired, validationLevel: React.PropTypes.oneOf(['off', 'moderate', 'strict']).isRequired, @@ -89,6 +95,7 @@ Validation.propTypes = { Validation.defaultProps = { editState: 'unmodified', viewMode: 'Rule Builder', + isExpressibleByRules: true, validationAction: 'warn', validatorDoc: {}, validationLevel: 'off', diff --git a/src/internal-packages/validation/lib/stores/index.js b/src/internal-packages/validation/lib/stores/index.js index 92cfafa3508..6a62e1b138c 100644 --- a/src/internal-packages/validation/lib/stores/index.js +++ b/src/internal-packages/validation/lib/stores/index.js @@ -34,7 +34,6 @@ const ValidationStore = Reflux.createStore({ */ init() { this.lastFetchedValidatorDoc = {}; - NamespaceStore.listen((ns) => { if (ns && toNS(ns).collection) { ValidationActions.fetchValidationRules(); @@ -86,9 +85,6 @@ const ValidationStore = Reflux.createStore({ const rules = _.map(validator, (field) => { const fieldName = field[0]; const rule = field[1]; - debug('structure of field is:', field); - debug('rule is:', rule); - debug('fieldName is:', fieldName); let parameters; const result = helper.nullableOrValidator(fieldName, rule); @@ -241,7 +237,6 @@ const ValidationStore = Reflux.createStore({ fetchState: 'fetching' }); this._fetchFromServer((err, res) => { - debug('result from server', err, res); if (err || !_.has(res, 'options')) { // an error occured during fetch, e.g. missing permissions this.setState({ @@ -252,8 +247,8 @@ const ValidationStore = Reflux.createStore({ const result = this._deconstructValidatorDoc(res.options); // store result from server - this.lastFetchedValidatorDoc = this._constructValidatorDoc(result); - const validatorDoc = _.clone(this.lastFetchedValidatorDoc); + const validatorDoc = res.options; + this.lastFetchedValidatorDoc = _.clone(validatorDoc); if (!result) { // the validatorDoc has an unexpected format. @@ -277,6 +272,7 @@ const ValidationStore = Reflux.createStore({ this.setState({ fetchState: 'success', isExpressibleByRules: false, + viewMode: 'JSON', validatorDoc: validatorDoc, validationLevel: result.level, validationAction: result.action, @@ -289,6 +285,7 @@ const ValidationStore = Reflux.createStore({ this.setState({ fetchState: 'success', isExpressibleByRules: true, + viewMode: 'Rule Builder', validatorDoc: validatorDoc, validationRules: result.rules, validationLevel: result.level, @@ -359,16 +356,54 @@ const ValidationStore = Reflux.createStore({ this._updateState({rules: rules}); }, - setValidationAction(validationAction) { - if (_.includes(['warn', 'error'], validationAction)) { + setValidatorDocument(validatorDoc) { + const result = this._deconstructValidatorDoc(validatorDoc); + + this.setState({ + validatorDoc: validatorDoc, + validationRules: result.rules || [], + validationLevel: result.level, + validationAction: result.action, + isExpressibleByRules: _.isArray(result.rules), + editState: _.isEqual(this.lastFetchedValidatorDoc, validatorDoc) ? + 'unmodified' : 'modified' + }); + }, + + setValidationAction(validationAction, setByRuleBuilder) { + if (!_.includes(['warn', 'error'], validationAction)) { + return; + } + if (setByRuleBuilder) { this._updateState({action: validationAction}); + return; } + const validatorDoc = _.clone(this.state.validatorDoc); + validatorDoc.validationAction = validationAction; + this.setState({ + validatorDoc: validatorDoc, + validationAction: validationAction, + editState: _.isEqual(this.lastFetchedValidatorDoc, validatorDoc) ? + 'unmodified' : 'modified' + }); }, - setValidationLevel(validationLevel) { - if (_.includes(['off', 'moderate', 'strict'], validationLevel)) { + setValidationLevel(validationLevel, setByRuleBuilder) { + if (!_.includes(['off', 'moderate', 'strict'], validationLevel)) { + return; + } + if (setByRuleBuilder) { this._updateState({level: validationLevel}); + return; } + const validatorDoc = _.clone(this.state.validatorDoc); + validatorDoc.validationLevel = validationLevel; + this.setState({ + validatorDoc: validatorDoc, + validationLevel: validationLevel, + editState: _.isEqual(this.lastFetchedValidatorDoc, validatorDoc) ? + 'unmodified' : 'modified' + }); }, /** diff --git a/src/internal-packages/validation/styles/index.less b/src/internal-packages/validation/styles/index.less index d8e96a4ee1c..6056a1148e5 100644 --- a/src/internal-packages/validation/styles/index.less +++ b/src/internal-packages/validation/styles/index.less @@ -6,3 +6,4 @@ @import './json-view.less'; @import './option-selector.less'; @import './editable.less'; +@import './json-input.less'; diff --git a/src/internal-packages/validation/styles/json-input.less b/src/internal-packages/validation/styles/json-input.less new file mode 100644 index 00000000000..cbcf5b46823 --- /dev/null +++ b/src/internal-packages/validation/styles/json-input.less @@ -0,0 +1,7 @@ +.json-input { + &-textarea { + font-family: Menlo, Monaco, Consolas, "Courier New", monospace; + height: 250px !important; + width: 90%; + } +} From eda82f599ff1a4250fdb5ace2fcdab67cfc70390 Mon Sep 17 00:00:00 2001 From: Thomas Rueckstiess Date: Tue, 1 Nov 2016 21:20:10 +1100 Subject: [PATCH 2/2] COMPASS-199 make JSON editable, validate JSON --- .../lib/components/common/editable.jsx | 13 ++- .../validation/lib/components/json-input.jsx | 70 --------------- .../validation/lib/components/json-view.jsx | 86 ++++++++++++++++--- .../validation/lib/components/validation.jsx | 19 ++-- .../validation/lib/stores/index.js | 13 ++- .../validation/styles/json-input.less | 3 +- .../validation/styles/validation.less | 4 + test/validation.store.test.js | 21 ----- 8 files changed, 110 insertions(+), 119 deletions(-) delete mode 100644 src/internal-packages/validation/lib/components/json-input.jsx diff --git a/src/internal-packages/validation/lib/components/common/editable.jsx b/src/internal-packages/validation/lib/components/common/editable.jsx index 427b3e220d9..ca7a09e7da1 100644 --- a/src/internal-packages/validation/lib/components/common/editable.jsx +++ b/src/internal-packages/validation/lib/components/common/editable.jsx @@ -23,6 +23,17 @@ class Editable extends React.Component {
); } + if (this.props.editState === 'error') { + return ( +
+ +
+ ); + } return null; } @@ -39,7 +50,7 @@ class Editable extends React.Component { case 'error': if (errorMsg) { return name ? `${name} could not be updated: ${errorMsg}` : - `Error during update: ${errorMsg}`; + `Error: ${errorMsg}`; } return name ? `${name} could not be updated.` : 'An error occurred during the update.'; default: return ''; diff --git a/src/internal-packages/validation/lib/components/json-input.jsx b/src/internal-packages/validation/lib/components/json-input.jsx deleted file mode 100644 index b130737e8ba..00000000000 --- a/src/internal-packages/validation/lib/components/json-input.jsx +++ /dev/null @@ -1,70 +0,0 @@ -const React = require('react'); -const { FormControl, FormGroup } = require('react-bootstrap'); -const ValidationActions = require('../actions'); - -// const debug = require('debug')('mongodb-compass:validation:json-input'); - -class JSONInput extends React.Component { - - constructor(props) { - super(props); - - this.state = { - errorState: '', - input: props.validatorDoc ? - JSON.stringify(props.validatorDoc, null, 2) : '{}' - }; - } - - componentWillReceiveProps(newProps) { - this.setState({ - input: JSON.stringify(newProps.validatorDoc, null, 2) - }); - } - - onInputChanged(evt) { - this.setState({ - input: evt.target.value - }); - } - - onBlur() { - const doc = this.validate(); - if (doc) { - ValidationActions.setValidatorDocument(doc); - } - } - - validate() { - try { - return JSON.parse(this.state.input); - } catch (e) { - this.setState({ - errorState: 'not valid JSON.' - }); - return false; - } - } - - render() { - return ( - - - - ); - } -} - -JSONInput.propTypes = { - validatorDoc: React.PropTypes.object.isRequired -}; - -JSONInput.displayName = 'JSONInput'; - -module.exports = JSONInput; diff --git a/src/internal-packages/validation/lib/components/json-view.jsx b/src/internal-packages/validation/lib/components/json-view.jsx index 7d82b38da63..92d6b20a6f9 100644 --- a/src/internal-packages/validation/lib/components/json-view.jsx +++ b/src/internal-packages/validation/lib/components/json-view.jsx @@ -2,17 +2,41 @@ const React = require('react'); const ValidationActions = require('../actions'); const OptionSelector = require('./common/option-selector'); const Editable = require('./common/editable'); -const JSONInput = require('./json-input'); -const ReactBootstrap = require('react-bootstrap'); -const Grid = ReactBootstrap.Grid; -const Row = ReactBootstrap.Row; -const Col = ReactBootstrap.Col; +const {Grid, Row, Col, FormGroup, FormControl} = require('react-bootstrap'); // const debug = require('debug')('validation:json-view'); class JSONView extends React.Component { + constructor(props) { + super(props); + this.state = { + isValidJSON: true, + input: props.validatorDoc ? + JSON.stringify(props.validatorDoc.validator, null, 2) : '{}' + }; + } + + componentWillReceiveProps(newProps) { + this.setState({ + input: JSON.stringify(newProps.validatorDoc.validator, null, 2) + }); + } + + onInputChanged(evt) { + this.setState({ + input: evt.target.value + }); + } + + onBlur() { + const doc = this.validate(); + if (doc) { + ValidationActions.setValidatorDocument(doc); + } + } + /** * New value from the validation action dropdown chosen. * @@ -36,6 +60,9 @@ class JSONView extends React.Component { * Revert all changes to the server state. */ onCancel() { + this.setState({ + isValidJSON: true + }); ValidationActions.cancelChanges(); } @@ -47,19 +74,46 @@ class JSONView extends React.Component { ValidationActions.saveChanges(); } + validate() { + try { + const doc = { + validator: JSON.parse(this.state.input), + validationLevel: this.props.validationLevel, + validationAction: this.props.validationAction + }; + this.setState({ + isValidJSON: true + }); + return doc; + } catch (e) { + this.setState({ + isValidJSON: false + }); + return false; + } + } + /** * Render status row component. * * @returns {React.Component} The component. */ render() { + const editableProps = { + editState: this.props.editState, + childName: 'Validation', + onCancel: this.onCancel.bind(this), + onUpdate: this.onUpdate.bind(this) + }; + + if (!this.state.isValidJSON) { + editableProps.editState = 'error'; + editableProps.errorMessage = 'Input is not valid JSON.'; + delete editableProps.childName; + } + return ( - + @@ -86,7 +140,15 @@ class JSONView extends React.Component {
- + + +
diff --git a/src/internal-packages/validation/lib/components/validation.jsx b/src/internal-packages/validation/lib/components/validation.jsx index b119c25d337..3bfa7d00dc3 100644 --- a/src/internal-packages/validation/lib/components/validation.jsx +++ b/src/internal-packages/validation/lib/components/validation.jsx @@ -40,12 +40,14 @@ class Validation extends React.Component { render() { const view = this.props.viewMode === 'Rule Builder' ? ( - +
+ +
) : ( - - This is an example status row with a link. - {' '} - more info - { - const id = ValidationStore.state.validationRules[0].id; - ValidationStore.setRuleField(id, 'foobar'); - ValidationStore.setRuleField(id, 'number'); - - expect(spy.callCount).to.be.equal(4); - - const editState = spy.thirdCall.args[0].editState; - expect(editState).to.have.equal('unmodified'); - done(); - }, 10); - }); - it('addValidationRule() adds a new empty rule', function(done) { mockFetchFromServer(null, mockValidatorDoc);