[explore-v2] setup, basic layout, control panels, v2 url (#1233)

* Explore control panel - Chart control, TimeFilter, GroupBy, Filters (#1205)

* create structure for new forked explore view (#1099)

* create structure for new forked explore view

* update component name

* add bootstrap data pattern

* remove console.log

* Associate version to entry files (#1060)

* Associate version to entry files

* Modified path joins for configs

* Made changes based on comments

* Created store and reducers (#1108)

* Created store and reducers

* Added spec

* Modifications based on comments

* Explore control panel components: Chart control, Time filter, SQL,
GroupBy and Filters

* Modifications based on comments

* accommodate old and new explore urls

* move bootstrap data up in scope

* fix code climate issues

* fix long lines

* fix syntax error
This commit is contained in:
Alanna Scott
2016-10-03 22:47:39 -07:00
committed by GitHub
parent d8638dbcf3
commit e6e902e8df
20 changed files with 1292 additions and 1 deletions

View File

@@ -0,0 +1,11 @@
import React from 'react';
import { Panel } from 'react-bootstrap';
const ChartContainer = function () {
return (
<Panel header="Chart title">
chart goes here
</Panel>
);
};
export default ChartContainer;

View File

@@ -0,0 +1,89 @@
import React from 'react';
import Select from 'react-select';
import { bindActionCreators } from 'redux';
import * as actions from '../actions/exploreActions';
import { connect } from 'react-redux';
import { VIZ_TYPES } from '../constants';
const propTypes = {
actions: React.PropTypes.object,
datasources: React.PropTypes.array,
datasourceId: React.PropTypes.number,
datasourceType: React.PropTypes.string,
vizType: React.PropTypes.string,
};
const defaultProps = {
datasources: [],
datasourceId: null,
datasourceType: null,
vizType: null,
};
class ChartControl extends React.Component {
componentWillMount() {
if (this.props.datasourceId) {
this.props.actions.setFormOpts(this.props.datasourceId, this.props.datasourceType);
}
}
changeDatasource(datasourceOpt) {
const val = (datasourceOpt) ? datasourceOpt.value : null;
this.props.actions.setDatasource(val);
this.props.actions.resetFormData();
this.props.actions.setFormOpts(val, this.props.datasourceType);
}
changeViz(vizOpt) {
const val = (vizOpt) ? vizOpt.value : null;
this.props.actions.setVizType(val);
}
render() {
return (
<div className="panel space-1">
<div className="panel-header">Chart Options</div>
<div className="panel-body">
<h5 className="section-heading">Datasource</h5>
<div className="row">
<Select
name="select-datasource"
placeholder="Select a datasource"
options={this.props.datasources.map((d) => ({ value: d[0], label: d[1] }))}
value={this.props.datasourceId}
autosize={false}
onChange={this.changeDatasource.bind(this)}
/>
</div>
<h5 className="section-heading">Viz Type</h5>
<div className="row">
<Select
name="select-viztype"
placeholder="Select a viz type"
options={VIZ_TYPES}
value={this.props.vizType}
autosize={false}
onChange={this.changeViz.bind(this)}
/>
</div>
</div>
</div>
);
}
}
ChartControl.propTypes = propTypes;
ChartControl.defaultProps = defaultProps;
function mapStateToProps(state) {
return {
datasources: state.datasources,
datasourceId: state.datasourceId,
datasourceType: state.datasourceType,
vizType: state.vizType,
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(actions, dispatch),
};
}
export default connect(mapStateToProps, mapDispatchToProps)(ChartControl);

View File

@@ -0,0 +1,20 @@
import React from 'react';
import { Panel } from 'react-bootstrap';
import TimeFilter from './TimeFilter';
import ChartControl from './ChartControl';
import GroupBy from './GroupBy';
import SqlClause from './SqlClause';
import Filters from './Filters';
const ControlPanelsContainer = function () {
return (
<Panel>
<ChartControl />
<TimeFilter />
<GroupBy />
<SqlClause />
<Filters />
</Panel>
);
};
export default ControlPanelsContainer;

View File

@@ -0,0 +1,26 @@
import React from 'react';
import ChartContainer from './ChartContainer';
import ControlPanelsContainer from './ControlPanelsContainer';
import QueryAndSaveButtons from './QueryAndSaveButtons';
const ExploreViewContainer = function () {
return (
<div className="container-fluid">
<div className="row">
<div className="col-sm-3">
<QueryAndSaveButtons
canAdd="True"
onQuery={() => { console.log('clicked query'); }}
/>
<br /><br />
<ControlPanelsContainer />
</div>
<div className="col-sm-9">
<ChartContainer />
</div>
</div>
</div>
);
};
export default ExploreViewContainer;

View File

@@ -0,0 +1,128 @@
import React from 'react';
// import { Tab, Row, Col, Nav, NavItem } from 'react-bootstrap';
import Select from 'react-select';
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,
filterColumnOpts: React.PropTypes.array,
filters: React.PropTypes.array,
};
const defaultProps = {
filterColumnOpts: [],
filters: [],
};
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,
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)}
/>
<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">
<Button
bsStyle="primary"
onClick={this.removeFilter.bind(this, filter)}
>
<i className="fa fa-minus" />
</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>
);
}
}
Filters.propTypes = propTypes;
Filters.defaultProps = defaultProps;
function mapStateToProps(state) {
return {
filterColumnOpts: state.filterColumnOpts,
filters: state.filters,
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(actions, dispatch),
};
}
export default connect(mapStateToProps, mapDispatchToProps)(Filters);

