[druid] fixing the having clause in the explore view (#2648)

* [druid] fixing the having clause in the explore view

* Backend

* Lint

* Fixing tests
This commit is contained in:
Maxime Beauchemin 2017-04-24 16:31:16 -07:00 committed by GitHub
parent f10ee13901
commit 29780821e8
5 changed files with 37 additions and 20 deletions

View File

@ -46,11 +46,12 @@ export default class DisplayQueryButton extends React.PureComponent {
language: data.language, language: data.language,
query: data.query, query: data.query,
isLoading: false, isLoading: false,
error: null,
}); });
}, },
error: (data) => { error: (data) => {
this.setState({ this.setState({
error: data.error, error: data.responseJSON ? data.responseJSON.error : 'Error...',
isLoading: false, isLoading: false,
}); });
}, },

View File

@ -8,12 +8,12 @@ const $ = window.$ = require('jquery');
const operatorsArr = [ const operatorsArr = [
{ val: 'in', type: 'array', useSelect: true, multi: true }, { val: 'in', type: 'array', useSelect: true, multi: true },
{ val: 'not 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, havingOnly: true },
{ val: '!=', type: 'string', useSelect: true, multi: false }, { val: '!=', type: 'string', useSelect: true, multi: false, havingOnly: true },
{ val: '>=', type: 'string' }, { val: '>=', type: 'string', havingOnly: true },
{ val: '<=', type: 'string' }, { val: '<=', type: 'string', havingOnly: true },
{ val: '>', type: 'string' }, { val: '>', type: 'string', havingOnly: true },
{ val: '<', type: 'string' }, { val: '<', type: 'string', havingOnly: true },
{ val: 'regex', type: 'string', datasourceTypes: ['druid'] }, { val: 'regex', type: 'string', datasourceTypes: ['druid'] },
{ val: 'LIKE', type: 'string', datasourceTypes: ['table'] }, { val: 'LIKE', type: 'string', datasourceTypes: ['table'] },
]; ];
@ -27,12 +27,14 @@ const propTypes = {
removeFilter: PropTypes.func, removeFilter: PropTypes.func,
filter: PropTypes.object.isRequired, filter: PropTypes.object.isRequired,
datasource: PropTypes.object, datasource: PropTypes.object,
having: PropTypes.bool,
}; };
const defaultProps = { const defaultProps = {
changeFilter: () => {}, changeFilter: () => {},
removeFilter: () => {}, removeFilter: () => {},
datasource: null, datasource: null,
having: false,
}; };
export default class Filter extends React.Component { export default class Filter extends React.Component {
@ -47,7 +49,7 @@ export default class Filter extends React.Component {
} }
fetchFilterValues(col) { fetchFilterValues(col) {
const datasource = this.props.datasource; const datasource = this.props.datasource;
if (col && this.props.datasource && this.props.datasource.filter_select) { if (col && this.props.datasource && this.props.datasource.filter_select && !this.props.having) {
this.setState({ valuesLoading: true }); this.setState({ valuesLoading: true });
$.ajax({ $.ajax({
type: 'GET', type: 'GET',
@ -90,7 +92,7 @@ export default class Filter extends React.Component {
} }
renderFilterFormControl(filter) { renderFilterFormControl(filter) {
const operator = operators[filter.op]; const operator = operators[filter.op];
if (operator.useSelect) { if (operator.useSelect && !this.props.having) {
return ( return (
<SelectControl <SelectControl
multi={operator.multi} multi={operator.multi}
@ -117,18 +119,28 @@ export default class Filter extends React.Component {
const datasource = this.props.datasource; const datasource = this.props.datasource;
const filter = this.props.filter; const filter = this.props.filter;
const opsChoices = operatorsArr const opsChoices = operatorsArr
.filter(o => !o.datasourceTypes || o.datasourceTypes.indexOf(datasource.type) >= 0) .filter((o) => {
if (this.props.having) {
return !!o.havingOnly;
}
return (!o.datasourceTypes || o.datasourceTypes.indexOf(datasource.type) >= 0);
})
.map(o => ({ value: o.val, label: o.val })); .map(o => ({ value: o.val, label: o.val }));
const colChoices = datasource ? let colChoices;
datasource.filterable_cols.map(c => ({ value: c[0], label: c[1] })) : if (datasource) {
null; if (this.props.having) {
colChoices = datasource.metrics_combo.map(c => ({ value: c[0], label: c[1] }));
} else {
colChoices = datasource.filterable_cols.map(c => ({ value: c[0], label: c[1] }));
}
}
return ( return (
<div> <div>
<Row className="space-1"> <Row className="space-1">
<Col md={12}> <Col md={12}>
<Select <Select
id="select-col" id="select-col"
placeholder="Select column" placeholder={this.props.having ? 'Select metric' : 'Select column'}
clearable={false} clearable={false}
options={colChoices} options={colChoices}
value={filter.col} value={filter.col}

View File

@ -22,6 +22,10 @@ const defaultProps = {
type: 'qtable', type: 'qtable',
filter_select: false, filter_select: false,
filterable_cols: ['col1', 'col2'], filterable_cols: ['col1', 'col2'],
metrics_combo: [
['m1', 'v1'],
['m2', 'v2'],
],
}, },
}; };
@ -46,7 +50,7 @@ describe('Filter', () => {
}); });
it('renders five op choices for table datasource', () => { it('renders five op choices for table datasource', () => {
const props = defaultProps; const props = Object.assign({}, defaultProps);
props.datasource = { props.datasource = {
id: 1, id: 1,
type: 'druid', type: 'druid',
@ -58,10 +62,10 @@ describe('Filter', () => {
}); });
it('renders six op choices for having filter', () => { it('renders six op choices for having filter', () => {
const props = defaultProps; const props = Object.assign({}, defaultProps);
props.having = true; props.having = true;
const havingWrapper = shallow(<Filter {...props} />); const havingWrapper = shallow(<Filter {...props} />);
expect(havingWrapper.find('#select-op').prop('options')).to.have.lengthOf(9); expect(havingWrapper.find('#select-op').prop('options')).to.have.lengthOf(6);
}); });
it('calls changeFilter when select is changed', () => { it('calls changeFilter when select is changed', () => {
@ -75,7 +79,7 @@ describe('Filter', () => {
}); });
it('renders input for regex filters', () => { it('renders input for regex filters', () => {
const props = defaultProps; const props = Object.assign({}, defaultProps);
props.filter = { props.filter = {
col: null, col: null,
op: 'regex', op: 'regex',

View File

@ -835,7 +835,7 @@ class DruidDatasource(Model, BaseDatasource):
qry['having'] = having_filters qry['having'] = having_filters
orig_filters = filters orig_filters = filters
if len(groupby) == 0: if len(groupby) == 0 and not having_filters:
del qry['dimensions'] del qry['dimensions']
client.timeseries(**qry) client.timeseries(**qry)
if not having_filters and len(groupby) == 1: if not having_filters and len(groupby) == 1:

View File

@ -29,7 +29,7 @@ def get_error_msg():
def json_error_response(msg, status=None, stacktrace=None): def json_error_response(msg, status=None, stacktrace=None):
data = {'error': msg} data = {'error': str(msg)}
if stacktrace: if stacktrace:
data['stacktrace'] = stacktrace data['stacktrace'] = stacktrace
status = status if status else 500 status = status if status else 500