Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
3d6c120
COMPASS 235: DV - Full range component
pzrq Oct 30, 2016
f888ae6
Add JSDoc of proposed coupling between RangeInput validationStates
pzrq Oct 28, 2016
1169102
Add RuleCategoryRange unit and Rule integration tests
pzrq Oct 31, 2016
e435d61
Update RangeInput.value to no longer be isRequired
pzrq Oct 31, 2016
c97bfa9
Add failing test for the empty range state
pzrq Oct 31, 2016
0918163
Mostly working, but for some reason renders the stale value...
pzrq Oct 31, 2016
4a96c33
Refactor componentWillMount into the constructor
pzrq Oct 31, 2016
3e1e8dc
Refactor onRangeInputBlur to the end of validate
pzrq Oct 31, 2016
311ccf7
Remove 10 from parseFloat
pzrq Oct 31, 2016
f14e53e
Drop unused RangeInput.onChange prop
pzrq Oct 31, 2016
d5efc05
Be clearer which store
pzrq Oct 31, 2016
fb90307
Call validate after changing the dropdownOp value
pzrq Oct 31, 2016
54d1a75
Remove componentWillReceiveProps
pzrq Oct 31, 2016
53d73be
ESlint
pzrq Oct 31, 2016
3fc8115
Fix race where validationState is not allowed to be empty string
pzrq Oct 31, 2016
6a306a2
Finally, playing with the (unvalidated) component works correctly
pzrq Oct 31, 2016
67b9a81
Refactor into validateCombinedParams
pzrq Oct 31, 2016
e1f15b2
Finally get comboValidationState working
pzrq Oct 31, 2016
c5d3d40
Add a regex to warn the user they cannot enter expressions like 10+5
pzrq Oct 31, 2016
b9f68ff
Handle and test the 0 case correctly
pzrq Oct 31, 2016
770d7f7
Improve comments
pzrq Oct 31, 2016
b59811d
Add better regex to handle exponential float forms
pzrq Nov 1, 2016
6608859
Fix translating regexes fail (the |, vertical bar ASCII code 0x7c sho…
pzrq Nov 1, 2016
ddf7a40
Add some Decimal128 tests
pzrq Nov 1, 2016
8fe1b88
Handle Decimal128 and drop clunky parseFloat uses
pzrq Nov 1, 2016
55978f3
Be more explicit that combined ranges of Decimal128s are not validated
pzrq Nov 1, 2016
f19bf05
shrink range inputs, remove title labels.
Nov 1, 2016
f038bf1
:shirt: fix linter issue.
Nov 1, 2016
e6453c3
don’t validate initially, fix tests.
Nov 2, 2016
c908d79
form validation. such difficult.
Nov 2, 2016
a301d45
[wip] up and downwards validation almost working
Nov 2, 2016
a50f8f9
fixing some edge cases, updating tests.
Nov 2, 2016
400d382
Restore tests so accepts/rejects is clearer
pzrq Nov 3, 2016
0d1054e
make rule id consistent (based on index of rule).
Nov 3, 2016
978538f
store cancel always needs to call _updateState()
Nov 3, 2016
2a91175
range: disable bson type casting
Nov 3, 2016
8214d33
cancelChanges no longer takes a boolean.
Nov 3, 2016
6f5e7d6
validation rewritten. only use single validate() method
Nov 6, 2016
7d9cf64
use componentWillReceiveProps where props are forked.
Nov 6, 2016
478057a
force a redraw when cancel was pressed via key change
Nov 6, 2016
20f734c
:shirt: fixing eslint issues.
Nov 6, 2016
106d496
Drop HP_VERSION until COMPASS-294
pzrq Nov 8, 2016
f9eb65b
:green_heart: fix tests.
Nov 8, 2016
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
149 changes: 88 additions & 61 deletions src/internal-packages/validation/lib/components/common/range-input.jsx
Original file line number Diff line number Diff line change
@@ -1,86 +1,118 @@
const React = require('react');
const app = require('ampersand-app');
const _ = require('lodash');
const FormGroup = require('react-bootstrap').FormGroup;
const InputGroup = require('react-bootstrap').InputGroup;
const FormControl = require('react-bootstrap').FormControl;
const DropdownButton = require('react-bootstrap').DropdownButton;
const ControlLabel = require('react-bootstrap').ControlLabel;
const MenuItem = require('react-bootstrap').MenuItem;
const TypeChecker = require('hadron-type-checker');

// const debug = require('debug')('mongodb-compass:validation:action-selector');

/**
* The version at which high precision values are available.
*/
const HP_VERSION = '3.4.0';

/**
* A RangeInput represents a numeric lower or upper bound and the value of the
* lower or upper bound, if the bound exists.
*
* The `validationState` of this RangeInput can be set to 'error' either:
* - by the RangeInput validating the `value` prop is not of type `number`, or
* - by the RangeInput's parent `RuleCategoryRange` component validating the
* combined range expression, such as `5 < x < 5` is displayed as red/error
* even though the RangeInputs `5 < x` and `5 > x` are individually valid.
*/
class RangeInput extends React.Component {

constructor(props) {
super(props);
const op = this._getOperatorString(props);
this.state = {
disabled: false,
operator: '',
value: '',
validationState: null
};
}

componentWillMount() {
const op = this._getOperatorString();
this.setState({
value: _.isNumber(this.props.value) ? String(this.props.value) : '',
operator: op,
disabled: op === 'none'
});
}

componentWillReceiveProps(nextProps) {
const op = this._getOperatorString(nextProps);
this.setState({
value: _.isNumber(this.props.value) ? String(this.props.value) : '',
disabled: op === 'none',
operator: op,
disabled: op === 'none'
});
value: this.props.value,
isValid: true,
hasStartedValidating: false
};
this._ENABLE_HP = app.instance && (
app.instance.build.version >= HP_VERSION);
}

/**
* called whenever the input changes (i.e. user is typing). We don't bubble up the
* value at this stage yet, but wait until the user blurs the input field.
*
* @param {Object} evt The onChange event
*/
onInputChange(evt) {
this.setState({
value: evt.target.value
});
}

/**
* called whenever the field is blurred (loses focus). At this point, we want to
* validate the input and if it is valid, report the value change up to the parent.
*/
onInputBlur() {
this.validate();
this.validate(true);
this.props.onRangeInputBlur({
value: this.state.value,
operator: this.state.operator
});
}

/**
* called when the user chooses a value from the operator dropdown. As there are
* no invalid values, we can always immediately report up the value/operator change.
*
* @param {Object} evtKey the selected value from the dropdown (e.g. "<=", "none", ...)
*/
onDropdownSelect(evtKey) {
this.setState({
disabled: evtKey === 'none',
operator: evtKey
});
// need to defer validation until setState has propagated
// _.defer(() => {
// this.validate();
// });
this.props.onRangeInputBlur({
value: this.state.value,
operator: evtKey
});
}

validate() {
const value = parseFloat(this.state.value, 10);
let error = false;
if (_.isNaN(value)) {
error = true;
this.setState({
validationState: 'error'
});
} else {
this.setState({
validationState: null
});
/**
* determines if the input by itself is valid (e.g. a value that can be
* cast to a number).
*
* @param {Boolean} force forces validation from now on.
* @return {Boolean} whether the input is valid or not.
*/
validate(force) {
if (!force && !this.state.hasStartedValidating) {
return true;
}
if (this.props.onChange) {
this.props.onChange({
disabled: this.state.disabled,
operator: this.state.operator,
value: value,
hasError: error
});
if (this.state.disabled) {
return true;
}
const value = this.state.value;
const valueTypes = TypeChecker.castableTypes(value, this._ENABLE_HP);

// Not sure if hadron-type-checker should make NUMBER_TYPES public
const NUMBER_TYPES = [
'Long',
'Int32',
'Double',
'Decimal128'
];

const isValid = (_.intersection(valueTypes, NUMBER_TYPES).length > 0);
this.setState({
isValid: isValid,
hasStartedValidating: true
});
return isValid;
}

_getOperatorString(props) {
Expand Down Expand Up @@ -124,9 +156,6 @@ class RangeInput extends React.Component {
if (this.state.disabled) {
return (
<FormGroup>
<div>
<ControlLabel>{boundString}</ControlLabel>
</div>
<DropdownButton
id={`range-input-${this.props.upperBound ? 'upper' : 'lower'}`}
style={{width: this.props.width}}
Expand All @@ -138,13 +167,10 @@ class RangeInput extends React.Component {
);
}
// not disabled, render input group with value input and operator dropdown
const placeholder = `enter ${boundString}`.toLowerCase();

const placeholder = `${boundString}`.toLowerCase();
const validationState = this.state.isValid ? null : 'error';
return (
<FormGroup validationState={this.state.validationState}>
<div>
<ControlLabel>{boundString}</ControlLabel>
</div>
<FormGroup validationState={this.props.validationState || validationState}>
<InputGroup style={{width: this.props.width}}>
<DropdownButton
id={`range-input-${this.props.upperBound ? 'upper' : 'lower'}`}
Expand All @@ -167,22 +193,23 @@ class RangeInput extends React.Component {
}

RangeInput.propTypes = {
value: React.PropTypes.number.isRequired,
value: React.PropTypes.string, // Can't be required to allow "none" in GUI,
// can't be number to work with Decimal128.
upperBound: React.PropTypes.bool,
validationState: React.PropTypes.string,
boundIncluded: React.PropTypes.bool.isRequired,
disabled: React.PropTypes.bool.isRequired,
onChange: React.PropTypes.func,
onRangeInputBlur: React.PropTypes.func,
width: React.PropTypes.number
};

RangeInput.defaultProps = {
disabled: false,
boundIncluded: false,
upperBound: false,
validationState: '',
value: null,
width: 200
validationState: null,
value: '',
width: 160
};

RangeInput.displayName = 'RangeInput';
Expand Down
65 changes: 53 additions & 12 deletions src/internal-packages/validation/lib/components/rule-builder.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,24 @@ const Table = ReactBootstrap.Table;

class RuleBuilder extends React.Component {

constructor(props) {
super(props);
this.state = {
isValid: true,
forceRenderKey: 0
};
}

componentWillReceiveProps(props) {
this.validate(false);
if (props.editState === 'unmodified' && this.props.editState !== 'unmodified') {
// force a complete redraw of the component by increasing the key
this.setState({
forceRenderKey: this.state.forceRenderKey + 1,
isValid: true
});
}
}

/**
* Add button clicked to create a new rule.
Expand Down Expand Up @@ -52,29 +70,52 @@ class RuleBuilder extends React.Component {

/**
* The "Update" button from the `Editable component has been clicked.
* Send the new validator doc to the server.
* Check that all rules are valid, then send the new validator doc to
* the server.
*/
onUpdate() {
ValidationActions.saveChanges();
if (this.validate(true)) {
ValidationActions.saveChanges();
}
}

validate(force) {
const isValid = _.all(this.props.validationRules, (rule) => {
return this.refs[rule.id].validate(force);
});
this.setState({
isValid: isValid
});
return isValid;
}

renderRules() {
return _.map(this.props.validationRules, (rule) => {
return <Rule ref={rule.id} key={rule.id} {...rule} />;
});
}
/**
* Render status row component.
*
* @returns {React.Component} The component.
*/
render() {
const rules = _.map(this.props.validationRules, (rule) => {
return <Rule key={rule.id} {...rule} />;
});
const editableProps = {
editState: this.props.editState,
childName: 'Validation',
onCancel: this.onCancel.bind(this),
onUpdate: this.onUpdate.bind(this),
key: this.state.forceRenderKey
};

if (!this.state.isValid) {
editableProps.editState = 'error';
editableProps.errorMessage = 'Input is not valid.';
delete editableProps.childName;
}

return (
<Editable
editState={this.props.editState}
childName="Validation"
onCancel={this.onCancel.bind(this)}
onUpdate={this.onUpdate.bind(this)}
>
<Editable {...editableProps} >
<Grid fluid className="rule-builder">
<Row className="header">
<Col lg={6} md={6} sm={6} xs={6}>
Expand Down Expand Up @@ -118,7 +159,7 @@ class RuleBuilder extends React.Component {
</tr>
</thead>
<tbody>
{rules}
{this.renderRules()}
</tbody>
</Table>
</Col>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,6 @@ const _ = require('lodash');

class RuleCategoryExists extends React.Component {

constructor(props) {
super(props);
}

/**
* get the initial parameters for this rule category.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@ module.exports = {
exists: require('./exists'),
mustNotExist: require('./mustnotexist'),
type: require('./type'),
// range: require('./range'), // work in progress
range: require('./range'),
regex: require('./regex')
};
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,6 @@ const _ = require('lodash');

class RuleCategoryMustNotExist extends React.Component {

constructor(props) {
super(props);
}

/**
* get the initial parameters for this rule category.
*
Expand Down
Loading