Added filter in ControlPanelsContainer for explore V2 (#1647)

* Added filter in ControlPanelsContainer

* Move function for getting url params object to utils

* Fixed python test

* Move Filter to separate component

* Added specs and made changes based on comments

* Moved specs to right folder
This commit is contained in:
vera-liu
2016-11-23 09:51:19 -08:00
committed by GitHub
parent cef4a8296a
commit 39ce4aa049
15 changed files with 354 additions and 133 deletions

View File

@@ -4,9 +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 } from '../stores/store';
import { visTypes, sectionsToRender, commonControlPanelSections } from '../stores/store';
import ControlPanelSection from './ControlPanelSection';
import FieldSetRow from './FieldSetRow';
import Filters from './Filters';
const propTypes = {
datasource_type: PropTypes.string.isRequired,
@@ -44,6 +45,12 @@ class ControlPanelsContainer extends React.Component {
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;
@@ -86,7 +93,20 @@ class ControlPanelsContainer extends React.Component {
))}
</ControlPanelSection>
))}
{/* TODO: add filters section */}
{this.filterSectionsToRender().map((section) => (
<ControlPanelSection
key={section.label}
label={section.label}
tooltip={section.description}
>
<Filters
filterColumnOpts={[]}
filters={this.props.form_data.filters}
actions={this.props.actions}
prefix={section.prefix}
/>
</ControlPanelSection>
))}
</div>
</div>
}

View File

@@ -8,6 +8,8 @@ import ControlPanelsContainer from './ControlPanelsContainer';
import SaveModal from './SaveModal';
import QueryAndSaveBtns from '../../explore/components/QueryAndSaveBtns';
import { autoQueryFields } from '../stores/store';
import { getParamObject } from '../../modules/utils.js';
const $ = require('jquery');
const propTypes = {
@@ -47,18 +49,7 @@ class ExploreViewContainer extends React.Component {
}
onQuery(form_data) {
const data = {};
Object.keys(form_data).forEach((field) => {
// filter out null fields
if (form_data[field] !== null && field !== 'datasource') {
data[field] = form_data[field];
}
});
// V2 tag temporarily for updating url
// Todo: remove after launch
data.V2 = true;
data.datasource_id = this.props.form_data.datasource;
data.datasource_type = this.props.datasource_type;
const data = getParamObject(form_data, this.props.datasource_type);
this.queryFormData(data);
const params = $.param(data, true);

View File

@@ -0,0 +1,92 @@
import React from 'react';
// import { Tab, Row, Col, Nav, NavItem } from 'react-bootstrap';
import Select from 'react-select';
import { Button } from 'react-bootstrap';
const propTypes = {
actions: React.PropTypes.object.isRequired,
filterColumnOpts: React.PropTypes.array,
prefix: React.PropTypes.string,
filter: React.PropTypes.object.isRequired,
};
const defaultProps = {
filterColumnOpts: [],
prefix: 'flt',
};
export default class Filter extends React.Component {
constructor(props) {
super(props);
const opChoices = this.props.prefix === 'flt' ?
['in', 'not in'] : ['==', '!=', '>', '<', '>=', '<='];
this.state = {
opChoices,
};
}
changeCol(filter, colOpt) {
const val = (colOpt) ? colOpt.value : null;
this.props.actions.changeFilter(filter, 'col', val);
}
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);
}
removeFilter(filter) {
this.props.actions.removeFilter(filter);
}
render() {
return (
<div>
<div className="row space-1">
<Select
className="col-lg-12"
multi={false}
name="select-column"
placeholder="Select column"
options={this.props.filterColumnOpts.map((o) => ({ value: o, label: o }))}
value={this.props.filter.col}
autosize={false}
onChange={this.changeCol.bind(this, this.props.filter)}
/>
</div>
<div className="row space-1">
<Select
className="col-lg-4"
multi={false}
name="select-op"
placeholder="Select operator"
options={this.state.opChoices.map((o) => ({ value: o, label: o }))}
value={this.props.filter.op}
autosize={false}
onChange={this.changeOp.bind(this, this.props.filter)}
/>
<div className="col-lg-6">
<input
type="text"
onChange={this.changeValue.bind(this, this.props.filter)}
value={this.props.filter.value}
className="form-control input-sm"
placeholder="Filter value"
/>
</div>
<div className="col-lg-2">
<Button
id="remove-button"
bsSize="small"
onClick={this.removeFilter.bind(this, this.props.filter)}
>
<i className="fa fa-minus" />
</Button>
</div>
</div>
</div>
);
}
}
Filter.propTypes = propTypes;
Filter.defaultProps = defaultProps;

View File

