Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[explore] improved filters #2330

Merged
merged 4 commits into from
Mar 21, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 0 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,6 @@ def get_git_sha():
'sqlparse==0.1.19',
'thrift>=0.9.3',
'thrift-sasl>=0.2.1',
'werkzeug==0.11.15',
],
extras_require={
'cors': ['Flask-Cors>=2.0.0'],
Expand Down
149 changes: 76 additions & 73 deletions superset/assets/javascripts/explorev2/components/controls/Filter.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,32 @@ import Select from 'react-select';
import { Button, Row, Col } from 'react-bootstrap';
import SelectControl from './SelectControl';

const arrayFilterOps = ['in', 'not in'];
const strFilterOps = ['==', '!=', '>', '<', '>=', '<=', 'regex'];
const operatorsArr = [
{ val: 'in', type: 'array', useSelect: true, multi: true },
{ val: 'not in', type: 'array', useSelect: true, multi: true },
{ val: '==', type: 'string', useSelect: true, multi: false },
{ val: '!=', type: 'string', useSelect: true, multi: false },
{ val: '>=', type: 'string' },
{ val: '<=', type: 'string' },
{ val: '>', type: 'string' },
{ val: '<', type: 'string' },
{ val: 'regex', type: 'string', datasourceTypes: ['druid'] },
{ val: 'LIKE', type: 'string', datasourceTypes: ['table'] },
];
const operators = {};
operatorsArr.forEach(op => {
operators[op.val] = op;
});

const propTypes = {
choices: PropTypes.array,
changeFilter: PropTypes.func,
removeFilter: PropTypes.func,
filter: PropTypes.object.isRequired,
datasource: PropTypes.object,
having: PropTypes.bool,
};

const defaultProps = {
having: false,
changeFilter: () => {},
removeFilter: () => {},
choices: [],
Expand All @@ -27,112 +39,102 @@ const defaultProps = {
export default class Filter extends React.Component {
constructor(props) {
super(props);
const filterOps = props.datasource.type === 'table' ?
['in', 'not in'] : ['==', '!=', 'in', 'not in', 'regex'];
this.opChoices = this.props.having ? ['==', '!=', '>', '<', '>=', '<=']
: filterOps;
this.state = {
valuesLoading: false,
};
}
componentDidMount() {
this.fetchFilterValues(this.props.filter.col);
}
fetchFilterValues(col) {
if (!this.props.datasource) {
return;
}
const datasource = this.props.datasource;
let choices = [];
if (col) {
if (col && this.props.datasource && this.props.datasource.filter_select) {
this.setState({ valuesLoading: true });
$.ajax({
type: 'GET',
url: `/superset/filter/${datasource.type}/${datasource.id}/${col}/`,
success: (data) => {
choices = Object.keys(data).map((k) =>
([`'${data[k]}'`, `'${data[k]}'`]));
this.props.changeFilter('choices', choices);
this.props.changeFilter('choices', data);
this.setState({ valuesLoading: false });
},
});
}
}
switchFilterValue(prevFilter, nextOp) {
const prevOp = prevFilter.op;
let newVal = null;
if (arrayFilterOps.indexOf(prevOp) !== -1
&& strFilterOps.indexOf(nextOp) !== -1) {
switchFilterValue(prevOp, nextOp) {
if (operators[prevOp].type !== operators[nextOp].type) {
const val = this.props.filter.value;
let newVal;
// switch from array to string
newVal = this.props.filter.val.length > 0 ? this.props.filter.val[0] : '';
}
if (strFilterOps.indexOf(prevOp) !== -1
&& arrayFilterOps.indexOf(nextOp) !== -1) {
// switch from string to array
newVal = this.props.filter.val === '' ? [] : [this.props.filter.val];
}
return newVal;
}
changeFilter(control, event) {
let value = event;
if (event && event.target) {
value = event.target.value;
}
if (event && event.value) {
value = event.value;
}
if (control === 'op') {
const newVal = this.switchFilterValue(this.props.filter, value);
if (newVal) {
this.props.changeFilter(['op', 'val'], [value, newVal]);
} else {
this.props.changeFilter(control, value);
if (operators[nextOp].type === 'string' && val && val.length > 0) {
newVal = val[0];
} else if (operators[nextOp].type === 'string' && val) {
newVal = [val];
}
} else {
this.props.changeFilter(control, value);
}
if (control === 'col' && value !== null && this.props.datasource.filter_select) {
this.fetchFilterValues(value);
this.props.changeFilter('val', newVal);
}
}
changeText(event) {
this.props.changeFilter('val', event.target.value);
}
changeSelect(value) {
this.props.changeFilter('val', value);
}
changeColumn(event) {
this.props.changeFilter('col', event.value);
this.fetchFilterValues(event.value);
}
changeOp(event) {
this.switchFilterValue(this.props.filter.op, event.value);
this.props.changeFilter('op', event.value);
}
removeFilter(filter) {
this.props.removeFilter(filter);
}
renderFilterFormControl(filter) {
const datasource = this.props.datasource;
if (datasource && datasource.filter_select) {
if (!filter.choices) {
this.fetchFilterValues(filter.col);
}
}
// switching filter value between array/string when needed
if (strFilterOps.indexOf(filter.op) !== -1) {
// druid having filter or regex/==/!= filters
const operator = operators[filter.op];
if (operator.useSelect) {
return (
<input
type="text"
onChange={this.changeFilter.bind(this, 'val')}
<SelectControl
multi={operator.multi}
freeForm
name="filter-value"
value={filter.val}
className="form-control input-sm"
placeholder="Filter value"
isLoading={this.state.valuesLoading}
choices={filter.choices}
onChange={this.changeSelect.bind(this)}
/>
);
}
return (
<SelectControl
multi
freeForm
name="filter-value"
<input
type="text"
onChange={this.changeText.bind(this)}
value={filter.val}
choices={filter.choices || []}
onChange={this.changeFilter.bind(this, 'val')}
className="form-control input-sm"
placeholder="Filter value"
/>
);
}
render() {
const datasource = this.props.datasource;
const filter = this.props.filter;
const opsChoices = operatorsArr
.filter(o => !o.datasourceTypes || o.datasourceTypes.indexOf(datasource.type) >= 0)
.map(o => ({ value: o.val, label: o.val }));
const colChoices = datasource ?
datasource.filterable_cols.map(c => ({ value: c[0], label: c[1] })) :
null;
return (
<div>
<Row className="space-1">
<Col md={12}>
<Select
id="select-col"
placeholder="Select column"
options={this.props.choices.map((c) => ({ value: c[0], label: c[1] }))}
clearable={false}
options={colChoices}
value={filter.col}
onChange={this.changeFilter.bind(this, 'col')}
onChange={this.changeColumn.bind(this)}
/>
</Col>
</Row>
Expand All @@ -141,9 +143,10 @@ export default class Filter extends React.Component {
<Select
id="select-op"
placeholder="Select operator"
options={this.opChoices.map((o) => ({ value: o, label: o }))}
options={opsChoices}
clearable={false}
value={filter.op}
onChange={this.changeFilter.bind(this, 'op')}
onChange={this.changeOp.bind(this)}
/>
</Col>
<Col md={7}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,24 @@ import Filter from './Filter';

const propTypes = {
name: PropTypes.string,
choices: PropTypes.array,
onChange: PropTypes.func,
value: PropTypes.array,
datasource: PropTypes.object,
};

const defaultProps = {
choices: [],
onChange: () => {},
value: [],
};

export default class FilterControl extends React.Component {
addFilter() {
const newFilters = Object.assign([], this.props.value);
const col = this.props.datasource && this.props.datasource.filterable_cols.length > 0 ?
this.props.datasource.filterable_cols[0][0] :
null;
newFilters.push({
col: null,
col,
op: 'in',
val: this.props.datasource.filter_select ? [] : '',
});
Expand All @@ -43,22 +44,17 @@ export default class FilterControl extends React.Component {
this.props.onChange(this.props.value.filter((f, i) => i !== index));
}
render() {
const filters = [];
this.props.value.forEach((filter, i) => {
const filterBox = (
<div key={i}>
<Filter
having={this.props.name === 'having_filters'}
filter={filter}
choices={this.props.choices}
datasource={this.props.datasource}
removeFilter={this.removeFilter.bind(this, i)}
changeFilter={this.changeFilter.bind(this, i)}
/>
</div>
);
filters.push(filterBox);
});
const filters = this.props.value.map((filter, i) => (
<div key={i}>
<Filter
having={this.props.name === 'having_filters'}
filter={filter}
datasource={this.props.datasource}
removeFilter={this.removeFilter.bind(this, i)}
changeFilter={this.changeFilter.bind(this, i)}
/>
</div>
));
return (
<div>
{filters}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,30 +47,39 @@ export default class SelectControl extends React.PureComponent {
this.props.onChange(optionValue);
}
getOptions(props) {
const options = props.choices.map((c) => {
const label = c.length > 1 ? c[1] : c[0];
const newOptions = {
value: c[0],
label,
};
if (c[2]) newOptions.imgSrc = c[2];
return newOptions;
// Accepts different formats of input
const options = props.choices.map(c => {
let option;
if (Array.isArray(c)) {
const label = c.length > 1 ? c[1] : c[0];
option = {
value: c[0],
label,
};
if (c[2]) option.imgSrc = c[2];
} else if (Object.is(c)) {
option = c;
} else {
option = {
value: c,
label: c,
};
}
return option;
});
if (props.freeForm) {
// For FreeFormSelect, insert value into options if not exist
const values = props.choices.map((c) => c[0]);
const values = options.map(c => c.value);
if (props.value) {
if (typeof props.value === 'object') {
props.value.forEach((v) => {
if (values.indexOf(v) === -1) {
options.push({ value: v, label: v });
}
});
} else {
if (values.indexOf(props.value) === -1) {
options.push({ value: props.value, label: props.value });
}
let valuesToAdd = props.value;
if (!Array.isArray(valuesToAdd)) {
valuesToAdd = [valuesToAdd];
}
valuesToAdd.forEach(v => {
if (values.indexOf(v) < 0) {
options.push({ value: v, label: v });
}
});
}
}
return options;
Expand Down
1 change: 0 additions & 1 deletion superset/assets/javascripts/explorev2/stores/controls.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -1177,7 +1177,6 @@ export const controls = {
default: [],
description: '',
mapStateToProps: (state) => ({
choices: (state.datasource) ? state.datasource.filterable_cols : [],
datasource: state.datasource,
}),
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ const defaultProps = {
id: 1,
type: 'table',
filter_select: false,
filterable_cols: ['country_name'],
},
};

Expand Down
Loading