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
 
 

Reply via email to