[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,192 @@
const $ = window.$ = require('jquery');
export const SET_DATASOURCE = 'SET_DATASOURCE';
export const SET_VIZTYPE = 'SET_VIZTYPE';
export const SET_TIME_COLUMN_OPTS = 'SET_TIME_COLUMN_OPTS';
export const SET_TIME_GRAIN_OPTS = 'SET_TIME_GRAIN_OPTS';
export const SET_TIME_COLUMN = 'SET_TIME_COLUMN';
export const SET_TIME_GRAIN = 'SET_TIME_GRAIN';
export const SET_SINCE = 'SET_SINCE';
export const SET_UNTIL = 'SET_UNTIL';
export const SET_GROUPBY_COLUMNS = 'SET_GROUPBY_COLUMNS';
export const SET_GROUPBY_COLUMN_OPTS = 'SET_GROUPBY_COLUMN_OPTS';
export const SET_METRICS = 'SET_METRICS';
export const SET_METRICS_OPTS = 'SET_METRICS_OPTS';
export const ADD_COLUMN = 'ADD_COLUMN';
export const REMOVE_COLUMN = 'REMOVE_COLUMN';
export const ADD_ORDERING = 'ADD_ORDERING';
export const REMOVE_ORDERING = 'REMOVE_ORDERING';
export const SET_TIME_STAMP = 'SET_TIME_STAMP';
export const SET_ROW_LIMIT = 'SET_ROW_LIMIT';
export const TOGGLE_SEARCHBOX = 'TOGGLE_SEARCHBOX';
export const SET_FILTER_COLUMN_OPTS = 'SET_FILTER_COLUMN_OPTS';
export const SET_WHERE_CLAUSE = 'SET_WHERE_CLAUSE';
export const SET_HAVING_CLAUSE = 'SET_HAVING_CLAUSE';
export const ADD_FILTER = 'ADD_FILTER';
export const SET_FILTER = 'SET_FILTER';
export const REMOVE_FILTER = 'REMOVE_FILTER';
export const CHANGE_FILTER_FIELD = 'CHANGE_FILTER_FIELD';
export const CHANGE_FILTER_OP = 'CHANGE_FILTER_OP';
export const CHANGE_FILTER_VALUE = 'CHANGE_FILTER_VALUE';
export const RESET_FORM_DATA = 'RESET_FORM_DATA';
export const CLEAR_ALL_OPTS = 'CLEAR_ALL_OPTS';
export function setTimeColumnOpts(timeColumnOpts) {
return { type: SET_TIME_COLUMN_OPTS, timeColumnOpts };
}
export function setTimeGrainOpts(timeGrainOpts) {
return { type: SET_TIME_GRAIN_OPTS, timeGrainOpts };
}
export function setGroupByColumnOpts(groupByColumnOpts) {
return { type: SET_GROUPBY_COLUMN_OPTS, groupByColumnOpts };
}
export function setMetricsOpts(metricsOpts) {
return { type: SET_METRICS_OPTS, metricsOpts };
}
export function setFilterColumnOpts(filterColumnOpts) {
return { type: SET_FILTER_COLUMN_OPTS, filterColumnOpts };
}
export function resetFormData() {
// Clear all form data when switching datasource
return { type: RESET_FORM_DATA };
}
export function clearAllOpts() {
return { type: CLEAR_ALL_OPTS };
}
export function setFormOpts(datasourceId, datasourceType) {
return function (dispatch) {
const timeColumnOpts = [];
const groupByColumnOpts = [];
const metricsOpts = [];
const filterColumnOpts = [];
const timeGrainOpts = [];
if (datasourceId) {
const params = [`datasource_id=${datasourceId}`, `datasource_type=${datasourceType}`];
const url = '/caravel/fetch_datasource_metadata?' + params.join('&');
$.get(url, (data, status) => {
if (status === 'success') {
data.dttm_cols.forEach((d) => {
if (d) timeColumnOpts.push({ value: d, label: d });
});
data.groupby_cols.forEach((d) => {
if (d) groupByColumnOpts.push({ value: d, label: d });
});
data.metrics.forEach((d) => {
if (d) metricsOpts.push({ value: d[1], label: d[0] });
});
data.filter_cols.forEach((d) => {
if (d) filterColumnOpts.push({ value: d, label: d });
});
data.time_grains.forEach((d) => {
if (d) timeGrainOpts.push({ value: d, label: d });
});
// Repopulate options for controls
dispatch(setTimeColumnOpts(timeColumnOpts));
dispatch(setTimeGrainOpts(timeGrainOpts));
dispatch(setGroupByColumnOpts(groupByColumnOpts));
dispatch(setMetricsOpts(metricsOpts));
dispatch(setFilterColumnOpts(filterColumnOpts));
}
});
} else {
// Clear all Select options
dispatch(clearAllOpts());
}
};
}
export function setDatasource(datasourceId) {
return { type: SET_DATASOURCE, datasourceId };
}
export function setVizType(vizType) {
return { type: SET_VIZTYPE, vizType };
}
export function setTimeColumn(timeColumn) {
return { type: SET_TIME_COLUMN, timeColumn };
}
export function setTimeGrain(timeGrain) {
return { type: SET_TIME_GRAIN, timeGrain };
}
export function setSince(since) {
return { type: SET_SINCE, since };
}
export function setUntil(until) {
return { type: SET_UNTIL, until };
}
export function setGroupByColumns(groupByColumns) {
return { type: SET_GROUPBY_COLUMNS, groupByColumns };
}
export function setMetrics(metrics) {
return { type: SET_METRICS, metrics };
}
export function addColumn(column) {
return { type: ADD_COLUMN, column };
}
export function removeColumn(column) {
return { type: REMOVE_COLUMN, column };
}
export function addOrdering(ordering) {
return { type: ADD_ORDERING, ordering };
}
export function removeOrdering(ordering) {
return { type: REMOVE_ORDERING, ordering };
}
export function setTimeStamp(timeStampFormat) {
return { type: SET_TIME_STAMP, timeStampFormat };
}
export function setRowLimit(rowLimit) {
return { type: SET_ROW_LIMIT, rowLimit };
}
export function toggleSearchBox(searchBox) {
return { type: TOGGLE_SEARCHBOX, searchBox };
}
export function setWhereClause(whereClause) {
return { type: SET_WHERE_CLAUSE, whereClause };
}
export function setHavingClause(havingClause) {
return { type: SET_HAVING_CLAUSE, havingClause };
}
export function addFilter(filter) {
return { type: ADD_FILTER, filter };
}
export function removeFilter(filter) {
return { type: REMOVE_FILTER, filter };
}
export function changeFilterField(filter, field) {
return { type: CHANGE_FILTER_FIELD, filter, field };
}
export function changeFilterOp(filter, op) {
return { type: CHANGE_FILTER_OP, filter, op };
}
export function changeFilterValue(filter, value) {
return { type: CHANGE_FILTER_VALUE, filter, value };
}

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);

