Skip to content
This repository has been archived by the owner on Jul 9, 2022. It is now read-only.

Commit

Permalink
Add an extras field for Presto getredash#3909
Browse files Browse the repository at this point in the history
This makes it possible to allow arbitrary connection parameters
that are not stored as configuration field.

Also extended Dynamic Forms to allow JSON validation.
  • Loading branch information
ktmud committed Jun 20, 2019
1 parent 27259b5 commit 912db34
Show file tree
Hide file tree
Showing 5 changed files with 66 additions and 16 deletions.
50 changes: 41 additions & 9 deletions client/app/components/dynamic-form/DynamicForm.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,37 @@ import Checkbox from 'antd/lib/checkbox';
import Button from 'antd/lib/button';
import Upload from 'antd/lib/upload';
import Icon from 'antd/lib/icon';
import { includes, isFunction } from 'lodash';
import { includes, isFunction, isPlainObject, isArray } from 'lodash';
import Select from 'antd/lib/select';
import notification from '@/services/notification';
import { Field, Action, AntdForm } from '../proptypes';
import helper from './dynamicFormHelper';

const { TextArea } = Input;

const fieldRules = ({ type, required, minLength }) => {
const requiredRule = required;
const minLengthRule = minLength && includes(['text', 'email', 'password'], type);
const emailTypeRule = type === 'email';
const jsonRule = type === 'json';

return [
requiredRule && { required, message: 'This field is required.' },
minLengthRule && { min: minLength, message: 'This field is too short.' },
emailTypeRule && { type: 'email', message: 'This field must be a valid email.' },
jsonRule && {
type: 'object',
transform(x) {
if (x.trim()) {
try {
JSON.parse(x);
} catch {
return '';
}
}
},
message: 'This field must be a JSON string.',
},
].filter(rule => rule);
};

Expand Down Expand Up @@ -74,13 +90,22 @@ class DynamicForm extends React.Component {
}));
};

parseValues = (values) => {
this.props.fields.forEach((field) => {
if (field.type === 'json' && field.name in values) {
values[field.name] = JSON.parse(values[field.name]);
}
});
return values;
};

handleSubmit = (e) => {
this.setState({ isSubmitting: true });
e.preventDefault();
this.props.form.validateFieldsAndScroll((err, values) => {
if (!err) {
this.props.onSubmit(
values,
this.parseValues(values),
(msg) => {
const { setFieldsValue, getFieldsValue } = this.props.form;
this.setState({ isSubmitting: false });
Expand Down Expand Up @@ -155,8 +180,13 @@ class DynamicForm extends React.Component {

renderField(field, props) {
const { getFieldDecorator } = this.props.form;
const { name, type, initialValue } = field;
const { name, type } = field;
const fieldLabel = field.title || helper.toHuman(name);
let initialValue = field.initialValue;

if (isPlainObject(initialValue) || isArray(initialValue)) {
initialValue = JSON.stringify(field.initialValue);
}

const options = {
rules: fieldRules(field),
Expand All @@ -174,6 +204,8 @@ class DynamicForm extends React.Component {
return field.content;
} else if (type === 'number') {
return getFieldDecorator(name, options)(<InputNumber {...props} />);
} else if (type === 'json') {
return getFieldDecorator(name, options)(<TextArea {...props} />);
}
return getFieldDecorator(name, options)(<Input {...props} />);
}
Expand All @@ -185,12 +217,6 @@ class DynamicForm extends React.Component {
const fieldLabel = title || helper.toHuman(name);
const { feedbackIcons, form } = this.props;

const formItemProps = {
className: 'm-b-10',
hasFeedback: type !== 'checkbox' && type !== 'file' && feedbackIcons,
label: type === 'checkbox' ? '' : fieldLabel,
};

const fieldProps = {
...field.props,
className: 'w-100',
Expand All @@ -201,6 +227,12 @@ class DynamicForm extends React.Component {
placeholder: field.placeholder,
'data-test': fieldLabel,
};
const formItemProps = {
className: 'm-b-10',
hasFeedback: type !== 'checkbox' && type !== 'file' && feedbackIcons,
label: type === 'checkbox' ? '' : fieldLabel,
extra: fieldProps.extra,
};

return (
<React.Fragment key={name}>
Expand Down
14 changes: 10 additions & 4 deletions client/app/components/dynamic-form/dynamicFormHelper.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@ function orderedInputs(properties, order, targetOptions) {
const inputs = new Array(order.length);
Object.keys(properties).forEach((key) => {
const position = order.indexOf(key);
const field = properties[key];
const input = {
name: key,
title: properties[key].title,
type: properties[key].type,
placeholder: properties[key].default && properties[key].default.toString(),
required: properties[key].required,
title: field.title,
type: field.type,
placeholder: field.default && field.default.toString(),
required: field.required,
initialValue: targetOptions[key],
props: field.props,
};

if (position > -1) {
Expand Down Expand Up @@ -41,6 +43,10 @@ function normalizeSchema(configurationSchema) {
prop.type = 'text';
}

if (prop.type === 'object') {
prop.type = 'json';
}

prop.required = includes(configurationSchema.required, name);
});

Expand Down
2 changes: 2 additions & 0 deletions client/app/components/proptypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,13 @@ export const Field = PropTypes.shape({
'file',
'select',
'content',
'json',
]).isRequired,
initialValue: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number,
PropTypes.bool,
PropTypes.object,
PropTypes.arrayOf(PropTypes.string),
PropTypes.arrayOf(PropTypes.number),
]),
Expand Down
2 changes: 1 addition & 1 deletion redash/handlers/data_sources.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ def post(self, data_source_id):
try:
data_source.options.set_schema(schema)
data_source.options.update(filter_none(req['options']))
except ValidationError:
except ValidationError, e:
abort(400)

data_source.type = req['type']
Expand Down
14 changes: 12 additions & 2 deletions redash/query_runner/presto.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,17 @@ def configuration_schema(cls):
'password': {
'type': 'string'
},
'extras': {
'type': 'object',
'default': '{ "requests_kwargs": null }',
'props': {
'rows': 2,
'extra': 'Extra kwargs passed to presto.connect(...)',
}
}
},
'order': ['host', 'protocol', 'port', 'username', 'password', 'schema', 'catalog'],
'order': ['host', 'protocol', 'port', 'username', 'password',
'schema', 'catalog', 'extras'],
'required': ['host']
}

Expand Down Expand Up @@ -105,7 +114,8 @@ def run_query(self, query, user):
username=self.configuration.get('username', 'redash'),
password=(self.configuration.get('password') or None),
catalog=self.configuration.get('catalog', 'hive'),
schema=self.configuration.get('schema', 'default'))
schema=self.configuration.get('schema', 'default'),
**self.configuration.get('extras', {}))

cursor = connection.cursor()

Expand Down

0 comments on commit 912db34

Please sign in to comment.