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 v2] populate dynamic select field options #1543

Merged
merged 16 commits into from
Nov 8, 2016
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) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I used to think this would be used for setting Druid/Table for datasource, but it seems that we don't have the option to switch datasource type in explore view? @mistercrunch

Copy link
Author

Choose a reason for hiding this comment

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

i don't think we actually need this. we can change a datasource but there is not a concept of changing the datasource type only. datasource type is tied to the datasource. i think this shows up like this because of some white space diffing. we can remove in a follow up pr.

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?
Copy link
Contributor

@vera-liu vera-liu Nov 8, 2016

Choose a reason for hiding this comment

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

in react-select there's an option to empty the select field, namely datasource could be null, not sure if this is needed

Copy link
Author

Choose a reason for hiding this comment

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

i'm not sure it's needed either. in the explore view we should always have a datasource. we can remove in a follow up PR.

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