View File

@@ -0,0 +1,35 @@
export const VIZ_TYPES = [
{ value: 'dist_bar', label: 'Distribution - Bar Chart', requiresTime: false },
{ value: 'pie', label: 'Pie Chart', requiresTime: false },
{ value: 'line', label: 'Time Series - Line Chart', requiresTime: true },
{ value: 'bar', label: 'Time Series - Bar Chart', requiresTime: true },
{ value: 'compare', label: 'Time Series - Percent Change', requiresTime: true },
{ value: 'area', label: 'Time Series - Stacked', requiresTime: true },
{ value: 'table', label: 'Table View', requiresTime: false },
{ value: 'markup', label: 'Markup', requiresTime: false },
{ value: 'pivot_table', label: 'Pivot Table', requiresTime: false },
{ value: 'separator', label: 'Separator', requiresTime: false },
{ value: 'word_cloud', label: 'Word Cloud', requiresTime: false },
{ value: 'treemap', label: 'Treemap', requiresTime: false },
{ value: 'cal_heatmap', label: 'Calendar Heatmap', requiresTime: true },
{ value: 'box_plot', label: 'Box Plot', requiresTime: false },
{ value: 'bubble', label: 'Bubble Chart', requiresTime: false },
{ value: 'big_number', label: 'Big Number with Trendline', requiresTime: false },
{ value: 'bubble', label: 'Bubble Chart', requiresTime: false },
{ value: 'histogram', label: 'Histogram', requiresTime: false },
{ value: 'sunburst', label: 'Sunburst', requiresTime: false },
{ value: 'sankey', label: 'Sankey', requiresTime: false },
{ value: 'directed_force', label: 'Directed Force Layout', requiresTime: false },
{ value: 'world_map', label: 'World Map', requiresTime: false },
{ value: 'filter_box', label: 'Filter Box', requiresTime: false },
{ value: 'iframe', label: 'iFrame', requiresTime: false },
{ value: 'para', label: 'Parallel Coordinates', requiresTime: false },
{ value: 'heatmap', label: 'Heatmap', requiresTime: false },
{ value: 'horizon', label: 'Horizon', requiresTime: false },
{ value: 'mapbox', label: 'Mapbox', requiresTime: false },
];
export const sinceOptions = ['1 hour ago', '12 hours ago', '1 day ago',
'7 days ago', '28 days ago', '90 days ago', '1 year ago'];
export const untilOptions = ['now', '1 day ago', '7 days ago',
'28 days ago', '90 days ago', '1 year ago'];

