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 Aug 14, 2020
1 parent 3d64faf commit a3336ce
Show file tree
Hide file tree
Showing 4 changed files with 70 additions and 15 deletions.
53 changes: 45 additions & 8 deletions client/app/components/dynamic-form/DynamicForm.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,31 @@ import helper from "./dynamicFormHelper";

import "./DynamicForm.less";

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 && x.trim()) {
try {
JSON.parse(x);
} catch {
return "";
}
}
},
message: "This field must be a JSON string.",
},
].filter(rule => rule);
};

Expand Down Expand Up @@ -85,6 +101,20 @@ class DynamicForm extends React.Component {
}));
};

parseValues = values => {
this.props.fields.forEach(field => {
if (field.type === "json" && field.name in values) {
try {
values[field.name] = JSON.parse(values[field.name]);
} catch {
// Invalid JSON must be discarded
values[field.name] = null;
}
}
});
return values;
};

handleSubmit = e => {
this.setState({ isSubmitting: true });
e.preventDefault();
Expand All @@ -99,7 +129,7 @@ class DynamicForm extends React.Component {

if (!err) {
this.props.onSubmit(
values,
this.parseValues(values),
msg => {
const { setFieldsValue, getFieldsValue } = this.props.form;
this.setState({ isSubmitting: false });
Expand Down Expand Up @@ -189,6 +219,11 @@ class DynamicForm extends React.Component {
const { getFieldDecorator } = this.props.form;
const { name, type, initialValue } = field;
const fieldLabel = field.title || toHuman(name);
let initialValue = field.initialValue;

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

const options = {
rules: fieldRules(field),
Expand All @@ -207,9 +242,11 @@ class DynamicForm extends React.Component {
} else if (type === "number") {
return getFieldDecorator(name, options)(<InputNumber {...props} />);
} else if (type === "textarea") {
return getFieldDecorator(name, options)(<Input.TextArea {...props} />);
return getFieldDecorator(name, options)(<TextArea {...props} />);
} else if (type === "ace") {
return getFieldDecorator(name, options)(<AceEditorInput {...props} />);
} else if (type === "json") {
return getFieldDecorator(name, options)(<TextArea {...props} />);
}
return getFieldDecorator(name, options)(<Input {...props} />);
}
Expand All @@ -221,12 +258,6 @@ class DynamicForm extends React.Component {
const fieldLabel = title || 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 @@ -237,6 +268,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
16 changes: 11 additions & 5 deletions client/app/components/dynamic-form/dynamicFormHelper.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,16 @@ 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: isNil(properties[key].default) ? null : properties[key].default.toString(),
required: properties[key].required,
extra: properties[key].extra,
title: field.title,
type: field.type,
placeholder: isNil(field.default) ? null : field.default.toString(),
required: field.required,
extra: field.extra,
initialValue: targetOptions[key],
props: field.props,
};

if (input.type === "select") {
Expand Down Expand Up @@ -57,6 +59,10 @@ function normalizeSchema(configurationSchema) {
prop.options = prop.extendedEnum;
}

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

prop.required = includes(configurationSchema.required, name);
prop.extra = includes(configurationSchema.extra_options, 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 @@ -45,11 +45,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
14 changes: 12 additions & 2 deletions redash/query_runner/presto.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ def configuration_schema(cls):
"host": {"type": "string"},
"protocol": {"type": "string", "default": "http"},
"port": {"type": "number"},
"username": {"type": "string"},
"password": {"type": "string"},
"schema": {"type": "string"},
"schema_filter": {
"type": "string",
Expand All @@ -53,8 +55,14 @@ def configuration_schema(cls):
"default": "RegExp to filter schema.table",
},
"catalog": {"type": "string"},
"username": {"type": "string"},
"password": {"type": "string"},
"extras": {
"type": "object",
"default": '{ "requests_kwargs": null }',
"props": {
"rows": 2,
"extra": "Extra kwargs passed to presto.connect(...)",
},
},
},
"order": [
"host",
Expand All @@ -66,6 +74,7 @@ def configuration_schema(cls):
"schema_filter",
"table_filter",
"catalog",
"extra",
],
"required": ["host"],
}
Expand Down Expand Up @@ -126,6 +135,7 @@ def run_query(self, query, user):
password=(self.configuration.get("password") or None),
catalog=self.configuration.get("catalog", "hive"),
schema=self.configuration.get("schema", "default"),
**self.configuration.get("extras", {})
)

cursor = connection.cursor()
Expand Down

0 comments on commit a3336ce

Please sign in to comment.