Skip to content

Commit

Permalink
[explore v2] populate dynamic select field options (#1543)
Browse files Browse the repository at this point in the history
* pass field options in viz json

* move field options to fetch_datasource_metadata

* on control panels container mount, fetch datasource meta data and set dynamic field choices

* render options for select fields

* use component class rather than sic

* fix linting

* fix whitespace

* delete unused var

* only render fields once datasource meta has returned

* fix typo

* add datasources and fix column formatting

* fix tests

* never used function

* fix tests

* add test for fetch_datasource_metadata

* remove unneeded props
  • Loading branch information
Alanna Scott committed Nov 8, 2016
1 parent 4530047 commit 51c0470
Show file tree
Hide file tree
Showing 11 changed files with 198 additions and 201 deletions.
90 changes: 24 additions & 66 deletions caravel/assets/javascripts/explorev2/actions/exploreActions.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
const $ = window.$ = require('jquery');
export const SET_DATASOURCE = 'SET_DATASOURCE';
export const SET_TIME_COLUMN_OPTS = 'SET_TIME_COLUMN_OPTS';
export const SET_TIME_GRAIN_OPTS = 'SET_TIME_GRAIN_OPTS';
export const SET_GROUPBY_COLUMN_OPTS = 'SET_GROUPBY_COLUMN_OPTS';
export const SET_METRICS_OPTS = 'SET_METRICS_OPTS';
export const SET_COLUMN_OPTS = 'SET_COLUMN_OPTS';
export const SET_ORDERING_OPTS = 'SET_ORDERING_OPTS';
export const SET_FIELD_OPTIONS = 'SET_FIELD_OPTIONS';
export const TOGGLE_SEARCHBOX = 'TOGGLE_SEARCHBOX';
export const SET_FILTER_COLUMN_OPTS = 'SET_FILTER_COLUMN_OPTS';
export const ADD_FILTER = 'ADD_FILTER';
Expand All @@ -19,89 +14,52 @@ export const CLEAR_ALL_OPTS = 'CLEAR_ALL_OPTS';
export const SET_DATASOURCE_TYPE = 'SET_DATASOURCE_TYPE';
export const SET_FIELD_VALUE = 'SET_FIELD_VALUE';

export function setTimeColumnOpts(timeColumnOpts) {
return { type: SET_TIME_COLUMN_OPTS, timeColumnOpts };
export function setFieldOptions(options) {
return { type: SET_FIELD_OPTIONS, options };
}

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 clearAllOpts() {
return { type: CLEAR_ALL_OPTS };
}

export function setColumnOpts(columnOpts) {
return { type: SET_COLUMN_OPTS, columnOpts };
export function setDatasourceType(datasourceType) {
return { type: SET_DATASOURCE_TYPE, datasourceType };
}

export function setOrderingOpts(orderingOpts) {
return { type: SET_ORDERING_OPTS, orderingOpts };
export const FETCH_STARTED = 'FETCH_STARTED';
export function fetchStarted() {
return { type: FETCH_STARTED };
}

export function setFilterColumnOpts(filterColumnOpts) {
return { type: SET_FILTER_COLUMN_OPTS, filterColumnOpts };
export const FETCH_SUCCEEDED = 'FETCH_SUCCEEDED';
export function fetchSucceeded() {
return { type: FETCH_SUCCEEDED };
}

export function clearAllOpts() {
return { type: CLEAR_ALL_OPTS };
export const FETCH_FAILED = 'FETCH_FAILED';
export function fetchFailed() {
return { type: FETCH_FAILED };
}

export function setFormOpts(datasourceId, datasourceType) {
export function fetchFieldOptions(datasourceId, datasourceType) {
return function (dispatch) {
const timeColumnOpts = [];
const groupByColumnOpts = [];
const metricsOpts = [];
const filterColumnOpts = [];
const timeGrainOpts = [];
const columnOpts = [];
const orderingOpts = [];
dispatch(fetchStarted());

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.time_columns.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 });
});
data.columns.forEach((d) => {
if (d) columnOpts.push({ value: d, label: d });
});
data.ordering_cols.forEach((d) => {
if (d) orderingOpts.push({ value: d, label: d });
});

// Repopulate options for controls
dispatch(setTimeColumnOpts(timeColumnOpts));
dispatch(setTimeGrainOpts(timeGrainOpts));
dispatch(setGroupByColumnOpts(groupByColumnOpts));
dispatch(setMetricsOpts(metricsOpts));
dispatch(setFilterColumnOpts(filterColumnOpts));
dispatch(setColumnOpts(columnOpts));
dispatch(setOrderingOpts(orderingOpts));
// populate options for select type fields
dispatch(setFieldOptions(data.field_options));
dispatch(fetchSucceeded());
} else if (status === 'error') {
dispatch(fetchFailed());
}
});
} else {
// Clear all Select options
dispatch(clearAllOpts());
// in what case don't we have a datasource id?
}
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ const propTypes = {
datasource_id: PropTypes.number.isRequired,
datasource_type: PropTypes.string.isRequired,
actions: PropTypes.object.isRequired,
fields: PropTypes.object.isRequired,
isDatasourceMetaLoading: PropTypes.bool.isRequired,
};

const defaultProps = {
Expand All @@ -23,7 +25,7 @@ class ControlPanelsContainer extends React.Component {
componentWillMount() {
const { datasource_id, datasource_type } = this.props;
if (datasource_id) {
this.props.actions.setFormOpts(datasource_id, datasource_type);
this.props.actions.fetchFieldOptions(datasource_id, datasource_type);
}
}

Expand All @@ -47,27 +49,30 @@ class ControlPanelsContainer extends React.Component {
render() {
return (
<Panel>
<div className="scrollbar-container">
<div className="scrollbar-content">
{this.sectionsToRender().map((section) => (
<ControlPanelSection
key={section.label}
label={section.label}
tooltip={section.description}
>
{section.fieldSetRows.map((fieldSets, i) => (
<FieldSetRow
key={`${section.label}-fieldSetRow-${i}`}
fieldSets={fieldSets}
fieldOverrides={this.fieldOverrides()}
onChange={this.onChange.bind(this)}
/>
))}
</ControlPanelSection>
))}
{/* TODO: add filters section */}
{!this.props.isDatasourceMetaLoading &&
<div className="scrollbar-container">
<div className="scrollbar-content">
{this.sectionsToRender().map((section) => (
<ControlPanelSection
key={section.label}
label={section.label}
tooltip={section.description}
>
{section.fieldSetRows.map((fieldSets, i) => (
<FieldSetRow
key={`${section.label}-fieldSetRow-${i}`}
fieldSets={fieldSets}
fieldOverrides={this.fieldOverrides()}
onChange={this.onChange.bind(this)}
fields={this.props.fields}
/>
))}
</ControlPanelSection>
))}
{/* TODO: add filters section */}
</div>
</div>
</div>
}
</Panel>
);
}
Expand All @@ -78,6 +83,8 @@ ControlPanelsContainer.defaultProps = defaultProps;

function mapStateToProps(state) {
return {
isDatasourceMetaLoading: state.isDatasourceMetaLoading,
fields: state.fields,
datasource_id: state.datasource_id,
datasource_type: state.datasource_type,
viz_type: state.viz.form_data.viz_type,
Expand Down
48 changes: 26 additions & 22 deletions caravel/assets/javascripts/explorev2/components/FieldSetRow.jsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import React, { PropTypes } from 'react';
import FieldSet from './FieldSet';
import { fields } from '../stores/store';

const NUM_COLUMNS = 12;

const propTypes = {
fields: PropTypes.object.isRequired,
fieldSets: PropTypes.array.isRequired,
fieldOverrides: PropTypes.object,
onChange: PropTypes.func,
Expand All @@ -15,29 +15,33 @@ const defaultProps = {
onChange: () => {},
};

function getFieldData(fs, fieldOverrides) {
let fieldData = fields[fs];
if (fieldOverrides.hasOwnProperty(fs)) {
const overrideData = fieldOverrides[fs];
fieldData = Object.assign({}, fieldData, overrideData);
export default class FieldSetRow extends React.Component {
getFieldData(fs) {
const { fields, fieldOverrides } = this.props;
let fieldData = fields[fs];
if (fieldOverrides.hasOwnProperty(fs)) {
const overrideData = fieldOverrides[fs];
fieldData = Object.assign({}, fieldData, overrideData);
}
return fieldData;
}
return fieldData;
}

export default function FieldSetRow({ fieldSets, fieldOverrides, onChange }) {
const colSize = NUM_COLUMNS / fieldSets.length;
return (
<div className="row">
{fieldSets.map((fs) => {
const fieldData = getFieldData(fs, fieldOverrides);
return (
<div className={`col-lg-${colSize} col-xs-12`} key={fs}>
<FieldSet name={fs} onChange={onChange} {...fieldData} />
</div>
);
})}
</div>
);
render() {
const colSize = NUM_COLUMNS / this.props.fieldSets.length;
const { onChange } = this.props;
return (
<div className="row">
{this.props.fieldSets.map((fs) => {
const fieldData = this.getFieldData(fs);
return (
<div className={`col-lg-${colSize} col-xs-12`} key={fs}>
<FieldSet name={fs} onChange={onChange} {...fieldData} />
</div>
);
})}
</div>
);
}
}

FieldSetRow.propTypes = propTypes;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { slugify } from '../../modules/utils';

const propTypes = {
name: PropTypes.string.isRequired,
choices: PropTypes.array,
label: PropTypes.string,
description: PropTypes.string,
onChange: PropTypes.func,
Expand All @@ -20,6 +21,7 @@ export default class SelectField extends React.Component {
onChange(opt) {
this.props.onChange(this.props.name, opt.target.value);
}

render() {
return (
<FormGroup controlId={`formControlsSelect-${slugify(this.props.label)}`}>
Expand All @@ -32,8 +34,7 @@ export default class SelectField extends React.Component {
placeholder="select"
onChange={this.onChange.bind(this)}
>
<option value="select">select</option>
<option value="other">...</option>
{this.props.choices.map((c) => <option key={c[0]} value={c[0]}>{c[1]}</option>)}
</FormControl>
</FormGroup>
);
Expand Down
39 changes: 27 additions & 12 deletions caravel/assets/javascripts/explorev2/reducers/exploreReducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,39 @@ import { addToArr, removeFromArr, alterInArr } from '../../../utils/reducerUtils

export const exploreReducer = function (state, action) {
const actionHandlers = {
[actions.SET_TIME_COLUMN_OPTS]() {
return Object.assign({}, state, { timeColumnOpts: action.timeColumnOpts });
[actions.SET_DATASOURCE]() {
return Object.assign({}, state, { datasourceId: action.datasourceId });
},
[actions.SET_TIME_GRAIN_OPTS]() {
return Object.assign({}, state, { timeGrainOpts: action.timeGrainOpts });

[actions.FETCH_STARTED]() {
return Object.assign({}, state, { isDatasourceMetaLoading: true });
},
[actions.SET_GROUPBY_COLUMN_OPTS]() {
return Object.assign({}, state, { groupByColumnOpts: action.groupByColumnOpts });

[actions.FETCH_SUCCEEDED]() {
return Object.assign({}, state, { isDatasourceMetaLoading: false });
},
[actions.SET_METRICS_OPTS]() {
return Object.assign({}, state, { metricsOpts: action.metricsOpts });

[actions.FETCH_FAILED]() {
// todo(alanna) handle failure/error state
return Object.assign({}, state, { isDatasourceMetaLoading: false });
},
[actions.SET_COLUMN_OPTS]() {
return Object.assign({}, state, { columnOpts: action.columnOpts });

[actions.SET_FIELD_OPTIONS]() {
const newState = Object.assign({}, state);
const optionsByFieldName = action.options;
const fieldNames = Object.keys(optionsByFieldName);

fieldNames.forEach((fieldName) => {
newState.fields[fieldName].choices = optionsByFieldName[fieldName];
});

return Object.assign({}, state, newState);
},
[actions.SET_ORDERING_OPTS]() {
return Object.assign({}, state, { orderingOpts: action.orderingOpts });

[actions.TOGGLE_SEARCHBOX]() {
return Object.assign({}, state, { searchBox: action.searchBox });
},

[actions.SET_FILTER_COLUMN_OPTS]() {
return Object.assign({}, state, { filterColumnOpts: action.filterColumnOpts });
},
Expand Down
Loading

0 comments on commit 51c0470

Please sign in to comment.