mirror of
https://github.com/apache/superset.git
synced 2026-04-19 08:04:53 +00:00
[explorev2] adding support for client side validators on controls (#1920)
* Adding support for client side validators on controls * Applying validators to more fields * Addressing comments
This commit is contained in:
committed by
GitHub
parent
fc74fbeeaa
commit
470a6e9d76
@@ -11,14 +11,12 @@ const propTypes = {
|
||||
|
||||
const defaultProps = {
|
||||
value: false,
|
||||
label: null,
|
||||
description: null,
|
||||
onChange: () => {},
|
||||
};
|
||||
|
||||
export default class CheckboxField extends React.Component {
|
||||
onToggle() {
|
||||
this.props.onChange(this.props.name);
|
||||
this.props.onChange(!this.props.value);
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
import React, { PropTypes } from 'react';
|
||||
import { ControlLabel, OverlayTrigger, Tooltip } from 'react-bootstrap';
|
||||
import InfoTooltipWithTrigger from '../../components/InfoTooltipWithTrigger';
|
||||
|
||||
const propTypes = {
|
||||
label: PropTypes.string.isRequired,
|
||||
description: PropTypes.string,
|
||||
validationErrors: PropTypes.array,
|
||||
};
|
||||
|
||||
const defaultProps = {
|
||||
description: null,
|
||||
validationErrors: [],
|
||||
};
|
||||
|
||||
export default function ControlHeader({ label, description, validationErrors }) {
|
||||
const hasError = (validationErrors.length > 0);
|
||||
return (
|
||||
<ControlLabel>
|
||||
{hasError ?
|
||||
<strong className="text-danger">{label}</strong> :
|
||||
<span>{label}</span>
|
||||
}
|
||||
{' '}
|
||||
{(validationErrors.length > 0) &&
|
||||
<span>
|
||||
<OverlayTrigger
|
||||
placement="right"
|
||||
overlay={
|
||||
<Tooltip id={'error-tooltip'}>
|
||||
{validationErrors.join(' ')}
|
||||
</Tooltip>
|
||||
}
|
||||
>
|
||||
<i className="fa fa-exclamation-circle text-danger" />
|
||||
</OverlayTrigger>
|
||||
{' '}
|
||||
</span>
|
||||
}
|
||||
{description &&
|
||||
<InfoTooltipWithTrigger label={label} tooltip={description} />
|
||||
}
|
||||
</ControlLabel>
|
||||
);
|
||||
}
|
||||
|
||||
ControlHeader.propTypes = propTypes;
|
||||
ControlHeader.defaultProps = defaultProps;
|
||||
@@ -1,26 +0,0 @@
|
||||
import React, { PropTypes } from 'react';
|
||||
import { ControlLabel } from 'react-bootstrap';
|
||||
import InfoTooltipWithTrigger from '../../components/InfoTooltipWithTrigger';
|
||||
|
||||
const propTypes = {
|
||||
label: PropTypes.string.isRequired,
|
||||
description: PropTypes.string,
|
||||
};
|
||||
|
||||
const defaultProps = {
|
||||
description: null,
|
||||
};
|
||||
|
||||
export default function ControlLabelWithTooltip({ label, description }) {
|
||||
return (
|
||||
<ControlLabel>
|
||||
{label}
|
||||
{description &&
|
||||
<InfoTooltipWithTrigger label={label} tooltip={description} />
|
||||
}
|
||||
</ControlLabel>
|
||||
);
|
||||
}
|
||||
|
||||
ControlLabelWithTooltip.propTypes = propTypes;
|
||||
ControlLabelWithTooltip.defaultProps = defaultProps;
|
||||
@@ -27,7 +27,6 @@ class ControlPanelsContainer extends React.Component {
|
||||
this.fieldOverrides = this.fieldOverrides.bind(this);
|
||||
this.getFieldData = this.getFieldData.bind(this);
|
||||
this.removeAlert = this.removeAlert.bind(this);
|
||||
this.onChange = this.onChange.bind(this);
|
||||
}
|
||||
componentWillMount() {
|
||||
const datasource_id = this.props.form_data.datasource;
|
||||
@@ -44,14 +43,8 @@ class ControlPanelsContainer extends React.Component {
|
||||
}
|
||||
}
|
||||
}
|
||||
onChange(name, value) {
|
||||
this.props.actions.setFieldValue(this.props.datasource_type, name, value);
|
||||
}
|
||||
getFieldData(fs) {
|
||||
const fieldOverrides = this.fieldOverrides();
|
||||
if (!this.props.fields) {
|
||||
return null;
|
||||
}
|
||||
let fieldData = this.props.fields[fs] || {};
|
||||
if (fieldOverrides.hasOwnProperty(fs)) {
|
||||
const overrideData = fieldOverrides[fs];
|
||||
@@ -100,13 +93,14 @@ class ControlPanelsContainer extends React.Component {
|
||||
{section.fieldSetRows.map((fieldSets, i) => (
|
||||
<FieldSetRow
|
||||
key={`fieldsetrow-${i}`}
|
||||
fields={fieldSets.map(field => (
|
||||
fields={fieldSets.map(fieldName => (
|
||||
<FieldSet
|
||||
name={field}
|
||||
key={`field-${field}`}
|
||||
onChange={this.onChange}
|
||||
value={this.props.form_data[field]}
|
||||
{...this.getFieldData(field)}
|
||||
name={fieldName}
|
||||
key={`field-${fieldName}`}
|
||||
value={this.props.form_data[fieldName]}
|
||||
validationErrors={this.props.fields[fieldName].validationErrors}
|
||||
actions={this.props.actions}
|
||||
{...this.getFieldData(fieldName)}
|
||||
/>
|
||||
))}
|
||||
/>
|
||||
|
||||
@@ -15,6 +15,7 @@ const propTypes = {
|
||||
actions: React.PropTypes.object.isRequired,
|
||||
datasource_type: React.PropTypes.string.isRequired,
|
||||
chartStatus: React.PropTypes.string.isRequired,
|
||||
fields: React.PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
|
||||
@@ -72,6 +73,28 @@ class ExploreViewContainer extends React.Component {
|
||||
toggleModal() {
|
||||
this.setState({ showModal: !this.state.showModal });
|
||||
}
|
||||
renderErrorMessage() {
|
||||
// Returns an error message as a node if any errors are in the store
|
||||
const errors = [];
|
||||
for (const fieldName in this.props.fields) {
|
||||
const field = this.props.fields[fieldName];
|
||||
if (field.validationErrors && field.validationErrors.length > 0) {
|
||||
errors.push(
|
||||
<div key={fieldName}>
|
||||
<strong>{`[ ${field.label} ] `}</strong>
|
||||
{field.validationErrors.join('. ')}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
let errorMessage;
|
||||
if (errors.length > 0) {
|
||||
errorMessage = (
|
||||
<div style={{ textAlign: 'left' }}>{errors}</div>
|
||||
);
|
||||
}
|
||||
return errorMessage;
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
@@ -98,8 +121,9 @@ class ExploreViewContainer extends React.Component {
|
||||
onQuery={this.onQuery.bind(this, this.props.form_data)}
|
||||
onSave={this.toggleModal.bind(this)}
|
||||
disabled={this.props.chartStatus === 'loading'}
|
||||
errorMessage={this.renderErrorMessage()}
|
||||
/>
|
||||
<br /><br />
|
||||
<br />
|
||||
<ControlPanelsContainer
|
||||
actions={this.props.actions}
|
||||
form_data={this.props.form_data}
|
||||
@@ -126,6 +150,7 @@ function mapStateToProps(state) {
|
||||
datasource_type: state.datasource_type,
|
||||
form_data: state.viz.form_data,
|
||||
chartStatus: state.chartStatus,
|
||||
fields: state.fields,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import CheckboxField from './CheckboxField';
|
||||
import TextAreaField from './TextAreaField';
|
||||
import SelectField from './SelectField';
|
||||
|
||||
import ControlLabelWithTooltip from './ControlLabelWithTooltip';
|
||||
import ControlHeader from './ControlHeader';
|
||||
|
||||
const fieldMap = {
|
||||
TextField,
|
||||
@@ -15,14 +15,15 @@ const fieldMap = {
|
||||
const fieldTypes = Object.keys(fieldMap);
|
||||
|
||||
const propTypes = {
|
||||
actions: PropTypes.object.isRequired,
|
||||
name: PropTypes.string.isRequired,
|
||||
type: PropTypes.oneOf(fieldTypes).isRequired,
|
||||
label: PropTypes.string.isRequired,
|
||||
choices: PropTypes.arrayOf(PropTypes.array),
|
||||
description: PropTypes.string,
|
||||
places: PropTypes.number,
|
||||
validators: PropTypes.any,
|
||||
onChange: React.PropTypes.func,
|
||||
validators: PropTypes.array,
|
||||
validationErrors: PropTypes.array,
|
||||
value: PropTypes.oneOfType([
|
||||
PropTypes.string,
|
||||
PropTypes.number,
|
||||
@@ -31,16 +32,46 @@ const propTypes = {
|
||||
};
|
||||
|
||||
const defaultProps = {
|
||||
onChange: () => {},
|
||||
validators: [],
|
||||
validationErrors: [],
|
||||
};
|
||||
|
||||
export default class FieldSet extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.validate = this.validate.bind(this);
|
||||
this.onChange = this.onChange.bind(this);
|
||||
}
|
||||
onChange(value) {
|
||||
const validationErrors = this.validate(value);
|
||||
this.props.actions.setFieldValue(this.props.name, value, validationErrors);
|
||||
}
|
||||
validate(value) {
|
||||
const validators = this.props.validators;
|
||||
const validationErrors = [];
|
||||
if (validators && validators.length > 0) {
|
||||
validators.forEach(f => {
|
||||
const v = f(value);
|
||||
if (v) {
|
||||
validationErrors.push(v);
|
||||
}
|
||||
});
|
||||
}
|
||||
return validationErrors;
|
||||
}
|
||||
render() {
|
||||
const FieldType = fieldMap[this.props.type];
|
||||
return (
|
||||
<div>
|
||||
<ControlLabelWithTooltip label={this.props.label} description={this.props.description} />
|
||||
<FieldType {...this.props} />
|
||||
<ControlHeader
|
||||
label={this.props.label}
|
||||
description={this.props.description}
|
||||
validationErrors={this.props.validationErrors}
|
||||
/>
|
||||
<FieldType
|
||||
onChange={this.onChange}
|
||||
{...this.props}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -33,11 +33,7 @@ export default class SelectField extends React.Component {
|
||||
if (this.props.multi) {
|
||||
optionValue = opt ? opt.map((o) => o.value) : null;
|
||||
}
|
||||
if (this.props.name === 'datasource' && optionValue !== null) {
|
||||
this.props.onChange(this.props.name, optionValue, opt.label);
|
||||
} else {
|
||||
this.props.onChange(this.props.name, optionValue);
|
||||
}
|
||||
this.props.onChange(optionValue);
|
||||
}
|
||||
renderOption(opt) {
|
||||
if (this.props.name === 'viz_type') {
|
||||
|
||||
@@ -18,7 +18,7 @@ const defaultProps = {
|
||||
|
||||
export default class TextAreaField extends React.Component {
|
||||
onChange(event) {
|
||||
this.props.onChange(this.props.name, event.target.value);
|
||||
this.props.onChange(event.target.value);
|
||||
}
|
||||
render() {
|
||||
return (
|
||||
|
||||
@@ -18,16 +18,17 @@ const defaultProps = {
|
||||
|
||||
export default class TextField extends React.Component {
|
||||
onChange(event) {
|
||||
this.props.onChange(this.props.name, event.target.value);
|
||||
this.props.onChange(event.target.value);
|
||||
}
|
||||
render() {
|
||||
const value = this.props.value || '';
|
||||
return (
|
||||
<FormGroup controlId="formInlineName" bsSize="small">
|
||||
<FormControl
|
||||
type="text"
|
||||
placeholder=""
|
||||
onChange={this.onChange.bind(this)}
|
||||
value={this.props.value}
|
||||
value={value}
|
||||
/>
|
||||
</FormGroup>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user