View File

@@ -0,0 +1,43 @@
import React from 'react';
import ReactDOM from 'react-dom';
import ExploreViewContainer from './components/ExploreViewContainer';
import { createStore, applyMiddleware, compose } from 'redux';
import { Provider } from 'react-redux';
import thunk from 'redux-thunk';
import { initialState } from './stores/store';
const exploreViewContainer = document.getElementById('js-explore-view-container');
const bootstrapData = JSON.parse(exploreViewContainer.getAttribute('data-bootstrap'));
import { exploreReducer } from './reducers/exploreReducer';
const bootstrappedState = Object.assign(initialState, {
datasources: bootstrapData.datasources,
datasourceId: parseInt(bootstrapData.datasource_id, 10),
datasourceType: bootstrapData.datasource_type,
sliceName: bootstrapData.viz.form_data.slice_name,
sliceId: bootstrapData.viz.form_data.slice_id,
vizType: bootstrapData.viz.form_data.viz_type,
timeColumn: bootstrapData.viz.form_data.granularity_sqla,
timeGrain: bootstrapData.viz.form_data.time_grain_sqla,
metrics: [bootstrapData.viz.form_data.metric].map((m) => ({ value: m, label: m })),
since: bootstrapData.viz.form_data.since,
until: bootstrapData.viz.form_data.until,
havingClause: bootstrapData.viz.form_data.having,
whereClause: bootstrapData.viz.form_data.where,
});
const store = createStore(exploreReducer, bootstrappedState,
compose(applyMiddleware(thunk))
);
ReactDOM.render(
<Provider store={store}>
<ExploreViewContainer
data={bootstrapData}
/>
</Provider>,
exploreViewContainer
);

View File

