Skip to content

Commit

Permalink
Support for dropdown of predefined options in data sources setup (#4161)
Browse files Browse the repository at this point in the history
* Support for predefined options in data sources

* DynamicForm Select: title -> name

* Make it work with "enum" prop

* Make it work for "extendedEnum" prop

* Not JS

* Deep copy the configuration schema
  • Loading branch information
gabrieldutra authored and arikfr committed Oct 6, 2019
1 parent d8a0af1 commit 569c325
Show file tree
Hide file tree
Showing 5 changed files with 64 additions and 18 deletions.
18 changes: 15 additions & 3 deletions client/app/components/dynamic-form/DynamicForm.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -146,9 +146,21 @@ class DynamicForm extends React.Component {
};

return getFieldDecorator(name, decoratorOptions)(
<Select {...props} optionFilterProp="children" loading={loading || false} mode={mode}>
{options && options.map(({ value, title }) => (
<Option key={`${value}`} value={value} disabled={readOnly}>{ title || value }</Option>
<Select
{...props}
optionFilterProp="children"
loading={loading || false}
mode={mode}
getPopupContainer={trigger => trigger.parentNode}
>
{options && options.map(option => (
<Option
key={`${option.value}`}
value={option.value}
disabled={readOnly}
>
{option.name || option.value}
</Option>
))}
</Select>,
);
Expand Down
45 changes: 34 additions & 11 deletions client/app/components/dynamic-form/dynamicFormHelper.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from 'react';
import { each, includes, isUndefined } from 'lodash';
import { each, includes, isUndefined, isEmpty, map } from 'lodash';

function orderedInputs(properties, order, targetOptions) {
const inputs = new Array(order.length);
Expand All @@ -14,6 +14,11 @@ function orderedInputs(properties, order, targetOptions) {
initialValue: targetOptions[key],
};

if (input.type === 'select') {
input.placeholder = 'Select an option';
input.options = properties[key].options;
}

if (position > -1) {
inputs[position] = input;
} else {
Expand Down Expand Up @@ -41,27 +46,45 @@ function normalizeSchema(configurationSchema) {
prop.type = 'text';
}

if (!isEmpty(prop.enum)) {
prop.type = 'select';
prop.options = map(prop.enum, value => ({ value, name: value }));
}

if (!isEmpty(prop.extendedEnum)) {
prop.type = 'select';
prop.options = prop.extendedEnum;
}

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

configurationSchema.order = configurationSchema.order || [];
}

function setDefaultValueForCheckboxes(configurationSchema, options = {}) {
if (Object.keys(options).length === 0) {
const properties = configurationSchema.properties;
Object.keys(properties).forEach((property) => {
if (!isUndefined(properties[property].default) && properties[property].type === 'checkbox') {
options[property] = properties[property].default;
}
});
}
function setDefaultValueToFields(configurationSchema, options = {}) {
const properties = configurationSchema.properties;
Object.keys(properties).forEach((key) => {
const property = properties[key];
// set default value for checkboxes
if (!isUndefined(property.default) && property.type === 'checkbox') {
options[key] = property.default;
}
// set default or first value when value has predefined options
if (property.type === 'select') {
const optionValues = map(property.options, option => option.value);
options[key] = includes(optionValues, property.default) ? property.default : optionValues[0];
}
});
}

function getFields(type = {}, target = { options: {} }) {
const configurationSchema = type.configuration_schema;
normalizeSchema(configurationSchema);
setDefaultValueForCheckboxes(configurationSchema, target.options);
const hasTargetObject = Object.keys(target.options).length > 0;
if (!hasTargetObject) {
setDefaultValueToFields(configurationSchema, target.options);
}

const isNewTarget = !target.id;
const inputs = [
Expand Down
4 changes: 2 additions & 2 deletions client/app/components/users/UserEdit.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export default class UserEdit extends React.Component {
componentDidMount() {
Group.query((groups) => {
this.setState({
groups: groups.map(({ id, name }) => ({ value: id, title: name })),
groups: groups.map(({ id, name }) => ({ value: id, name })),
loadingGroups: false,
});
});
Expand Down Expand Up @@ -165,7 +165,7 @@ export default class UserEdit extends React.Component {
<div data-test="Groups">
{groups.filter(group => includes(user.groupIds, group.value)).map((group => (
<Tag className="m-b-5 m-r-5" key={group.value}>
<a href={`groups/${group.value}`}>{group.title}</a>
<a href={`groups/${group.value}`}>{group.name}</a>
</Tag>
)))}
</div>
Expand Down
6 changes: 5 additions & 1 deletion redash/query_runner/impala_ds.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,11 @@ def configuration_schema(cls):
},
"protocol": {
"type": "string",
"title": "Please specify beeswax or hiveserver2"
"extendedEnum": [
{"value": "beeswax", "name": "Beeswax"},
{"value": "hiveserver2", "name": "Hive Server 2"}
],
"title": "Protocol"
},
"database": {
"type": "string"
Expand Down
9 changes: 8 additions & 1 deletion redash/utils/configuration.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import jsonschema
import copy
from jsonschema import ValidationError
from sqlalchemy.ext.mutable import Mutable

Expand All @@ -24,7 +25,13 @@ def __init__(self, config, schema=None):
self.set_schema(schema)

def set_schema(self, schema):
self._schema = schema
configuration_schema = copy.deepcopy(schema)
if isinstance(configuration_schema, dict):
for prop in configuration_schema.get('properties', {}).values():
if 'extendedEnum' in prop:
prop['enum'] = map(lambda v: v['value'], prop['extendedEnum'])
del prop['extendedEnum']
self._schema = configuration_schema

@property
def schema(self):
Expand Down

0 comments on commit 569c325

Please sign in to comment.