This is an automated email from the ASF dual-hosted git repository.
beto pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-superset.git
The following commit(s) were added to refs/heads/master by this push:
new 7e0e7c8 Improving Filter Box (#6523)
7e0e7c8 is described below
commit 7e0e7c89ba8c9384d0db4cf945e0a978910c3b18
Author: Maxime Beauchemin <[email protected]>
AuthorDate: Fri Jan 11 15:28:23 2019 -0800
Improving Filter Box (#6523)
* Improve Filter Box
* layout tweaks
---
.../spec/javascripts/components/FormRow_spec.jsx | 41 +++++
.../components/FilterBoxItemControl_spec.jsx | 37 +++++
superset/assets/src/chart/ChartRenderer.jsx | 2 +-
superset/assets/src/components/FormRow.jsx | 47 ++++++
.../components/controls/CollectionControl.css | 3 +
.../components/controls/CollectionControl.jsx | 4 +-
.../components/controls/FilterBoxItemControl.jsx | 179 +++++++++++++++++++++
.../src/explore/components/controls/index.js | 2 +
.../controlPanels/{FilterBox.js => FilterBox.jsx} | 20 ++-
superset/assets/src/explore/controls.jsx | 9 ++
.../src/visualizations/FilterBox/FilterBox.jsx | 75 +++++----
.../src/visualizations/FilterBox/transformProps.js | 9 +-
superset/data/world_bank.py | 18 ++-
.../versions/fb13d49b72f9_better_filters.py | 84 ++++++++++
superset/viz.py | 53 +++---
15 files changed, 515 insertions(+), 68 deletions(-)
diff --git a/superset/assets/spec/javascripts/components/FormRow_spec.jsx
b/superset/assets/spec/javascripts/components/FormRow_spec.jsx
new file mode 100644
index 0000000..d30fc71
--- /dev/null
+++ b/superset/assets/spec/javascripts/components/FormRow_spec.jsx
@@ -0,0 +1,41 @@
+import React from 'react';
+import { shallow } from 'enzyme';
+
+import { Col, Row } from 'react-bootstrap';
+import TextControl from '../../../src/explore/components/controls/TextControl';
+import InfoTooltipWithTrigger from
'../../../src/components/InfoTooltipWithTrigger';
+import FormRow from '../../../src/components/FormRow';
+
+const defaultProps = {
+ label: 'Hello',
+ tooltip: 'A tooltip',
+ control: <TextControl label="test_cbox" />,
+};
+
+describe('FormRow', () => {
+ let wrapper;
+
+ const getWrapper = (overrideProps = {}) => {
+ const props = {
+ ...defaultProps,
+ ...overrideProps,
+ };
+ return shallow(<FormRow {...props} />);
+ };
+
+ beforeEach(() => {
+ wrapper = getWrapper();
+ });
+
+ it('renders an InfoTooltipWithTrigger only if needed', () => {
+ expect(wrapper.find(InfoTooltipWithTrigger)).toHaveLength(1);
+ wrapper = getWrapper({ tooltip: null });
+ expect(wrapper.find(InfoTooltipWithTrigger)).toHaveLength(0);
+ });
+
+ it('renders a Row and 2 Cols', () => {
+ expect(wrapper.find(Row)).toHaveLength(1);
+ expect(wrapper.find(Col)).toHaveLength(2);
+ });
+
+});
diff --git
a/superset/assets/spec/javascripts/explore/components/FilterBoxItemControl_spec.jsx
b/superset/assets/spec/javascripts/explore/components/FilterBoxItemControl_spec.jsx
new file mode 100644
index 0000000..1fd6bd8
--- /dev/null
+++
b/superset/assets/spec/javascripts/explore/components/FilterBoxItemControl_spec.jsx
@@ -0,0 +1,37 @@
+/* eslint-disable no-unused-expressions */
+import React from 'react';
+import sinon from 'sinon';
+import { shallow } from 'enzyme';
+import { OverlayTrigger } from 'react-bootstrap';
+
+import FilterBoxItemControl from
'../../../../src/explore/components/controls/FilterBoxItemControl';
+import FormRow from '../../../../src/components/FormRow';
+import datasources from '../../../fixtures/mockDatasource';
+
+const defaultProps = {
+ datasource: datasources['7__table'],
+ onChange: sinon.spy(),
+};
+
+describe('FilterBoxItemControl', () => {
+ let wrapper;
+ let inst;
+
+ const getWrapper = (propOverrides) => {
+ const props = { ...defaultProps, ...propOverrides };
+ return shallow(<FilterBoxItemControl {...props} />);
+ };
+ beforeEach(() => {
+ wrapper = getWrapper();
+ inst = wrapper.instance();
+ });
+
+ it('renders an OverlayTrigger', () => {
+ expect(wrapper.find(OverlayTrigger)).toHaveLength(1);
+ });
+
+ it('renderForms does the job', () => {
+ const popover = shallow(inst.renderForm());
+ expect(popover.find(FormRow)).toHaveLength(7);
+ });
+});
diff --git a/superset/assets/src/chart/ChartRenderer.jsx
b/superset/assets/src/chart/ChartRenderer.jsx
index 5730ff9..20fae47 100644
--- a/superset/assets/src/chart/ChartRenderer.jsx
+++ b/superset/assets/src/chart/ChartRenderer.jsx
@@ -38,7 +38,7 @@ const defaultProps = {
triggerRender: false,
};
-class ChartRenderer extends React.PureComponent {
+class ChartRenderer extends React.Component {
constructor(props) {
super(props);
this.state = {};
diff --git a/superset/assets/src/components/FormRow.jsx
b/superset/assets/src/components/FormRow.jsx
new file mode 100644
index 0000000..2365d85
--- /dev/null
+++ b/superset/assets/src/components/FormRow.jsx
@@ -0,0 +1,47 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { Row, Col } from 'react-bootstrap';
+
+import InfoTooltipWithTrigger from './InfoTooltipWithTrigger';
+
+const STYLE_ROW = { marginTop: '5px', minHeight: '30px' };
+const STYLE_RALIGN = { textAlign: 'right' };
+
+const propTypes = {
+ label: PropTypes.string.isRequired,
+ tooltip: PropTypes.string,
+ control: PropTypes.node.isRequired,
+ isCheckbox: PropTypes.bool,
+};
+
+const defaultProps = {
+ tooltip: null,
+ isCheckbox: false,
+};
+
+export default function FormRow({ label, tooltip, control, isCheckbox }) {
+ const labelAndTooltip = (
+ <span>
+ {label}{' '}
+ {tooltip &&
+ <InfoTooltipWithTrigger
+ placement="top"
+ label={label}
+ tooltip={tooltip}
+ />}
+ </span>);
+ if (isCheckbox) {
+ return (
+ <Row style={STYLE_ROW}>
+ <Col md={4} style={STYLE_RALIGN}>{control}</Col>
+ <Col md={8}>{labelAndTooltip}</Col>
+ </Row>);
+ }
+ return (
+ <Row style={STYLE_ROW}>
+ <Col md={4} style={STYLE_RALIGN}>{labelAndTooltip}</Col>
+ <Col md={8}>{control}</Col>
+ </Row>);
+}
+FormRow.propTypes = propTypes;
+FormRow.defaultProps = defaultProps;
diff --git
a/superset/assets/src/explore/components/controls/CollectionControl.css
b/superset/assets/src/explore/components/controls/CollectionControl.css
new file mode 100644
index 0000000..c43f938
--- /dev/null
+++ b/superset/assets/src/explore/components/controls/CollectionControl.css
@@ -0,0 +1,3 @@
+.CollectionControl .list-group-item i.fa {
+ padding-top: 5px;
+}
diff --git
a/superset/assets/src/explore/components/controls/CollectionControl.jsx
b/superset/assets/src/explore/components/controls/CollectionControl.jsx
index b545072..bd78f47 100644
--- a/superset/assets/src/explore/components/controls/CollectionControl.jsx
+++ b/superset/assets/src/explore/components/controls/CollectionControl.jsx
@@ -9,6 +9,7 @@ import {
import InfoTooltipWithTrigger from
'../../../components/InfoTooltipWithTrigger';
import ControlHeader from '../ControlHeader';
import controlMap from './';
+import './CollectionControl.css';
const propTypes = {
name: PropTypes.string.isRequired,
@@ -82,6 +83,7 @@ export default class CollectionControl extends
React.Component {
</div>
<div className="pull-left">
<Control
+ {...this.props}
{...o}
onChange={this.onChange.bind(this, i)}
/>
@@ -101,7 +103,7 @@ export default class CollectionControl extends
React.Component {
}
render() {
return (
- <div>
+ <div className="CollectionControl">
<ControlHeader {...this.props} />
{this.renderList()}
<InfoTooltipWithTrigger
diff --git
a/superset/assets/src/explore/components/controls/FilterBoxItemControl.jsx
b/superset/assets/src/explore/components/controls/FilterBoxItemControl.jsx
new file mode 100644
index 0000000..94c7f0f
--- /dev/null
+++ b/superset/assets/src/explore/components/controls/FilterBoxItemControl.jsx
@@ -0,0 +1,179 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { OverlayTrigger, Popover } from 'react-bootstrap';
+import { t } from '@superset-ui/translation';
+
+import InfoTooltipWithTrigger from
'../../../components/InfoTooltipWithTrigger';
+import FormRow from '../../../components/FormRow';
+import SelectControl from './SelectControl';
+import CheckboxControl from './CheckboxControl';
+import TextControl from './TextControl';
+
+const propTypes = {
+ datasource: PropTypes.object.isRequired,
+ onChange: PropTypes.func,
+ asc: PropTypes.bool,
+ clearable: PropTypes.bool,
+ multiple: PropTypes.bool,
+ column: PropTypes.string,
+ metric: PropTypes.string,
+ defaultValue: PropTypes.string,
+};
+
+const defaultProps = {
+ onChange: () => {},
+ asc: true,
+ clearable: true,
+ multiple: true,
+};
+
+const STYLE_WIDTH = { width: 350 };
+
+export default class FilterBoxItemControl extends React.Component {
+ constructor(props) {
+ super(props);
+ const { column, metric, asc, clearable, multiple, defaultValue } = props;
+ const state = { column, metric, asc, clearable, multiple, defaultValue };
+ this.state = state;
+ this.onChange = this.onChange.bind(this);
+ this.onControlChange = this.onControlChange.bind(this);
+ }
+ onChange() {
+ this.props.onChange(this.state);
+ }
+ onControlChange(attr, value) {
+ this.setState({ [attr]: value }, this.onChange);
+ }
+ setType() {
+ }
+ textSummary() {
+ return this.state.column || 'N/A';
+ }
+ renderForm() {
+ return (
+ <div>
+ <FormRow
+ label={t('Column')}
+ control={
+ <SelectControl
+ value={this.state.column}
+ name="column"
+ clearable={false}
+ options={this.props.datasource.columns.map(col => ({
+ value: col.column_name,
+ label: col.column_name,
+ }))}
+ onChange={v => this.onControlChange('column', v)}
+ />
+ }
+ />
+ <FormRow
+ label={t('Label')}
+ control={
+ <TextControl
+ value={this.state.label}
+ name="label"
+ onChange={v => this.onControlChange('label', v)}
+ />
+ }
+ />
+ <FormRow
+ label={t('Default')}
+ tooltip={t(
+ '(optional) default value for the filter, when using ' +
+ 'the multiple option, you can use a semicolon-delimited list ' +
+ 'of options.')}
+ control={
+ <TextControl
+ value={this.state.defaultValue}
+ name="defaultValue"
+ onChange={v => this.onControlChange('defaultValue', v)}
+ />
+ }
+ />
+ <FormRow
+ label={t('Sort Metric')}
+ tooltip={t('Metric to sort the results by')}
+ control={
+ <SelectControl
+ value={this.state.metric}
+ name="column"
+ options={this.props.datasource.metrics.map(m => ({
+ value: m.metric_name,
+ label: m.metric_name,
+ }))}
+ onChange={v => this.onControlChange('metric', v)}
+ />
+ }
+ />
+ <FormRow
+ label={t('Sort Ascending')}
+ tooltip={t('Check for sorting ascending')}
+ isCheckbox
+ control={
+ <CheckboxControl
+ value={this.state.asc}
+ onChange={v => this.onControlChange('asc', v)}
+ />
+ }
+ />
+ <FormRow
+ label={t('Allow Multiple Selections')}
+ isCheckbox
+ tooltip={t(
+ 'Multiple selections allowed, otherwise filter ' +
+ 'is limited to a single value')}
+ control={
+ <CheckboxControl
+ value={this.state.multiple}
+ onChange={v => this.onControlChange('multiple', v)}
+ />
+ }
+ />
+ <FormRow
+ label={t('Required')}
+ tooltip={t('User must select a value for this filter')}
+ isCheckbox
+ control={
+ <CheckboxControl
+ value={!this.state.clearable}
+ onChange={v => this.onControlChange('clearable', !v)}
+ />
+ }
+ />
+ </div>);
+ }
+ renderPopover() {
+ return (
+ <Popover id="ts-col-popo" title={t('Filter Configuration')}>
+ <div style={STYLE_WIDTH}>
+ {this.renderForm()}
+ </div>
+ </Popover>
+ );
+ }
+ render() {
+ return (
+ <span>
+ {this.textSummary()}{' '}
+ <OverlayTrigger
+ container={document.body}
+ trigger="click"
+ rootClose
+ ref="trigger"
+ placement="right"
+ overlay={this.renderPopover()}
+ >
+ <InfoTooltipWithTrigger
+ icon="edit"
+ className="text-primary"
+ label="edit-ts-column"
+ />
+ </OverlayTrigger>
+ </span>
+ );
+ }
+}
+
+FilterBoxItemControl.propTypes = propTypes;
+FilterBoxItemControl.defaultProps = defaultProps;
diff --git a/superset/assets/src/explore/components/controls/index.js
b/superset/assets/src/explore/components/controls/index.js
index 76ebf4e..953b3b4 100644
--- a/superset/assets/src/explore/components/controls/index.js
+++ b/superset/assets/src/explore/components/controls/index.js
@@ -20,6 +20,7 @@ import VizTypeControl from './VizTypeControl';
import MetricsControl from './MetricsControl';
import AdhocFilterControl from './AdhocFilterControl';
import FilterPanel from './FilterPanel';
+import FilterBoxItemControl from './FilterBoxItemControl';
const controlMap = {
AnnotationLayerControl,
@@ -44,5 +45,6 @@ const controlMap = {
MetricsControl,
AdhocFilterControl,
FilterPanel,
+ FilterBoxItemControl,
};
export default controlMap;
diff --git a/superset/assets/src/explore/controlPanels/FilterBox.js
b/superset/assets/src/explore/controlPanels/FilterBox.jsx
similarity index 52%
rename from superset/assets/src/explore/controlPanels/FilterBox.js
rename to superset/assets/src/explore/controlPanels/FilterBox.jsx
index 529fbe2..f987eb2 100644
--- a/superset/assets/src/explore/controlPanels/FilterBox.js
+++ b/superset/assets/src/explore/controlPanels/FilterBox.jsx
@@ -1,29 +1,27 @@
+import React from 'react';
import { t } from '@superset-ui/translation';
export default {
controlPanelSections: [
{
- label: t('Query'),
+ label: t('Filters Configuration'),
expanded: true,
controlSetRows: [
- ['groupby'],
- ['metric'],
- ['adhoc_filters'],
+ ['filter_configs'],
+ [<hr />],
['date_filter', 'instant_filtering'],
['show_sqla_time_granularity', 'show_sqla_time_column'],
['show_druid_time_granularity', 'show_druid_time_origin'],
+ ['adhoc_filters'],
],
},
],
controlOverrides: {
- groupby: {
- label: t('Filter controls'),
+ adhoc_filters: {
+ label: t('Global Filters'),
description: t(
- 'The controls you want to filter on. Note that only columns ' +
- 'checked as "filterable" will show up on this list.'),
- mapStateToProps: state => ({
- options: (state.datasource) ? state.datasource.columns.filter(c =>
c.filterable) : [],
- }),
+ 'These filters, like the time filters, will be applied ' +
+ 'to each individual filters as the values are populated.'),
},
},
};
diff --git a/superset/assets/src/explore/controls.jsx
b/superset/assets/src/explore/controls.jsx
index 3beed8a..f0efeef 100644
--- a/superset/assets/src/explore/controls.jsx
+++ b/superset/assets/src/explore/controls.jsx
@@ -2289,6 +2289,15 @@ export const controls = {
default: true,
},
+ filter_configs: {
+ type: 'CollectionControl',
+ label: 'Filters',
+ description: t('Filter configuration for the filter box'),
+ validators: [v.nonEmpty],
+ controlName: 'FilterBoxItemControl',
+ mapStateToProps: ({ datasource }) => ({ datasource }),
+ },
+
normalized: {
type: 'CheckboxControl',
label: t('Normalized'),
diff --git a/superset/assets/src/visualizations/FilterBox/FilterBox.jsx
b/superset/assets/src/visualizations/FilterBox/FilterBox.jsx
index f5a63f2..79f1093 100644
--- a/superset/assets/src/visualizations/FilterBox/FilterBox.jsx
+++ b/superset/assets/src/visualizations/FilterBox/FilterBox.jsx
@@ -170,9 +170,8 @@ class FilterBox extends React.Component {
}
return datasourceFilters;
}
-
- renderFilters() {
- const { filtersFields, filtersChoices } = this.props;
+ renderSelect(filterConfig) {
+ const { filtersChoices } = this.props;
const { selectedValues } = this.state;
// Add created options to filtersChoices, even though it doesn't exist,
@@ -196,35 +195,55 @@ class FilterBox extends React.Component {
});
});
});
+ const { key, label } = filterConfig;
+ const data = this.props.filtersChoices[key];
+ const max = Math.max(...data.map(d => d.metric));
+ let value = selectedValues[key] || null;
+
+ // Assign default value if required
+ if (!value && filterConfig.defaultValue) {
+ if (filterConfig.multiple) {
+ // Support for semicolon-delimited multiple values
+ value = filterConfig.defaultValue.split(';');
+ } else {
+ value = filterConfig.defaultValue;
+ }
+ }
+ return (
+ <OnPasteSelect
+ placeholder={t('Select [%s]', label)}
+ key={key}
+ multi={filterConfig.multiple}
+ clearable={filterConfig.clearable}
+ value={value}
+ options={data.map((opt) => {
+ const perc = Math.round((opt.metric / max) * 100);
+ const backgroundImage = (
+ 'linear-gradient(to right, lightgrey, ' +
+ `lightgrey ${perc}%, rgba(0,0,0,0) ${perc}%`
+ );
+ const style = {
+ backgroundImage,
+ padding: '2px 5px',
+ };
+ return { value: opt.id, label: opt.id, style };
+ })}
+ onChange={(...args) => { this.changeFilter(key, ...args); }}
+ selectComponent={Creatable}
+ selectWrap={VirtualizedSelect}
+ optionRenderer={VirtualizedRendererWrap(opt => opt.label)}
+ />);
+ }
+
+ renderFilters() {
- return filtersFields.map(({ key, label }) => {
- const data = filtersChoices[key];
- const max = Math.max(...data.map(d => d.metric));
+ const { filtersFields } = this.props;
+ return filtersFields.map((filterConfig) => {
+ const { label, key } = filterConfig;
return (
<div key={key} className="m-b-5">
{label}
- <OnPasteSelect
- placeholder={t('Select [%s]', label)}
- key={key}
- multi
- value={selectedValues[key]}
- options={data.map((opt) => {
- const perc = Math.round((opt.metric / max) * 100);
- const backgroundImage = (
- 'linear-gradient(to right, lightgrey, ' +
- `lightgrey ${perc}%, rgba(0,0,0,0) ${perc}%`
- );
- const style = {
- backgroundImage,
- padding: '2px 5px',
- };
- return { value: opt.id, label: opt.id, style };
- })}
- onChange={(...args) => { this.changeFilter(key, ...args); }}
- selectComponent={Creatable}
- selectWrap={VirtualizedSelect}
- optionRenderer={VirtualizedRendererWrap(opt => opt.label)}
- />
+ {this.renderSelect(filterConfig)}
</div>
);
});
diff --git a/superset/assets/src/visualizations/FilterBox/transformProps.js
b/superset/assets/src/visualizations/FilterBox/transformProps.js
index f846c40..b09f730 100644
--- a/superset/assets/src/visualizations/FilterBox/transformProps.js
+++ b/superset/assets/src/visualizations/FilterBox/transformProps.js
@@ -9,7 +9,7 @@ export default function transformProps(chartProps) {
} = chartProps;
const {
dateFilter,
- groupby,
+ filterConfigs,
instantFiltering,
showDruidTimeGranularity,
showDruidTimeOrigin,
@@ -18,9 +18,10 @@ export default function transformProps(chartProps) {
} = formData;
const { verboseMap } = datasource;
- const filtersFields = groupby.map(key => ({
- key,
- label: verboseMap[key] || key,
+ const filtersFields = filterConfigs.map(flt => ({
+ ...flt,
+ key: flt.column,
+ label: flt.label || verboseMap[flt.column] || flt.column,
}));
return {
diff --git a/superset/data/world_bank.py b/superset/data/world_bank.py
index b75a079..fa66a38 100644
--- a/superset/data/world_bank.py
+++ b/superset/data/world_bank.py
@@ -86,7 +86,23 @@ def load_world_bank_health_n_pop():
defaults,
viz_type='filter_box',
date_filter=False,
- groupby=['region', 'country_name'])),
+ filter_configs=[
+ {
+ 'asc': False,
+ 'clearable': True,
+ 'column': 'region',
+ 'key': '2s98dfu',
+ 'metric': 'sum__SP_POP_TOTL',
+ 'multiple': True,
+ }, {
+ 'asc': False,
+ 'clearable': True,
+ 'key': 'li3j2lk',
+ 'column': 'country_name',
+ 'metric': 'sum__SP_POP_TOTL',
+ 'multiple': True,
+ },
+ ])),
Slice(
slice_name="World's Population",
viz_type='big_number',
diff --git a/superset/migrations/versions/fb13d49b72f9_better_filters.py
b/superset/migrations/versions/fb13d49b72f9_better_filters.py
new file mode 100644
index 0000000..4d12627
--- /dev/null
+++ b/superset/migrations/versions/fb13d49b72f9_better_filters.py
@@ -0,0 +1,84 @@
+"""better_filters
+
+Revision ID: fb13d49b72f9
+Revises: 6c7537a6004a
+Create Date: 2018-12-11 22:03:21.612516
+
+"""
+import json
+import logging
+
+from alembic import op
+from sqlalchemy import Column, Integer, String, Text
+from sqlalchemy.ext.declarative import declarative_base
+
+from superset import db
+
+# revision identifiers, used by Alembic.
+revision = 'fb13d49b72f9'
+down_revision = 'de021a1ca60d'
+
+Base = declarative_base()
+
+
+class Slice(Base):
+ __tablename__ = 'slices'
+
+ id = Column(Integer, primary_key=True)
+ params = Column(Text)
+ viz_type = Column(String(250))
+ slice_name = Column(String(250))
+
+
+def upgrade():
+ bind = op.get_bind()
+ session = db.Session(bind=bind)
+
+ filter_box_slices = session.query(Slice).filter_by(viz_type='filter_box')
+ for slc in filter_box_slices.all():
+ try:
+ params = json.loads(slc.params)
+ logging.info(f'Upgrading {slc.slice_name}')
+ cols = params.get('groupby')
+ metrics = params.get('metrics')
+ if cols:
+ flts = [{
+ 'column': col,
+ 'metric': metrics[0] if metrics else None,
+ 'asc': False,
+ 'clearable': True,
+ 'multiple': True,
+ } for col in cols]
+ params['filter_configs'] = flts
+ if 'groupby' in params:
+ del params['groupby']
+ if 'metrics' in params:
+ del params['metrics']
+ slc.params = json.dumps(params, sort_keys=True)
+ except Exception as e:
+ logging.exception(e)
+
+ session.commit()
+ session.close()
+
+
+def downgrade():
+ bind = op.get_bind()
+ session = db.Session(bind=bind)
+
+ filter_box_slices = session.query(Slice).filter_by(viz_type='filter_box')
+ for slc in filter_box_slices.all():
+ try:
+ params = json.loads(slc.params)
+ logging.info(f'Downgrading {slc.slice_name}')
+ flts = params.get('filter_configs')
+ if not flts:
+ continue
+ params['metrics'] = [flts[0].get('metric')]
+ params['groupby'] = [o.get('column') for o in flts]
+ slc.params = json.dumps(params, sort_keys=True)
+ except Exception as e:
+ logging.exception(e)
+
+ session.commit()
+ session.close()
diff --git a/superset/viz.py b/superset/viz.py
index 55a4e8a..9318cb7 100644
--- a/superset/viz.py
+++ b/superset/viz.py
@@ -1785,40 +1785,49 @@ class FilterBoxViz(BaseViz):
is_timeseries = False
credits = 'a <a href="https://github.com/airbnb/superset">Superset</a>
original'
cache_type = 'get_data'
+ filter_row_limit = 1000
def query_obj(self):
return None
def run_extra_queries(self):
- qry = self.filter_query_obj()
- filters = [g for g in self.form_data['groupby']]
+ qry = super(FilterBoxViz, self).query_obj()
+ filters = self.form_data.get('filter_configs') or []
+ qry['row_limit'] = self.filter_row_limit
self.dataframes = {}
for flt in filters:
- qry['groupby'] = [flt]
+ col = flt.get('column')
+ if not col:
+ raise Exception(_(
+ 'Invalid filter configuration, please select a column'))
+ qry['groupby'] = [col]
+ metric = flt.get('metric')
+ qry['metrics'] = [metric] if metric else []
df = self.get_df_payload(query_obj=qry).get('df')
- self.dataframes[flt] = df
-
- def filter_query_obj(self):
- qry = super(FilterBoxViz, self).query_obj()
- groupby = self.form_data.get('groupby')
- if len(groupby) < 1 and not self.form_data.get('date_filter'):
- raise Exception(_('Pick at least one filter field'))
- qry['metrics'] = [
- self.form_data['metric']]
- return qry
+ self.dataframes[col] = df
def get_data(self, df):
+ filters = self.form_data.get('filter_configs') or []
d = {}
- filters = [g for g in self.form_data['groupby']]
for flt in filters:
- df = self.dataframes[flt]
- d[flt] = [{
- 'id': row[0],
- 'text': row[0],
- 'filter': flt,
- 'metric': row[1]}
- for row in df.itertuples(index=False)
- ]
+ col = flt.get('column')
+ metric = flt.get('metric')
+ df = self.dataframes.get(col)
+ if metric:
+ df = df.sort_values(metric, ascending=flt.get('asc'))
+ d[col] = [{
+ 'id': row[0],
+ 'text': row[0],
+ 'metric': row[1]}
+ for row in df.itertuples(index=False)
+ ]
+ else:
+ df = df.sort_values(col, ascending=flt.get('asc'))
+ d[col] = [{
+ 'id': row[0],
+ 'text': row[0]}
+ for row in df.itertuples(index=False)
+ ]
return d