@@ -0,0 +1,111 @@
import { defaultFormData, defaultOpts } from '../stores/store';
import * as actions from '../actions/exploreActions';
import { addToArr, removeFromArr, alterInArr } from '../../../utils/reducerUtils';
export const exploreReducer = function (state, action) {
const actionHandlers = {
[actions.SET_DATASOURCE]() {
return Object.assign({}, state, { datasourceId: action.datasourceId });
},
[actions.SET_VIZTYPE]() {
return Object.assign({}, state, { vizType: action.vizType });
},
[actions.SET_TIME_COLUMN_OPTS]() {
return Object.assign({}, state, { timeColumnOpts: action.timeColumnOpts });
},
[actions.SET_TIME_GRAIN_OPTS]() {
return Object.assign({}, state, { timeGrainOpts: action.timeGrainOpts });
},
[actions.SET_TIME_COLUMN]() {
return Object.assign({}, state, { timeColumn: action.timeColumn });
},
[actions.SET_TIME_GRAIN]() {
return Object.assign({}, state, { timeGrain: action.timeGrain });
},
[actions.SET_SINCE]() {
return Object.assign({}, state, { since: action.since });
},
[actions.SET_UNTIL]() {
return Object.assign({}, state, { until: action.until });
},
[actions.SET_GROUPBY_COLUMN_OPTS]() {
return Object.assign({}, state, { groupByColumnOpts: action.groupByColumnOpts });
},
[actions.SET_GROUPBY_COLUMNS]() {
return Object.assign({}, state, { groupByColumns: action.groupByColumns });
},
[actions.SET_METRICS_OPTS]() {
return Object.assign({}, state, { metricsOpts: action.metricsOpts });
},
[actions.SET_METRICS]() {
return Object.assign({}, state, { metrics: action.metrics });
},
[actions.ADD_COLUMN]() {
return Object.assign({}, state, { columns: [...state.columns, action.column] });
},
[actions.REMOVE_COLUMN]() {
const newColumns = [];
state.columns.forEach((c) => {
if (c !== action.column) {
newColumns.push(c);
}
});
return Object.assign({}, state, { columns: newColumns });
},
[actions.ADD_ORDERING]() {
return Object.assign({}, state, { orderings: [...state.orderings, action.ordering] });
},
[actions.REMOVE_ORDERING]() {
const newOrderings = [];
state.orderings.forEach((o) => {
if (o !== action.ordering) {
newOrderings.push(o);
}
});
return Object.assign({}, state, { orderings: newOrderings });
},
[actions.SET_TIME_STAMP]() {
return Object.assign({}, state, { timeStampFormat: action.timeStampFormat });
},
[actions.SET_ROW_LIMIT]() {
return Object.assign({}, state, { rowLimit: action.rowLimit });
},
[actions.TOGGLE_SEARCHBOX]() {
return Object.assign({}, state, { searchBox: action.searchBox });
},
[actions.SET_WHERE_CLAUSE]() {
return Object.assign({}, state, { whereClause: action.whereClause });
},
[actions.SET_HAVING_CLAUSE]() {
return Object.assign({}, state, { havingClause: action.havingClause });
},
[actions.SET_FILTER_COLUMN_OPTS]() {
return Object.assign({}, state, { filterColumnOpts: action.filterColumnOpts });
},
[actions.ADD_FILTER]() {
return addToArr(state, 'filters', action.filter);
},
[actions.REMOVE_FILTER]() {
return removeFromArr(state, 'filters', action.filter);
},
[actions.CHANGE_FILTER_FIELD]() {
return alterInArr(state, 'filters', action.filter, { field: action.field });
},
[actions.CHANGE_FILTER_OP]() {
return alterInArr(state, 'filters', action.filter, { op: action.op });
},
[actions.CHANGE_FILTER_VALUE]() {
return alterInArr(state, 'filters', action.filter, { value: action.value });
},
[actions.RESET_FORM_DATA]() {
return Object.assign({}, state, defaultFormData);
},
[actions.CLEAR_ALL_OPTS]() {
return Object.assign({}, state, defaultOpts);
},
};
if (action.type in actionHandlers) {
return actionHandlers[action.type]();
}
return state;
};

View File

@@ -0,0 +1,52 @@
export const initialState = {
datasources: null,
datasourceId: null,
datasourceType: null,
vizType: null,
timeColumnOpts: [],
timeColumn: null,
timeGrainOpts: [],
timeGrain: null,
since: null,
until: null,
groupByColumnOpts: [],
groupByColumns: [],
metricsOpts: [],
metrics: [],
columns: [],
orderings: [],
timeStampFormat: null,
rowLimit: null,
searchBox: false,
whereClause: '',
havingClause: '',
filters: [],
filterColumnOpts: [],
};
// TODO: add datasource_type here after druid support is added
export const defaultFormData = {
vizType: null,
timeColumn: null,
timeGrain: null,
since: null,
until: null,
groupByColumns: [],
metrics: [],
columns: [],
orderings: [],
timeStampFormat: null,
rowLimit: null,
searchBox: false,
whereClause: '',
havingClause: '',
filters: [],
};
export const defaultOpts = {
timeColumnOpts: [],
timeGrainOpts: [],
groupByColumnOpts: [],
metricsOpts: [],
filterColumnOpts: [],
};