View File

@@ -0,0 +1,82 @@
import React from 'react';
import Select from 'react-select';
import { bindActionCreators } from 'redux';
import * as actions from '../actions/exploreActions';
import { connect } from 'react-redux';
const propTypes = {
actions: React.PropTypes.object,
metricsOpts: React.PropTypes.array,
metrics: React.PropTypes.array,
groupByColumnOpts: React.PropTypes.array,
groupByColumns: React.PropTypes.array,
};
const defaultProps = {
metricsOpts: [],
metrics: [],
groupByColumnOpts: [],
groupByColumns: [],
};
class GroupBy extends React.Component {
changeColumns(groupByColumnOpts) {
this.props.actions.setGroupByColumns(groupByColumnOpts);
}
changeMetrics(metricsOpts) {
this.props.actions.setMetrics(metricsOpts);
}
render() {
return (
<div className="panel space-1">
<div className="panel-header">GroupBy</div>
<div className="panel-body">
<div className="row">
<h5 className="section-heading">GroupBy Column</h5>
<Select
multi
name="select-time-column"
placeholder="Select groupby columns"
options={this.props.groupByColumnOpts}
value={this.props.groupByColumns}
autosize={false}
onChange={this.changeColumns.bind(this)}
/>
</div>
<div className="row">
<h5 className="section-heading">Metrics</h5>
<Select
multi
name="select-since"
placeholder="Select metrics"
options={this.props.metricsOpts}
value={this.props.metrics}
autosize={false}
onChange={this.changeMetrics.bind(this)}
/>
</div>
</div>
</div>
);
}
}
GroupBy.propTypes = propTypes;
GroupBy.defaultProps = defaultProps;
function mapStateToProps(state) {
return {
metricsOpts: state.metricsOpts,
metrics: state.metrics,
groupByColumnOpts: state.groupByColumnOpts,
groupByColumns: state.groupByColumns,
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(actions, dispatch),
};
}
export default connect(mapStateToProps, mapDispatchToProps)(GroupBy);

View File

@@ -0,0 +1,31 @@
import React, { PropTypes } from 'react';
import classnames from 'classnames';
const propTypes = {
canAdd: PropTypes.string.isRequired,
onQuery: PropTypes.func.isRequired,
};
export default function QueryAndSaveBtns({ canAdd, onQuery }) {
const saveClasses = classnames('btn btn-default btn-sm', {
'disabled disabledButton': canAdd !== 'True',
});
return (
<div className="btn-group query-and-save">
<button type="button" className="btn btn-primary btn-sm" onClick={onQuery}>
<i className="fa fa-bolt"></i> Query
</button>
<button
type="button"
className={saveClasses}
data-target="#save_modal"
data-toggle="modal"
>
<i className="fa fa-plus-circle"></i> Save as
</button>
</div>
);
}
QueryAndSaveBtns.propTypes = propTypes;

View File