@@ -1,109 +1,63 @@
import React from 'react';
// import { Tab, Row, Col, Nav, NavItem } from 'react-bootstrap';
import Select from 'react-select';
import Filter from './Filter';
import { Button } from 'react-bootstrap';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import * as actions from '../actions/exploreActions';
import shortid from 'shortid';
const propTypes = {
actions: React.PropTypes.object,
actions: React.PropTypes.object.isRequired,
filterColumnOpts: React.PropTypes.array,
filters: React.PropTypes.array,
prefix: React.PropTypes.string,
};
const defaultProps = {
filterColumnOpts: [],
filters: [],
prefix: 'flt',
};
class Filters extends React.Component {
constructor(props) {
super(props);
this.state = {
opOpts: ['in', 'not in'],
};
}
changeField(filter, fieldOpt) {
const val = (fieldOpt) ? fieldOpt.value : null;
this.props.actions.changeFilterField(filter, val);
}
changeOp(filter, opOpt) {
const val = (opOpt) ? opOpt.value : null;
this.props.actions.changeFilterOp(filter, val);
}
changeValue(filter, value) {
this.props.actions.changeFilterValue(filter, value);
}
removeFilter(filter) {
this.props.actions.removeFilter(filter);
}
addFilter() {
this.props.actions.addFilter({
id: shortid.generate(),
field: null,
prefix: this.props.prefix,
col: null,
op: null,
value: null,
});
}
render() {
const filters = this.props.filters.map((filter) => (
<div>
<Select
className="row"
multi={false}
name="select-column"
placeholder="Select column"
options={this.props.filterColumnOpts}
value={filter.field}
autosize={false}
onChange={this.changeField.bind(this, filter)}
/>
<div className="row">
<Select
className="col-sm-3"
multi={false}
name="select-op"
placeholder="Select operator"
options={this.state.opOpts.map((o) => ({ value: o, label: o }))}
value={filter.op}
autosize={false}
onChange={this.changeOp.bind(this, filter)}
const filters = [];
this.props.filters.forEach((filter) => {
// only display filters with current prefix
if (filter.prefix === this.props.prefix) {
filters.push(
<Filter
filterColumnOpts={this.props.filterColumnOpts}
actions={this.props.actions}
prefix={this.props.prefix}
filter={filter}
/>
<div className="col-sm-6">
<input
type="text"
onChange={this.changeValue.bind(this, filter)}
className="form-control input-sm"
placeholder="Filter value"
/>
</div>
<div className="col-sm-3">
);
}
});
return (
<div>
{filters}
<div className="row space-2">
<div className="col-lg-2">
<Button
bsStyle="primary"
onClick={this.removeFilter.bind(this, filter)}
id="add-button"
bsSize="sm"
onClick={this.addFilter.bind(this)}
>
<i className="fa fa-minus" />
<i className="fa fa-plus" /> &nbsp; Add Filter
</Button>
</div>
</div>
</div>
)
);
return (
<div className="panel space-1">
<div className="panel-header">Filters</div>
<div className="panel-body">
{filters}
<Button
bsStyle="primary"
onClick={this.addFilter.bind(this)}
>
<i className="fa fa-plus" />Add Filter
</Button>
</div>
</div>
);
}
}
@@ -114,14 +68,9 @@ Filters.defaultProps = defaultProps;
function mapStateToProps(state) {
return {
filterColumnOpts: state.filterColumnOpts,
filters: state.filters,
filters: state.viz.form_data.filters,
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(actions, dispatch),
};
}
export default connect(mapStateToProps, mapDispatchToProps)(Filters);
export { Filters };
export default connect(mapStateToProps, () => ({}))(Filters);

View File

@@ -4,6 +4,7 @@ import $ from 'jquery';
import { Modal, Alert, Button, Radio } from 'react-bootstrap';
import Select from 'react-select';
import { connect } from 'react-redux';
import { getParamObject } from '../../modules/utils.js';
const propTypes = {
can_edit: PropTypes.bool,
@@ -57,10 +58,8 @@ class SaveModal extends React.Component {
saveOrOverwrite(gotodash) {
this.setState({ alert: null });
this.props.actions.removeSaveModalAlert();
const params = {};
const params = getParamObject(this.props.form_data, this.props.datasource_type);
const sliceParams = {};
params.datasource_id = this.props.form_data.datasource;
params.datasource_type = this.props.datasource_type;
params.datasource_name = this.props.form_data.datasource_name;
let sliceName = null;
@@ -76,12 +75,6 @@ class SaveModal extends React.Component {
sliceParams.slice_name = this.props.form_data.slice_name;
}
Object.keys(this.props.form_data).forEach((field) => {
if (this.props.form_data[field] !== null && field !== 'slice_name') {
params[field] = this.props.form_data[field];
}
});
const addToDash = this.state.addToDash;
sliceParams.add_to_dash = addToDash;
let dashboard = null;
@@ -105,7 +98,6 @@ class SaveModal extends React.Component {
default:
dashboard = null;
}
params.V2 = true;
sliceParams.goto_dash = gotodash;
const baseUrl = '/superset/explore/' +
`${this.props.datasource_type}/${this.props.form_data.datasource}/`;