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

Put filters in an array instead of flt strings #2090

Merged
merged 4 commits into from
Feb 2, 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: 1 addition & 0 deletions superset/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ def cast_form_data(form_data):
d[k] = v
return d


app = Flask(__name__)
app.config.from_object(CONFIG_MODULE)
conf = app.config
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,6 @@ class ControlPanelsContainer extends React.Component {
value={this.props.form_data[fieldName]}
validationErrors={this.props.fields[fieldName].validationErrors}
actions={this.props.actions}
prefix={section.prefix}
{...this.getFieldData(fieldName)}
/>
))}
Expand Down
37 changes: 23 additions & 14 deletions superset/assets/javascripts/explorev2/components/Filter.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,27 @@ import SelectField from './SelectField';

const propTypes = {
choices: PropTypes.array,
opChoices: 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: [],
datasource: null,
};

export default class Filter extends React.Component {
constructor(props) {
super(props);
this.opChoices = this.props.having ? ['==', '!=', '>', '<', '>=', '<=']
: ['in', 'not in'];
}
fetchFilterValues(col) {
if (!this.props.datasource) {
return;
Expand Down Expand Up @@ -61,24 +67,27 @@ export default class Filter extends React.Component {
if (!filter.choices) {
this.fetchFilterValues(filter.col);
}
}
if (this.props.having) {
// druid having filter
return (
<SelectField
multi
freeForm
name="filter-value"
<input
type="text"
onChange={this.changeFilter.bind(this, 'val')}
value={filter.value}
choices={filter.choices}
onChange={this.changeFilter.bind(this, 'value')}
className="form-control input-sm"
placeholder="Filter value"
/>
);
}
return (
<input
type="text"
onChange={this.changeFilter.bind(this, 'value')}
value={filter.value}
className="form-control input-sm"
placeholder="Filter value"
<SelectField
multi
freeForm
name="filter-value"
value={filter.val}
choices={filter.choices || []}
onChange={this.changeFilter.bind(this, 'val')}
/>
);
}
Expand All @@ -102,7 +111,7 @@ export default class Filter extends React.Component {
<Select
id="select-op"
placeholder="Select operator"
options={this.props.opChoices.map((o) => ({ value: o, label: o }))}
options={this.opChoices.map((o) => ({ value: o, label: o }))}
value={filter.op}
onChange={this.changeFilter.bind(this, 'op')}
/>
Expand Down
40 changes: 15 additions & 25 deletions superset/assets/javascripts/explorev2/components/FilterField.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,33 +3,26 @@ import { Button, Row, Col } from 'react-bootstrap';
import Filter from './Filter';

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

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

export default class FilterField extends React.Component {
constructor(props) {
super(props);
this.opChoices = props.prefix === 'flt' ?
['in', 'not in'] : ['==', '!=', '>', '<', '>=', '<='];
}
addFilter() {
const newFilters = Object.assign([], this.props.value);
newFilters.push({
prefix: this.props.prefix,
col: null,
op: 'in',
value: this.props.datasource.filter_select ? [] : '',
val: this.props.datasource.filter_select ? [] : '',
});
this.props.onChange(newFilters);
}
Expand All @@ -46,22 +39,19 @@ export default class FilterField extends React.Component {
render() {
const filters = [];
this.props.value.forEach((filter, i) => {
// only display filters with current prefix
if (filter.prefix === this.props.prefix) {
const filterBox = (
<div key={i}>
<Filter
filter={filter}
choices={this.props.choices}
opChoices={this.opChoices}
datasource={this.props.datasource}
removeFilter={this.removeFilter.bind(this, i)}
changeFilter={this.changeFilter.bind(this, i)}
/>
</div>
);
filters.push(filterBox);
}
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);
});
return (
<div>
Expand Down
47 changes: 1 addition & 46 deletions superset/assets/javascripts/explorev2/exploreUtils.js
Original file line number Diff line number Diff line change
@@ -1,61 +1,16 @@
/* eslint camelcase: 0 */
function formatFilters(filters) {
// outputs an object of url params of filters
// prefix can be 'flt' or 'having'
const params = {};
for (let i = 0; i < filters.length; i++) {
const filter = filters[i];
params[`${filter.prefix}_col_${i + 1}`] = filter.col;
params[`${filter.prefix}_op_${i + 1}`] = filter.op;
if (filter.value.constructor === Array) {
params[`${filter.prefix}_eq_${i + 1}`] = filter.value.join(',');
} else {
params[`${filter.prefix}_eq_${i + 1}`] = filter.value;
}
}
return params;
}

export function parseFilters(form_data, prefix = 'flt') {
const filters = [];
for (let i = 0; i <= 10; i++) {
if (form_data[`${prefix}_col_${i}`] && form_data[`${prefix}_op_${i}`]) {
filters.push({
prefix,
col: form_data[`${prefix}_col_${i}`],
op: form_data[`${prefix}_op_${i}`],
value: form_data[`${prefix}_eq_${i}`],
});
}
/* eslint no-param-reassign: 0 */
delete form_data[`${prefix}_col_${i}`];
delete form_data[`${prefix}_op_${i}`];
delete form_data[`${prefix}_eq_${i}`];
}
return filters;
}

export function getFilters(form_data, datasource_type) {
if (datasource_type === 'table') {
return parseFilters(form_data);
}
return parseFilters(form_data).concat(parseFilters(form_data, 'having'));
}

export function getParamObject(form_data, datasource_type, saveNewSlice) {
const data = {
datasource_id: form_data.datasource,
datasource_type,
};
Object.keys(form_data).forEach((field) => {
// filter out null fields
if (form_data[field] !== null && field !== 'datasource' && field !== 'filters'
if (form_data[field] !== null && field !== 'datasource'
&& !(saveNewSlice && field === 'slice_name')) {
data[field] = form_data[field];
}
});
const filterParams = formatFilters(form_data.filters);
Object.assign(data, filterParams);
return data;
}

Expand Down
12 changes: 12 additions & 0 deletions superset/assets/javascripts/explorev2/stores/fields.js
Original file line number Diff line number Diff line change
Expand Up @@ -1148,6 +1148,18 @@ export const fields = {
datasource: state.datasource,
}),
},

having_filters: {
type: 'FilterField',
label: '',
default: [],
description: '',
mapStateToProps: (state) => ({
choices: (state.datasource) ? state.datasource.metrics_combo
.concat(state.datasource.filterable_cols) : [],
datasource: state.datasource,
}),

slice_id: {
type: 'HiddenField',
label: 'Slice ID',
Expand Down
5 changes: 2 additions & 3 deletions superset/assets/javascripts/explorev2/stores/visTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,15 +61,13 @@ export const commonControlPanelSections = {
'Leave the value field empty to filter empty strings or nulls' +
'For filters with comma in values, wrap them in single quotes' +
"as in <NY, 'Tahoe, CA', DC>",
prefix: 'flt',
fieldSetRows: [['filters']],
},
{
label: 'Result Filters',
description: 'The filters to apply after post-aggregation.' +
'Leave the value field empty to filter empty strings or nulls',
prefix: 'having',
fieldSetRows: [['filters']],
fieldSetRows: [['having_filters']],
},
],
};
Expand Down Expand Up @@ -434,6 +432,7 @@ const visTypes = {
},

big_number_total: {
label: 'Big Number',
controlPanelSections: [
{
label: null,
Expand Down
83 changes: 83 additions & 0 deletions superset/migrations/versions/53fc3de270ae_.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
"""update filters for slice model

Revision ID: 53fc3de270ae
Revises: db0c65b146bd
Create Date: 2017-01-31 10:18:49.071296

"""

# revision identifiers, used by Alembic.
revision = '53fc3de270ae'
down_revision = 'db0c65b146bd'

from alembic import op
from superset import db
import json
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String, Text
import re

Base = declarative_base()


def cast_filter_data(form_data):
flts = []
having_flts = []
fd = form_data
filter_pattern = re.compile(r'''((?:[^,"']|"[^"]*"|'[^']*')+)''')
for i in range(0, 10):
for prefix in ['flt', 'having']:
col_str = '{}_col_{}'.format(prefix, i)
op_str = '{}_op_{}'.format(prefix, i)
val_str = '{}_eq_{}'.format(prefix, i)
if col_str in fd and op_str in fd and val_str in fd \
and len(fd[val_str]) > 0:
f = {}
f['col'] = fd[col_str]
f['op'] = fd[op_str]
if prefix == 'flt':
# transfer old strings in filter value to list
splitted = filter_pattern.split(fd[val_str])[1::2]
values = [types.replace("'", '').strip() for types in splitted]
f['val'] = values
flts.append(f)
if prefix == 'having':
f['val'] = fd[val_str]
having_flts.append(f)
if col_str in fd:
del fd[col_str]
if op_str in fd:
del fd[op_str]
if val_str in fd:
del fd[val_str]
fd['filters'] = flts
fd['having_filters'] = having_flts
return fd


class Slice(Base):
"""Declarative class to do query in upgrade"""
__tablename__ = 'slices'
id = Column(Integer, primary_key=True)
datasource_id = Column(Integer)
druid_datasource_id = Column(Integer)
table_id = Column(Integer)
datasource_type = Column(String(200))
params = Column(Text)


def upgrade():
bind = op.get_bind()
session = db.Session(bind=bind)

for slc in session.query(Slice).all():
d = json.loads(slc.params)
d = cast_filter_data(d)
slc.params = json.dumps(d)
session.merge(slc)
session.commit()
session.close()


def downgrade():
pass
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
"""update_slice_model_json
Revision ID: db0c65b146bd
Revises: 1296d28ec131
Revises: f18570e03440
Create Date: 2017-01-24 12:31:06.541746
"""

# revision identifiers, used by Alembic.
revision = 'db0c65b146bd'
down_revision = '1296d28ec131'
down_revision = 'f18570e03440'

from alembic import op
from superset import db, cast_form_data
Expand Down
Loading