From 1c338ba742108139c3fe5885765633f298734be5 Mon Sep 17 00:00:00 2001 From: vera-liu Date: Tue, 24 Jan 2017 13:32:40 -0800 Subject: [PATCH] [WIP] [explorev2] Refactor filter into FieldSet (#1981) * [explorev2] Refactor filter into FieldSet * Fixed tests * Added tests * Modifications based on comments --- .../explorev2/actions/exploreActions.js | 35 ---- .../components/ControlPanelsContainer.jsx | 24 +-- .../explorev2/components/FieldSet.jsx | 3 +- .../explorev2/components/Filter.jsx | 161 +++++++++--------- .../explorev2/components/FilterField.jsx | 86 ++++++++++ .../explorev2/components/Filters.jsx | 87 ---------- .../javascripts/explorev2/exploreUtils.js | 6 +- .../assets/javascripts/explorev2/index.jsx | 3 +- .../explorev2/reducers/exploreReducer.js | 32 ---- .../javascripts/explorev2/stores/fields.js | 11 ++ .../javascripts/explorev2/stores/store.js | 1 - .../javascripts/explorev2/stores/visTypes.js | 11 +- .../javascripts/explorev2/actions_spec.js | 11 -- .../explorev2/components/FilterField_spec.js | 47 +++++ .../explorev2/components/Filter_spec.js | 33 +++- .../explorev2/components/Filters_spec.js | 40 ----- superset/assets/version_info.json | 1 + superset/models.py | 4 +- superset/viz.py | 2 +- 19 files changed, 274 insertions(+), 324 deletions(-) create mode 100644 superset/assets/javascripts/explorev2/components/FilterField.jsx delete mode 100644 superset/assets/javascripts/explorev2/components/Filters.jsx create mode 100644 superset/assets/spec/javascripts/explorev2/components/FilterField_spec.js delete mode 100644 superset/assets/spec/javascripts/explorev2/components/Filters_spec.js create mode 100644 superset/assets/version_info.json diff --git a/superset/assets/javascripts/explorev2/actions/exploreActions.js b/superset/assets/javascripts/explorev2/actions/exploreActions.js index 84e2e6d179cc5..776014b0ac327 100644 --- a/superset/assets/javascripts/explorev2/actions/exploreActions.js +++ b/superset/assets/javascripts/explorev2/actions/exploreActions.js @@ -78,41 +78,6 @@ export function saveFaveStar(sliceId, isStarred) { }; } -export const ADD_FILTER = 'ADD_FILTER'; -export function addFilter(filter) { - return { type: ADD_FILTER, filter }; -} - -export const REMOVE_FILTER = 'REMOVE_FILTER'; -export function removeFilter(filter) { - return { type: REMOVE_FILTER, filter }; -} - -export const CHANGE_FILTER = 'CHANGE_FILTER'; -export function changeFilter(filter, field, value) { - return { type: CHANGE_FILTER, filter, field, value }; -} - -export function fetchFilterValues(datasource_type, datasource_id, filter, col) { - return function (dispatch) { - $.ajax({ - type: 'GET', - url: `/superset/filter/${datasource_type}/${datasource_id}/${col}/`, - success: (data) => { - dispatch(changeFilter( - filter, - 'choices', - Object.keys(data).map((k) => ([`'${data[k]}'`, `'${data[k]}'`])) - ) - ); - }, - error() { - dispatch(changeFilter(filter, 'choices', [])); - }, - }); - }; -} - export const SET_FIELD_VALUE = 'SET_FIELD_VALUE'; export function setFieldValue(fieldName, value, validationErrors) { return { type: SET_FIELD_VALUE, fieldName, value, validationErrors }; diff --git a/superset/assets/javascripts/explorev2/components/ControlPanelsContainer.jsx b/superset/assets/javascripts/explorev2/components/ControlPanelsContainer.jsx index e522922fa8f13..e5fdec909ae25 100644 --- a/superset/assets/javascripts/explorev2/components/ControlPanelsContainer.jsx +++ b/superset/assets/javascripts/explorev2/components/ControlPanelsContainer.jsx @@ -4,11 +4,10 @@ import { bindActionCreators } from 'redux'; import * as actions from '../actions/exploreActions'; import { connect } from 'react-redux'; import { Panel, Alert } from 'react-bootstrap'; -import visTypes, { sectionsToRender, commonControlPanelSections } from '../stores/visTypes'; +import visTypes, { sectionsToRender } from '../stores/visTypes'; import ControlPanelSection from './ControlPanelSection'; import FieldSetRow from './FieldSetRow'; import FieldSet from './FieldSet'; -import Filters from './Filters'; const propTypes = { datasource_type: PropTypes.string.isRequired, @@ -58,11 +57,6 @@ class ControlPanelsContainer extends React.Component { sectionsToRender() { return sectionsToRender(this.props.form_data.viz_type, this.props.datasource_type); } - filterSectionsToRender() { - const filterSections = this.props.datasource_type === 'table' ? - [commonControlPanelSections.filters[0]] : commonControlPanelSections.filters; - return filterSections; - } fieldOverrides() { const viz = visTypes[this.props.form_data.viz_type]; return viz.fieldOverrides || {}; @@ -100,6 +94,7 @@ class ControlPanelsContainer extends React.Component { value={this.props.form_data[fieldName]} validationErrors={this.props.fields[fieldName].validationErrors} actions={this.props.actions} + prefix={section.prefix} {...this.getFieldData(fieldName)} /> ))} @@ -107,21 +102,6 @@ class ControlPanelsContainer extends React.Component { ))} ))} - {this.filterSectionsToRender().map((section) => ( - - - - ))} ); diff --git a/superset/assets/javascripts/explorev2/components/FieldSet.jsx b/superset/assets/javascripts/explorev2/components/FieldSet.jsx index cf02acfcbb22f..1c67eb9ae800d 100644 --- a/superset/assets/javascripts/explorev2/components/FieldSet.jsx +++ b/superset/assets/javascripts/explorev2/components/FieldSet.jsx @@ -3,7 +3,7 @@ import TextField from './TextField'; import CheckboxField from './CheckboxField'; import TextAreaField from './TextAreaField'; import SelectField from './SelectField'; - +import FilterField from './FilterField'; import ControlHeader from './ControlHeader'; const fieldMap = { @@ -11,6 +11,7 @@ const fieldMap = { CheckboxField, TextAreaField, SelectField, + FilterField, }; const fieldTypes = Object.keys(fieldMap); diff --git a/superset/assets/javascripts/explorev2/components/Filter.jsx b/superset/assets/javascripts/explorev2/components/Filter.jsx index f0ba3182cf877..e0d3a2bf8a4c4 100644 --- a/superset/assets/javascripts/explorev2/components/Filter.jsx +++ b/superset/assets/javascripts/explorev2/components/Filter.jsx @@ -1,124 +1,125 @@ -import React from 'react'; +const $ = window.$ = require('jquery'); +import React, { PropTypes } from 'react'; import Select from 'react-select'; -import { Button } from 'react-bootstrap'; +import { Button, Row, Col } from 'react-bootstrap'; import SelectField from './SelectField'; const propTypes = { - actions: React.PropTypes.object.isRequired, - filterColumnOpts: React.PropTypes.array, - prefix: React.PropTypes.string, - filter: React.PropTypes.object.isRequired, - renderFilterSelect: React.PropTypes.bool, - datasource_type: React.PropTypes.string.isRequired, - datasource_id: React.PropTypes.number.isRequired, + choices: PropTypes.array, + opChoices: PropTypes.array, + changeFilter: PropTypes.func, + removeFilter: PropTypes.func, + filter: PropTypes.object.isRequired, + datasource: PropTypes.object, }; const defaultProps = { - filterColumnOpts: [], - prefix: 'flt', + changeFilter: () => {}, + removeFilter: () => {}, + choices: [], + datasource: null, }; export default class Filter extends React.Component { - constructor(props) { - super(props); - const opChoices = this.props.prefix === 'flt' ? - ['in', 'not in'] : ['==', '!=', '>', '<', '>=', '<=']; - this.state = { - opChoices, - }; - } - componentWillMount() { - if (this.props.filter.col) { - this.props.actions.fetchFilterValues( - this.props.datasource_type, - this.props.datasource_id, - this.props.filter, - this.props.filter.col); + fetchFilterValues(col) { + if (!this.props.datasource) { + return; } - } - changeCol(filter, colOpt) { - const val = (colOpt) ? colOpt.value : null; - this.props.actions.changeFilter(filter, 'col', val); - if (val) { - this.props.actions.fetchFilterValues( - this.props.datasource_type, this.props.datasource_id, filter, val); + const datasource = this.props.datasource; + let choices = []; + if (col) { + $.ajax({ + type: 'GET', + url: `/superset/filter/${datasource.type}/${datasource.id}/${col}/`, + success: (data) => { + choices = Object.keys(data).map((k) => + ([`'${data[k]}'`, `'${data[k]}'`])); + this.props.changeFilter('choices', choices); + }, + }); } } - changeOp(filter, opOpt) { - const val = (opOpt) ? opOpt.value : null; - this.props.actions.changeFilter(filter, 'op', val); - } - changeValue(filter, event) { - this.props.actions.changeFilter(filter, 'value', event.target.value); - } - changeSelectValue(filter, name, value) { - this.props.actions.changeFilter(filter, 'value', value); + changeFilter(field, event) { + let value = event; + if (event && event.target) { + value = event.target.value; + } + if (event && event.value) { + value = event.value; + } + this.props.changeFilter(field, value); + if (field === 'col' && value !== null && this.props.datasource.filter_select) { + this.fetchFilterValues(value); + } } removeFilter(filter) { - this.props.actions.removeFilter(filter); + this.props.removeFilter(filter); } - renderFilterFormField() { - if (this.props.renderFilterSelect) { + renderFilterFormField(filter) { + const datasource = this.props.datasource; + if (datasource && datasource.filter_select) { + if (!filter.choices) { + this.fetchFilterValues(filter.col); + } return ( ); } return ( ); } render() { + const filter = this.props.filter; return (
-
- ({ value: o, label: o }))} - value={this.props.filter.op} - autosize={false} - onChange={this.changeOp.bind(this, this.props.filter)} - /> -
- {this.renderFilterFormField()} -
-
+ + + ({ value: o, label: o }))} + value={filter.op} + onChange={this.changeFilter.bind(this, 'op')} + /> + + + {this.renderFilterFormField(filter)} + + -
-
+ +
); } diff --git a/superset/assets/javascripts/explorev2/components/FilterField.jsx b/superset/assets/javascripts/explorev2/components/FilterField.jsx new file mode 100644 index 0000000000000..112fac84c13d2 --- /dev/null +++ b/superset/assets/javascripts/explorev2/components/FilterField.jsx @@ -0,0 +1,86 @@ +import React, { PropTypes } from 'react'; +import { Button, Row, Col } from 'react-bootstrap'; +import Filter from './Filter'; + +const propTypes = { + prefix: PropTypes.string, + choices: PropTypes.array, + onChange: PropTypes.func, + value: PropTypes.array, + datasource: PropTypes.object, +}; + +const defaultProps = { + prefix: 'flt', + choices: [], + onChange: () => {}, + value: [], +}; + +export default class FilterField extends React.Component { + constructor(props) { + super(props); + this.opChoices = props.prefix === 'flt' ? + ['in', 'not in'] : ['==', '!=', '>', '<', '>=', '<=']; + } + addFilter() { + const newFilters = Object.assign([], this.props.value); + newFilters.push({ + prefix: this.props.prefix, + col: null, + op: 'in', + value: this.props.datasource.filter_select ? [] : '', + }); + this.props.onChange(newFilters); + } + changeFilter(index, field, value) { + const newFilters = Object.assign([], this.props.value); + const modifiedFilter = Object.assign({}, newFilters[index]); + modifiedFilter[field] = value; + newFilters.splice(index, 1, modifiedFilter); + this.props.onChange(newFilters); + } + removeFilter(index) { + this.props.onChange(this.props.value.filter((f, i) => i !== index)); + } + render() { + const filters = []; + this.props.value.forEach((filter, i) => { + // only display filters with current prefix + if (filter.prefix === this.props.prefix) { + const filterBox = ( +
+ +
+ ); + filters.push(filterBox); + } + }); + return ( +
+ {filters} + + + + + +
+ ); + } +} + +FilterField.propTypes = propTypes; +FilterField.defaultProps = defaultProps; diff --git a/superset/assets/javascripts/explorev2/components/Filters.jsx b/superset/assets/javascripts/explorev2/components/Filters.jsx deleted file mode 100644 index be0cd11289e61..0000000000000 --- a/superset/assets/javascripts/explorev2/components/Filters.jsx +++ /dev/null @@ -1,87 +0,0 @@ -import React from 'react'; -// import { Tab, Row, Col, Nav, NavItem } from 'react-bootstrap'; -import Filter from './Filter'; -import { Button } from 'react-bootstrap'; -import { connect } from 'react-redux'; -import shortid from 'shortid'; - -const propTypes = { - actions: React.PropTypes.object.isRequired, - datasource_type: React.PropTypes.string.isRequired, - datasource_id: React.PropTypes.number.isRequired, - filterColumnOpts: React.PropTypes.array, - filters: React.PropTypes.array, - prefix: React.PropTypes.string, - renderFilterSelect: React.PropTypes.bool, -}; - -const defaultProps = { - filterColumnOpts: [], - filters: [], - prefix: 'flt', -}; - -class Filters extends React.Component { - addFilter() { - this.props.actions.addFilter({ - id: shortid.generate(), - prefix: this.props.prefix, - col: null, - op: null, - value: null, - }); - } - render() { - const filters = []; - let i = 0; - this.props.filters.forEach((filter) => { - // only display filters with current prefix - i++; - if (filter.prefix === this.props.prefix) { - filters.push( - - ); - } - }); - return ( -
- {filters} -
-
- -
-
-
- ); - } -} - -Filters.propTypes = propTypes; -Filters.defaultProps = defaultProps; - -function mapStateToProps(state) { - return { - datasource_type: state.datasource_type, - filterColumnOpts: state.filterColumnOpts, - filters: state.viz.form_data.filters, - renderFilterSelect: state.filter_select, - }; -} - -export { Filters }; -export default connect(mapStateToProps, () => ({}))(Filters); diff --git a/superset/assets/javascripts/explorev2/exploreUtils.js b/superset/assets/javascripts/explorev2/exploreUtils.js index 15ac8e3517c37..bd0457a6be0c4 100644 --- a/superset/assets/javascripts/explorev2/exploreUtils.js +++ b/superset/assets/javascripts/explorev2/exploreUtils.js @@ -8,7 +8,11 @@ function formatFilters(filters) { const filter = filters[i]; params[`${filter.prefix}_col_${i + 1}`] = filter.col; params[`${filter.prefix}_op_${i + 1}`] = filter.op; - params[`${filter.prefix}_eq_${i + 1}`] = filter.value; + if (filter.value.constructor === Array) { + params[`${filter.prefix}_eq_${i + 1}`] = filter.value.join(','); + } else { + params[`${filter.prefix}_eq_${i + 1}`] = filter.value; + } } return params; } diff --git a/superset/assets/javascripts/explorev2/index.jsx b/superset/assets/javascripts/explorev2/index.jsx index 8daa189340b1a..dc48dd35b11f0 100644 --- a/superset/assets/javascripts/explorev2/index.jsx +++ b/superset/assets/javascripts/explorev2/index.jsx @@ -25,7 +25,6 @@ const bootstrappedState = Object.assign( initialState(bootstrapData.viz.form_data.viz_type, bootstrapData.datasource_type), { can_edit: bootstrapData.can_edit, can_download: bootstrapData.can_download, - filter_select: bootstrapData.filter_select, datasources: bootstrapData.datasources, datasource_type: bootstrapData.datasource_type, viz: bootstrapData.viz, @@ -40,7 +39,7 @@ bootstrappedState.viz.form_data.datasource_name = bootstrapData.datasource_name; function parseFilters(form_data, prefix = 'flt') { const filters = []; - for (let i = 0; i < 10; i++) { + for (let i = 0; i <= 10; i++) { if (form_data[`${prefix}_col_${i}`] && form_data[`${prefix}_op_${i}`]) { filters.push({ prefix, diff --git a/superset/assets/javascripts/explorev2/reducers/exploreReducer.js b/superset/assets/javascripts/explorev2/reducers/exploreReducer.js index 8be04285821ee..56a6976c574f3 100644 --- a/superset/assets/javascripts/explorev2/reducers/exploreReducer.js +++ b/superset/assets/javascripts/explorev2/reducers/exploreReducer.js @@ -1,7 +1,6 @@ /* eslint camelcase: 0 */ import { defaultFormData } from '../stores/store'; import * as actions from '../actions/exploreActions'; -import { addToArr, removeFromArr, alterInArr } from '../../../utils/reducerUtils'; import { now } from '../../modules/dates'; import { getExploreUrl } from '../exploreUtils'; @@ -41,37 +40,6 @@ export const exploreReducer = function (state, action) { [actions.SET_DATASOURCE]() { return Object.assign({}, state, { datasource: action.datasource }); }, - [actions.SET_FILTER_COLUMN_OPTS]() { - return Object.assign({}, state, { filterColumnOpts: action.filterColumnOpts }); - }, - [actions.ADD_FILTER]() { - const newFormData = addToArr(state.viz.form_data, 'filters', action.filter); - const newState = Object.assign( - {}, - state, - { viz: Object.assign({}, state.viz, { form_data: newFormData }) } - ); - return newState; - }, - [actions.REMOVE_FILTER]() { - const newFormData = removeFromArr(state.viz.form_data, 'filters', action.filter); - return Object.assign( - {}, - state, - { viz: Object.assign({}, state.viz, { form_data: newFormData }) } - ); - }, - [actions.CHANGE_FILTER]() { - const changes = {}; - changes[action.field] = action.value; - const newFormData = alterInArr( - state.viz.form_data, 'filters', action.filter, changes); - return Object.assign( - {}, - state, - { viz: Object.assign({}, state.viz, { form_data: newFormData }) } - ); - }, [actions.SET_FIELD_VALUE]() { let newFormData = Object.assign({}, state.viz.form_data); if (action.fieldName === 'datasource') { diff --git a/superset/assets/javascripts/explorev2/stores/fields.js b/superset/assets/javascripts/explorev2/stores/fields.js index 621f6105dc219..18e8136ce7c80 100644 --- a/superset/assets/javascripts/explorev2/stores/fields.js +++ b/superset/assets/javascripts/explorev2/stores/fields.js @@ -1123,6 +1123,17 @@ export const fields = { default: '', description: 'Labels for the marker lines', }, + + filters: { + type: 'FilterField', + label: '', + default: [], + description: '', + mapStateToProps: (state) => ({ + choices: (state.datasource) ? state.datasource.filterable_cols : [], + datasource: state.datasource, + }), + }, }; export default fields; diff --git a/superset/assets/javascripts/explorev2/stores/store.js b/superset/assets/javascripts/explorev2/stores/store.js index 3f533f9506624..240ec1df880c2 100644 --- a/superset/assets/javascripts/explorev2/stores/store.js +++ b/superset/assets/javascripts/explorev2/stores/store.js @@ -43,7 +43,6 @@ export function initialState(vizType = 'table', datasourceType = 'table') { datasources: null, datasource_type: null, filterColumnOpts: [], - filter_select: false, fields, viz: defaultViz(vizType, datasourceType), isStarred: false, diff --git a/superset/assets/javascripts/explorev2/stores/visTypes.js b/superset/assets/javascripts/explorev2/stores/visTypes.js index 876293c5dd1c6..1aea323b05840 100644 --- a/superset/assets/javascripts/explorev2/stores/visTypes.js +++ b/superset/assets/javascripts/explorev2/stores/visTypes.js @@ -60,11 +60,15 @@ export const commonControlPanelSections = { 'Leave the value field empty to filter empty strings or nulls' + 'For filters with comma in values, wrap them in single quotes' + "as in ", + prefix: 'flt', + fieldSetRows: [['filters']], }, { label: 'Result Filters', description: 'The filters to apply after post-aggregation.' + 'Leave the value field empty to filter empty strings or nulls', + prefix: 'having', + fieldSetRows: [['filters']], }, ], }; @@ -751,12 +755,15 @@ export function sectionsToRender(vizType, datasourceType) { const viz = visTypes[vizType]; const timeSection = datasourceType === 'table' ? commonControlPanelSections.sqlaTimeSeries : commonControlPanelSections.druidTimeSeries; - const { datasourceAndVizType, sqlClause } = commonControlPanelSections; + const { datasourceAndVizType, sqlClause, filters } = commonControlPanelSections; + const filtersToRender = + datasourceType === 'table' ? filters[0] : filters; const sections = [].concat( datasourceAndVizType, timeSection, viz.controlPanelSections, - sqlClause + sqlClause, + filtersToRender ); return sections; } diff --git a/superset/assets/spec/javascripts/explorev2/actions_spec.js b/superset/assets/spec/javascripts/explorev2/actions_spec.js index 4c74fd88ea617..67d91c15b494c 100644 --- a/superset/assets/spec/javascripts/explorev2/actions_spec.js +++ b/superset/assets/spec/javascripts/explorev2/actions_spec.js @@ -15,15 +15,4 @@ describe('reducers', () => { actions.setFieldValue('show_legend', true)); expect(newState.viz.form_data.show_legend).to.equal(true); }); - it('adds a filter given a new filter', () => { - const newState = exploreReducer(initialState('table'), - actions.addFilter({ - id: 1, - prefix: 'flt', - col: null, - op: null, - value: null, - })); - expect(newState.viz.form_data.filters).to.have.length(1); - }); }); diff --git a/superset/assets/spec/javascripts/explorev2/components/FilterField_spec.js b/superset/assets/spec/javascripts/explorev2/components/FilterField_spec.js new file mode 100644 index 0000000000000..a920c0d1fe963 --- /dev/null +++ b/superset/assets/spec/javascripts/explorev2/components/FilterField_spec.js @@ -0,0 +1,47 @@ +/* eslint-disable no-unused-expressions */ +import React from 'react'; +import { Button } from 'react-bootstrap'; +import sinon from 'sinon'; +import { expect } from 'chai'; +import { describe, it, beforeEach } from 'mocha'; +import { shallow } from 'enzyme'; +import FilterField from '../../../../javascripts/explorev2/components/FilterField'; +import Filter from '../../../../javascripts/explorev2/components/Filter'; + +const defaultProps = { + choices: ['country_name'], + prefix: 'flt', + onChange: sinon.spy(), + value: [], + datasource: { + id: 1, + type: 'table', + filter_select: false, + }, +}; + +describe('FilterField', () => { + let wrapper; + + beforeEach(() => { + wrapper = shallow(); + }); + + it('renders Filters', () => { + expect( + React.isValidElement() + ).to.equal(true); + }); + + it('renders one button', () => { + expect(wrapper.find(Filter)).to.have.lengthOf(0); + expect(wrapper.find(Button)).to.have.lengthOf(1); + }); + + it('add a filter when Add Filter button is clicked', () => { + const addButton = wrapper.find('#add-button'); + expect(addButton).to.have.lengthOf(1); + addButton.simulate('click'); + expect(defaultProps.onChange).to.have.property('callCount', 1); + }); +}); diff --git a/superset/assets/spec/javascripts/explorev2/components/Filter_spec.js b/superset/assets/spec/javascripts/explorev2/components/Filter_spec.js index ef8be8c145d85..2151b600490c6 100644 --- a/superset/assets/spec/javascripts/explorev2/components/Filter_spec.js +++ b/superset/assets/spec/javascripts/explorev2/components/Filter_spec.js @@ -2,24 +2,31 @@ import React from 'react'; import Select from 'react-select'; import { Button } from 'react-bootstrap'; +import sinon from 'sinon'; import { expect } from 'chai'; import { describe, it, beforeEach } from 'mocha'; import { shallow } from 'enzyme'; import Filter from '../../../../javascripts/explorev2/components/Filter'; const defaultProps = { - actions: { - fetchFilterValues: () => ({}), + choices: ['country_name'], + opChoices: ['in', 'not in'], + changeFilter: sinon.spy(), + removeFilter: () => { + // noop }, - filterColumnOpts: ['country_name'], filter: { id: 1, prefix: 'flt', - col: 'country_name', - eq: 'in', - value: 'China', + col: null, + op: 'in', + value: '', + }, + datasource: { + id: 1, + type: 'table', + filter_select: false, }, - prefix: 'flt', }; describe('Filter', () => { @@ -35,9 +42,19 @@ describe('Filter', () => { ).to.equal(true); }); - it('renders two select, one button and one input', () => { + it('renders two selects, one button and one input', () => { expect(wrapper.find(Select)).to.have.lengthOf(2); expect(wrapper.find(Button)).to.have.lengthOf(1); expect(wrapper.find('input')).to.have.lengthOf(1); }); + + it('calls changeFilter when select is changed', () => { + const selectCol = wrapper.find('#select-col'); + selectCol.simulate('change', { value: 'col' }); + const selectOp = wrapper.find('#select-op'); + selectOp.simulate('change', { value: 'in' }); + const input = wrapper.find('input'); + input.simulate('change', { target: { value: 'x' } }); + expect(defaultProps.changeFilter).to.have.property('callCount', 3); + }); }); diff --git a/superset/assets/spec/javascripts/explorev2/components/Filters_spec.js b/superset/assets/spec/javascripts/explorev2/components/Filters_spec.js deleted file mode 100644 index 4074bc96e127e..0000000000000 --- a/superset/assets/spec/javascripts/explorev2/components/Filters_spec.js +++ /dev/null @@ -1,40 +0,0 @@ -/* eslint-disable no-unused-expressions */ -import React from 'react'; -import { Button } from 'react-bootstrap'; -import { expect } from 'chai'; -import { describe, it, beforeEach } from 'mocha'; -import { shallow } from 'enzyme'; -import { Filters } from '../../../../javascripts/explorev2/components/Filters'; -import Filter from '../../../../javascripts/explorev2/components/Filter'; - -const defaultProps = { - filterColumnOpts: ['country_name'], - filters: [ - { - id: 1, - prefix: 'flt', - col: 'country_name', - eq: 'in', - value: 'China', - }], - prefix: 'flt', -}; - -describe('Filters', () => { - let wrapper; - - beforeEach(() => { - wrapper = shallow(); - }); - - it('renders Filters', () => { - expect( - React.isValidElement() - ).to.equal(true); - }); - - it('renders one filter', () => { - expect(wrapper.find(Filter)).to.have.lengthOf(1); - expect(wrapper.find(Button)).to.have.lengthOf(1); - }); -}); diff --git a/superset/assets/version_info.json b/superset/assets/version_info.json new file mode 100644 index 0000000000000..cff95f6d46e0f --- /dev/null +++ b/superset/assets/version_info.json @@ -0,0 +1 @@ +{"GIT_SHA": "2d08e240285288b71df98747ddd4b6cca3220c5a", "version": "0.15.2"} \ No newline at end of file diff --git a/superset/models.py b/superset/models.py index ed254a45f214e..39d3ffdbae01c 100644 --- a/superset/models.py +++ b/superset/models.py @@ -680,6 +680,7 @@ def data(self): """data representation of the datasource sent to the frontend""" gb_cols = [(col, col) for col in self.groupby_column_names] all_cols = [(c, c) for c in self.column_names] + filter_cols = [(c, c) for c in self.filterable_column_names] order_by_choices = [] for s in sorted(self.column_names): order_by_choices.append((json.dumps([s, True]), s + ' [asc]')) @@ -693,7 +694,8 @@ def data(self): 'order_by_choices': order_by_choices, 'gb_cols': gb_cols, 'all_cols': all_cols, - 'filterable_cols': self.filterable_column_names, + 'filterable_cols': filter_cols, + 'filter_select': self.filter_select_enabled, } if (self.type == 'table'): grains = self.database.grains() or [] diff --git a/superset/viz.py b/superset/viz.py index 20d0d091e0658..da16be32fd565 100755 --- a/superset/viz.py +++ b/superset/viz.py @@ -178,7 +178,7 @@ def get_filter_url(self): for item in v: ordered_data.add(key, item) href = Href( - '/caravel/filter/{self.datasource.type}/' + '/superset/filter/{self.datasource.type}/' '{self.datasource.id}/'.format(**locals())) return href(ordered_data)