From 7d47740661871bab828f159e6ee87356d0afcb14 Mon Sep 17 00:00:00 2001 From: Krist Wongsuphasawat Date: Thu, 6 Sep 2018 16:13:50 -0700 Subject: [PATCH] [SIP-5] Refactor filterbox (#5789) * reorganize variables * bring back datasource * refactor filter box * rename constants to all caps and update prop types * bind this.changeFilter * update event handler --- .../assets/src/visualizations/filter_box.jsx | 208 +++++++++++------- 1 file changed, 123 insertions(+), 85 deletions(-) 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 &&