You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@superset.apache.org by GitBox <gi...@apache.org> on 2019/01/11 23:28:25 UTC

[GitHub] betodealmeida closed pull request #6523: Improving Filter Box

betodealmeida closed pull request #6523: Improving Filter Box
URL: https://github.com/apache/incubator-superset/pull/6523
 
 
   

This is a PR merged from a forked repository.
As GitHub hides the original diff on merge, it is displayed below for
the sake of provenance:

As this is a foreign pull request (from a fork), the diff is supplied
below (as it won't show otherwise due to GitHub magic):

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 0000000000..d30fc71da8
--- /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 0000000000..1fd6bd8d2b
--- /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 5730ff9b0c..20fae477fc 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 0000000000..2365d85ee9
--- /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 0000000000..c43f93897a
--- /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 b545072bb3..bd78f47810 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 0000000000..94c7f0fd09
--- /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 76ebf4ea88..953b3b4764 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 529fbe2ad1..f987eb2a64 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 3beed8a553..f0efeef8c9 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 f5a63f2802..79f109342c 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 f846c40eb3..b09f730359 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 b75a079467..fa66a3830c 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 0000000000..4d12627d0f
--- /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 55a4e8a361..9318cb730b 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
 
 


 

----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on GitHub and use the
URL above to go to the specific comment.
 
For queries about this service, please contact Infrastructure at:
users@infra.apache.org


With regards,
Apache Git Services

---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org