diff --git a/superset/assets/src/visualizations/filter_box.jsx b/superset/assets/src/visualizations/filter_box.jsx
index d059f82611aab..34c8d77b00dad 100644
--- a/superset/assets/src/visualizations/filter_box.jsx
+++ b/superset/assets/src/visualizations/filter_box.jsx
@@ -1,5 +1,3 @@
-// JS
-import d3 from 'd3';
import React from 'react';
import PropTypes from 'prop-types';
import ReactDOM from 'react-dom';
@@ -13,28 +11,40 @@ import Control from '../explore/components/Control';
import controls from '../explore/controls';
import OnPasteSelect from '../components/OnPasteSelect';
import VirtualizedRendererWrap from '../components/VirtualizedRendererWrap';
-import './filter_box.css';
import { t } from '../locales';
+import './filter_box.css';
// maps control names to their key in extra_filters
-const timeFilterMap = {
+const TIME_FILTER_MAP = {
time_range: '__time_range',
granularity_sqla: '__time_col',
time_grain_sqla: '__time_grain',
druid_time_origin: '__time_origin',
granularity: '__granularity',
};
+
+const TIME_RANGE = '__time_range';
+
const propTypes = {
origSelectedValues: PropTypes.object,
+ datasource: PropTypes.object.isRequired,
instantFiltering: PropTypes.bool,
- filtersChoices: PropTypes.object,
+ filtersFields: PropTypes.arrayOf(PropTypes.shape({
+ field: PropTypes.string,
+ label: PropTypes.string,
+ })),
+ filtersChoices: PropTypes.objectOf(PropTypes.arrayOf(PropTypes.shape({
+ id: PropTypes.string,
+ text: PropTypes.string,
+ filter: PropTypes.string,
+ metric: PropTypes.number,
+ }))),
onChange: PropTypes.func,
showDateFilter: PropTypes.bool,
showSqlaTimeGrain: PropTypes.bool,
showSqlaTimeColumn: PropTypes.bool,
showDruidTimeGrain: PropTypes.bool,
showDruidTimeOrigin: PropTypes.bool,
- datasource: PropTypes.object.isRequired,
};
const defaultProps = {
origSelectedValues: {},
@@ -54,22 +64,23 @@ class FilterBox extends React.Component {
selectedValues: props.origSelectedValues,
hasChanged: false,
};
+ this.changeFilter = this.changeFilter.bind(this);
}
+
getControlData(controlName) {
- const control = Object.assign({}, controls[controlName]);
- const controlData = {
+ const { selectedValues } = this.state;
+ const control = Object.assign({}, controls[controlName], {
name: controlName,
key: `control-${controlName}`,
- value: this.state.selectedValues[timeFilterMap[controlName]],
- actions: { setControlValue: this.changeFilter.bind(this) },
- };
- Object.assign(control, controlData);
+ value: selectedValues[TIME_FILTER_MAP[controlName]],
+ actions: { setControlValue: this.changeFilter },
+ });
const mapFunc = control.mapStateToProps;
- if (mapFunc) {
- return Object.assign({}, control, mapFunc(this.props));
- }
- return control;
+ return mapFunc
+ ? Object.assign({}, control, mapFunc(this.props))
+ : control;
}
+
clickApply() {
const { selectedValues } = this.state;
Object.keys(selectedValues).forEach((fltr, i, arr) => {
@@ -81,8 +92,9 @@ class FilterBox extends React.Component {
});
this.setState({ hasChanged: false });
}
+
changeFilter(filter, options) {
- const fltr = timeFilterMap[filter] || filter;
+ const fltr = TIME_FILTER_MAP[filter] || filter;
let vals = null;
if (options !== null) {
if (Array.isArray(options)) {
@@ -100,31 +112,41 @@ class FilterBox extends React.Component {
this.props.onChange(fltr, vals, false, true);
}
}
- render() {
- let dateFilter;
- const timeRange = '__time_range';
- if (this.props.showDateFilter) {
- dateFilter = (
+
+ renderDateFilter() {
+ const { showDateFilter } = this.props;
+ if (showDateFilter) {
+ return (
{ this.changeFilter(TIME_RANGE, ...args); }}
+ value={this.state.selectedValues[TIME_RANGE]}
/>
);
}
+ return null;
+ }
+
+ renderDatasourceFilters() {
+ const {
+ showSqlaTimeGrain,
+ showSqlaTimeColumn,
+ showDruidTimeGrain,
+ showDruidTimeOrigin,
+ } = this.props;
const datasourceFilters = [];
const sqlaFilters = [];
const druidFilters = [];
- if (this.props.showSqlaTimeGrain) sqlaFilters.push('time_grain_sqla');
- if (this.props.showSqlaTimeColumn) sqlaFilters.push('granularity_sqla');
- if (this.props.showDruidTimeGrain) druidFilters.push('granularity');
- if (this.props.showDruidTimeOrigin) druidFilters.push('druid_time_origin');
+ if (showSqlaTimeGrain) sqlaFilters.push('time_grain_sqla');
+ if (showSqlaTimeColumn) sqlaFilters.push('granularity_sqla');
+ if (showDruidTimeGrain) druidFilters.push('granularity');
+ if (showDruidTimeOrigin) druidFilters.push('druid_time_origin');
if (sqlaFilters.length) {
datasourceFilters.push(
,
);
}
+ return datasourceFilters;
+ }
+
+ renderFilters() {
+ const { filtersFields, filtersChoices } = this.props;
+ const { selectedValues } = this.state;
+
// Add created options to filtersChoices, even though it doesn't exist,
// or these options will exist in query sql but invisible to end user.
- for (const filterKey in this.state.selectedValues) {
- if (
- !this.state.selectedValues.hasOwnProperty(filterKey) ||
- !(filterKey in this.props.filtersChoices)
- ) {
- continue;
- }
- const existValues = this.props.filtersChoices[filterKey].map(f => f.id);
- for (const v of this.state.selectedValues[filterKey]) {
- if (existValues.indexOf(v) === -1) {
- const addChoice = {
- filter: filterKey,
- id: v,
- text: v,
- metric: 0,
- };
- this.props.filtersChoices[filterKey].unshift(addChoice);
- }
- }
- }
- const filters = Object.keys(this.props.filtersChoices).map((filter) => {
- const data = this.props.filtersChoices[filter];
- const maxes = {};
- maxes[filter] = d3.max(data, function (d) {
- return d.metric;
+ Object.keys(selectedValues)
+ .filter(key => !selectedValues.hasOwnProperty(key)
+ || !(key in filtersChoices))
+ .forEach((key) => {
+ const choices = filtersChoices[key];
+ const choiceIds = new Set(choices.map(f => f.id));
+ selectedValues[key]
+ .filter(value => !choiceIds.has(value))
+ .forEach((value) => {
+ choices.unshift({
+ filter: key,
+ id: value,
+ text: value,
+ metric: 0,
+ });
+ });
});
+
+ return filtersFields.map(({ key, label }) => {
+ const data = filtersChoices[key];
+ const max = Math.max(...data.map(d => d.metric));
return (
-
- {this.props.datasource.verbose_map[filter] || filter}
+
+ {label}
{
- const perc = Math.round((opt.metric / maxes[opt.filter]) * 100);
+ const perc = Math.round((opt.metric / max) * 100);
const backgroundImage = (
'linear-gradient(to right, lightgrey, ' +
`lightgrey ${perc}%, rgba(0,0,0,0) ${perc}%`
@@ -195,7 +219,7 @@ class FilterBox extends React.Component {
};
return { value: opt.id, label: opt.id, style };
})}
- onChange={this.changeFilter.bind(this, filter)}
+ onChange={(...args) => { this.changeFilter(key, ...args); }}
selectComponent={Creatable}
selectWrap={VirtualizedSelect}
optionRenderer={VirtualizedRendererWrap(opt => opt.label)}
@@ -203,13 +227,18 @@ class FilterBox extends React.Component {
);
});
+ }
+
+ render() {
+ const { instantFiltering } = this.props;
+
return (
- {dateFilter}
- {datasourceFilters}
- {filters}
- {!this.props.instantFiltering &&
+ {this.renderDateFilter()}
+ {this.renderDatasourceFilters()}
+ {this.renderFilters()}
+ {!instantFiltering &&