@@ -0,0 +1,58 @@
import React from 'react';
import { bindActionCreators } from 'redux';
import * as actions from '../actions/exploreActions';
import { connect } from 'react-redux';
const propTypes = {
actions: React.PropTypes.object,
};
class SqlClause extends React.Component {
changeWhere(whereClause) {
this.props.actions.setWhereClause(whereClause);
}
changeHaving(havingClause) {
this.props.actions.setHavingClause(havingClause);
}
render() {
return (
<div className="panel space-1">
<div className="panel-header">SQL</div>
<div className="panel-body">
<div className="row">
<h5 className="section-heading">Where</h5>
<input
type="text"
onChange={this.changeWhere.bind(this)}
className="form-control input-sm"
placeholder="Where Clause"
/>
</div>
<div className="row">
<h5 className="section-heading">Having</h5>
<input
type="text"
onChange={this.changeHaving.bind(this)}
className="form-control input-sm"
placeholder="Having Clause"
/>
</div>
</div>
</div>
);
}
}
SqlClause.propTypes = propTypes;
function mapStateToProps() {
return {};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(actions, dispatch),
};
}
export default connect(mapStateToProps, mapDispatchToProps)(SqlClause);

View File

@@ -0,0 +1,117 @@
import React from 'react';
import Select from 'react-select';
import { bindActionCreators } from 'redux';
import * as actions from '../actions/exploreActions';
import { connect } from 'react-redux';
import { sinceOptions, untilOptions } from '../constants';
const propTypes = {
actions: React.PropTypes.object,
timeColumnOpts: React.PropTypes.array,
timeColumn: React.PropTypes.string,
timeGrainOpts: React.PropTypes.array,
timeGrain: React.PropTypes.string,
since: React.PropTypes.string,
until: React.PropTypes.string,
};
const defaultProps = {
timeColumnOpts: [],
timeColumn: null,
timeGrainOpts: [],
timeGrain: null,
since: null,
until: null,
};
class TimeFilter extends React.Component {
changeTimeColumn(timeColumnOpt) {
const val = (timeColumnOpt) ? timeColumnOpt.value : null;
this.props.actions.setTimeColumn(val);
}
changeTimeGrain(timeGrainOpt) {
const val = (timeGrainOpt) ? timeGrainOpt.value : null;
this.props.actions.setTimeGrain(val);
}
changeSince(sinceOpt) {
const val = (sinceOpt) ? sinceOpt.value : null;
this.props.actions.setSince(val);
}
changeUntil(untilOpt) {
const val = (untilOpt) ? untilOpt.value : null;
this.props.actions.setUntil(val);
}
render() {
return (
<div className="panel space-1">
<div className="panel-header">Time Filter</div>
<div className="panel-body">
<div className="row">
<h5 className="section-heading">Time Column & Grain</h5>
<Select
className="col-sm-6"
name="select-time-column"
placeholder="Select a time column"
options={this.props.timeColumnOpts}
value={this.props.timeColumn}
autosize={false}
onChange={this.changeTimeColumn.bind(this)}
/>
<Select
className="col-sm-6"
name="select-time-grain"
placeholder="Select a time grain"
options={this.props.timeGrainOpts}
value={this.props.timeGrain}
autosize={false}
onChange={this.changeTimeGrain.bind(this)}
/>
</div>
<div className="row">
<h5 className="section-heading">Since - Until</h5>
<Select
className="col-sm-6"
name="select-since"
placeholder="Select Since Time"
options={sinceOptions.map((s) => ({ value: s, label: s }))}
value={this.props.since}
autosize={false}
onChange={this.changeSince.bind(this)}
/>
<Select
className="col-sm-6"
name="select-until"
placeholder="Select Until Time"
options={untilOptions.map((u) => ({ value: u, label: u }))}
value={this.props.until}
autosize={false}
onChange={this.changeUntil.bind(this)}
/>
</div>
</div>
</div>
);
}
}
TimeFilter.propTypes = propTypes;
TimeFilter.defaultProps = defaultProps;
function mapStateToProps(state) {
return {
timeColumnOpts: state.timeColumnOpts,
timeColumn: state.timeColumn,
timeGrainOpts: state.timeGrainOpts,
timeGrain: state.timeGrain,
since: state.since,
until: state.until,
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(actions, dispatch),
};
}
export default connect(mapStateToProps, mapDispatchToProps)(TimeFilter);