Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Explore control panel - Chart control, TimeFilter, GroupBy, Filters #1205

Merged
merged 5 commits into from
Sep 29, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
192 changes: 192 additions & 0 deletions caravel/assets/javascripts/explorev2/actions/exploreActions.js
Original file line number Diff line number Diff line change
@@ -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) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think this function belongs in an actionCreators file but it we can change that in a subsequent pr

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 };
}
11 changes: 11 additions & 0 deletions caravel/assets/javascripts/explorev2/components/ChartContainer.jsx
Original file line number Diff line number Diff line change
@@ -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;
89 changes: 89 additions & 0 deletions caravel/assets/javascripts/explorev2/components/ChartControl.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import React from 'react';
import Select from 'react-select';
import { bindActionCreators } from 'redux';
import * as actions from '../actions/exploreActions';
Copy link
Contributor

@ascott ascott Sep 28, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rather than importing all actions here i would suggest just importing the ones needed for this component...

import { setFormOpts, setDatasource, resetFormData, setVizType }
  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);
Copy link
Contributor

@ascott ascott Sep 28, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

once we import actions above we can use them like this:
setFormOpts(this.props.datasourceId, this.props.datasourceType);

makes more sense to import the actions in this component rather than passing them in as props.

Copy link
Contributor Author

@vera-liu vera-liu Sep 28, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This didn't work.. Suggested by the redux doc here: http://redux.js.org/docs/basics/Actions.html#action-creators
We should bind action creators to dispatch() function, then map dispatch to component's props. I could still explicitly import actions like:
import { setFormOpts, setDatasource, resetFormData, setVizType } from '../actions/exploreActions';

and then do this in mapDispatchToProps:
actions: bindActionCreators({ setFormOpts, setDatasource, resetFormData, setVizType, }, dispatch),
But dispatching actions would still be the same:

this.props.actions.setDatasource(val);

Is there a better way to do this?
@ascott

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah ok, maybe i am carrying over some Alt patterns that don't work in redux. so when you call bindActionCreators the actions are passed as props to the component?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The way I understand it is that bindActionCreators bind all action creators with dispatch function, so that we don't have to write redundant onClick() => dispatch(actionFunc()) statements. mapDispatchToProps() allows us to call dispatch function from the component's props, instead of accessing global store everytime we dispatch.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

}
}
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);
Original file line number Diff line number Diff line change
@@ -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;
Original file line number Diff line number Diff line change
@@ -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;
Loading