diff --git a/superset/assets/javascripts/dashboard/Dashboard.jsx b/superset/assets/javascripts/dashboard/Dashboard.jsx index 5d150a20bc4..f1334245d8c 100644 --- a/superset/assets/javascripts/dashboard/Dashboard.jsx +++ b/superset/assets/javascripts/dashboard/Dashboard.jsx @@ -175,7 +175,7 @@ export function dashboardContainer(dashboard, datasources, userid) { const f = []; const immuneSlices = this.metadata.filter_immune_slices || []; if (sliceId && immuneSlices.includes(sliceId)) { - // The slice is immune to dashboard fiterls + // The slice is immune to dashboard filters return f; } @@ -205,8 +205,13 @@ export function dashboardContainer(dashboard, datasources, userid) { return f; }, addFilter(sliceId, col, vals, merge = true, refresh = true) { - if (this.getSlice(sliceId) && (col === '__from' || col === '__to' || - this.getSlice(sliceId).formData.groupby.indexOf(col) !== -1)) { + if ( + this.getSlice(sliceId) && ( + ['__from', '__to', '__time_col', '__time_grain', '__time_origin', '__granularity'] + .indexOf(col) >= 0 || + this.getSlice(sliceId).formData.groupby.indexOf(col) !== -1 + ) + ) { if (!(sliceId in this.filters)) { this.filters[sliceId] = {}; } diff --git a/superset/assets/javascripts/explore/stores/controls.jsx b/superset/assets/javascripts/explore/stores/controls.jsx index 57222a6efcf..cd5d38a1b35 100644 --- a/superset/assets/javascripts/explore/stores/controls.jsx +++ b/superset/assets/javascripts/explore/stores/controls.jsx @@ -562,6 +562,7 @@ export const controls = { mapStateToProps: state => ({ choices: (state.datasource) ? state.datasource.granularity_sqla : [], }), + freeForm: true, }, time_grain_sqla: { @@ -1020,6 +1021,34 @@ export const controls = { description: t('Whether to include a time filter'), }, + show_sqla_time_granularity: { + type: 'CheckboxControl', + label: 'Show SQL Granularity Dropdown', + default: false, + description: 'Check to include SQL Granularity dropdown', + }, + + show_sqla_time_column: { + type: 'CheckboxControl', + label: 'Show SQL Time Column', + default: false, + description: 'Check to include Time Column dropdown', + }, + + show_druid_time_granularity: { + type: 'CheckboxControl', + label: 'Show Druid Granularity Dropdown', + default: false, + description: 'Check to include Druid Granularity dropdown', + }, + + show_druid_time_origin: { + type: 'CheckboxControl', + label: 'Show Druid Time Origin', + default: false, + description: 'Check to include Time Origin dropdown', + }, + show_datatable: { type: 'CheckboxControl', label: t('Data Table'), diff --git a/superset/assets/javascripts/explore/stores/visTypes.js b/superset/assets/javascripts/explore/stores/visTypes.js index 1d4d79b5b47..ca8a63f20bf 100644 --- a/superset/assets/javascripts/explore/stores/visTypes.js +++ b/superset/assets/javascripts/explore/stores/visTypes.js @@ -902,12 +902,9 @@ export const visTypes = { controlSetRows: [ ['groupby'], ['metric'], - ], - }, - { - label: 'Options', - controlSetRows: [ ['date_filter', 'instant_filtering'], + ['show_sqla_time_granularity', 'show_sqla_time_column'], + ['show_druid_time_granularity', 'show_druid_time_origin'], ], }, ], diff --git a/superset/assets/javascripts/modules/superset.js b/superset/assets/javascripts/modules/superset.js index 6570abad606..9d58f02e949 100644 --- a/superset/assets/javascripts/modules/superset.js +++ b/superset/assets/javascripts/modules/superset.js @@ -208,8 +208,7 @@ const px = function (state) { this.force = force; } const formDataExtra = Object.assign({}, formData); - const extraFilters = controller.effectiveExtraFilters(sliceId); - formDataExtra.filters = formDataExtra.filters.concat(extraFilters); + formDataExtra.extra_filters = controller.effectiveExtraFilters(sliceId); controls.find('a.exploreChart').attr('href', getExploreUrl(formDataExtra)); controls.find('a.exportCSV').attr('href', getExploreUrl(formDataExtra, 'csv')); token.find('img.loading').show(); diff --git a/superset/assets/visualizations/filter_box.css b/superset/assets/visualizations/filter_box.css index e1b72f3bd77..b6938e8ee0b 100644 --- a/superset/assets/visualizations/filter_box.css +++ b/superset/assets/visualizations/filter_box.css @@ -7,6 +7,12 @@ padding-top: 0; } +.input-inline { + float: left; + display: inline-block; + padding-right: 3px; +} + ul.select2-results li.select2-highlighted div.filter_box{ color: black; border-width: 1px; diff --git a/superset/assets/visualizations/filter_box.jsx b/superset/assets/visualizations/filter_box.jsx index e465af56cc1..9605830ab2c 100644 --- a/superset/assets/visualizations/filter_box.jsx +++ b/superset/assets/visualizations/filter_box.jsx @@ -6,23 +6,42 @@ import ReactDOM from 'react-dom'; import Select from 'react-select'; import { Button } from 'react-bootstrap'; -import { TIME_CHOICES } from './constants'; +import DateFilterControl from '../javascripts/explore/components/controls/DateFilterControl'; +import ControlRow from '../javascripts/explore/components/ControlRow'; +import Control from '../javascripts/explore/components/Control'; +import controls from '../javascripts/explore/stores/controls'; import './filter_box.css'; import { t } from '../javascripts/locales'; +// maps control names to their key in extra_filters +const timeFilterMap = { + since: '__from', + until: '__to', + granularity_sqla: '__time_col', + time_grain_sqla: '__time_grain', + druid_time_origin: '__time_origin', + granularity: '__granularity', +}; const propTypes = { origSelectedValues: PropTypes.object, instantFiltering: PropTypes.bool, filtersChoices: PropTypes.object, 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: {}, onChange: () => {}, showDateFilter: false, + showSqlaTimeGrain: false, + showSqlaTimeColumn: false, + showDruidTimeGrain: false, + showDruidTimeOrigin: false, instantFiltering: true, }; @@ -34,46 +53,98 @@ class FilterBox extends React.Component { hasChanged: false, }; } + getControlData(controlName) { + const control = Object.assign({}, controls[controlName]); + const controlData = { + name: controlName, + key: `control-${controlName}`, + value: this.state.selectedValues[timeFilterMap[controlName]], + actions: { setControlValue: this.changeFilter.bind(this) }, + }; + Object.assign(control, controlData); + const mapFunc = control.mapStateToProps; + if (mapFunc) { + return Object.assign({}, control, mapFunc(this.props)); + } + return control; + } clickApply() { this.props.onChange(Object.keys(this.state.selectedValues)[0], [], true, true); this.setState({ hasChanged: false }); } changeFilter(filter, options) { + const fltr = timeFilterMap[filter] || filter; let vals = null; - if (options) { + if (options !== null) { if (Array.isArray(options)) { vals = options.map(opt => opt.value); - } else { + } else if (options.value) { vals = options.value; + } else { + vals = options; } } const selectedValues = Object.assign({}, this.state.selectedValues); - selectedValues[filter] = vals; + selectedValues[fltr] = vals; this.setState({ selectedValues, hasChanged: true }); - this.props.onChange(filter, vals, false, this.props.instantFiltering); + this.props.onChange(fltr, vals, false, this.props.instantFiltering); } render() { let dateFilter; + const since = '__from'; + const until = '__to'; if (this.props.showDateFilter) { - dateFilter = ['__from', '__to'].map((field) => { - const val = this.state.selectedValues[field]; - const choices = TIME_CHOICES.slice(); - if (!choices.includes(val)) { - choices.push(val); - } - const options = choices.map(s => ({ value: s, label: s })); - return ( -