[explore v2] populate dynamic select field options (#1543)

* 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
This commit is contained in:
Alanna Scott 2016-11-08 15:55:49 -08:00 committed by GitHub
parent 4530047c76
commit 51c0470f0b
11 changed files with 204 additions and 207 deletions

View File

@ -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';
@ -19,47 +14,36 @@ 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 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 setColumnOpts(columnOpts) {
return { type: SET_COLUMN_OPTS, columnOpts };
}
export function setOrderingOpts(orderingOpts) {
return { type: SET_ORDERING_OPTS, orderingOpts };
}
export function setFilterColumnOpts(filterColumnOpts) {
return { type: SET_FILTER_COLUMN_OPTS, filterColumnOpts };
export function setFieldOptions(options) {
return { type: SET_FIELD_OPTIONS, options };
}
export function clearAllOpts() {
return { type: CLEAR_ALL_OPTS };
}
export function setFormOpts(datasourceId, datasourceType) {
export function setDatasourceType(datasourceType) {
return { type: SET_DATASOURCE_TYPE, datasourceType };
}
export const FETCH_STARTED = 'FETCH_STARTED';
export function fetchStarted() {
return { type: FETCH_STARTED };
}
export const FETCH_SUCCEEDED = 'FETCH_SUCCEEDED';
export function fetchSucceeded() {
return { type: FETCH_SUCCEEDED };
}
export const FETCH_FAILED = 'FETCH_FAILED';
export function fetchFailed() {
return { type: FETCH_FAILED };
}
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}`];
@ -67,41 +51,15 @@ export function setFormOpts(datasourceId, datasourceType) {
$.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?
}
};
}

View File

@ -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 = {
@ -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);
}
}
@ -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>
);
}
@ -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,

View File

@ -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,
@ -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;

View File

@ -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,
@ -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)}`}>
@ -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>
);

View File

@ -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 });
},

View File

@ -10,7 +10,6 @@ export const fieldTypes = [
'TextAreaFeild',
'TextField',
];
const D3_FORMAT_DOCS = 'D3 format syntax: https://github.com/d3/d3-format';
// input choices & options
@ -708,13 +707,12 @@ export const visTypes = {
},
};
// todo: complete the choices and default keys from forms.py
export const fields = {
datasource: {
type: 'SelectField',
label: 'Datasource',
default: '',
choices: [['datasource', 'datasource']],
default: null,
choices: [],
description: '',
},
@ -729,23 +727,23 @@ export const fields = {
metrics: {
type: 'SelectMultipleSortableField',
label: 'Metrics',
choices: [[1, 1]],
default: ['todo'],
choices: [],
default: null,
description: 'One or many metrics to display',
},
order_by_cols: {
type: 'SelectMultipleSortableField',
label: 'Ordering',
choices: 'todo: order_by_choices',
choices: [],
description: 'One or many metrics to display',
},
metric: {
type: 'SelectField',
label: 'Metric',
choices: 'todo: ',
default: 'todo: ',
choices: [],
default: null,
description: 'Choose the metric',
},
@ -825,7 +823,7 @@ export const fields = {
type: 'SelectField',
label: 'YScale Interval',
choices: formatSelectOptionsForRange(1, 50),
default: '1',
default: null,
description: 'Number of steps to take between ticks when ' +
'displaying the Y scale',
},
@ -834,7 +832,7 @@ export const fields = {
type: 'CheckboxField',
label: 'Stacked Bars',
default: false,
description: '',
description: null,
},
show_markers: {
@ -882,7 +880,7 @@ export const fields = {
type: 'SelectField',
label: 'Color Metric',
choices: [],
default: '',
default: null,
description: 'A metric to use for color',
},
@ -910,30 +908,30 @@ export const fields = {
columns: {
type: 'SelectMultipleSortableField',
label: 'Columns',
choices: [[1, 1]],
choices: [],
description: 'One or many fields to pivot as columns',
},
all_columns: {
type: 'SelectMultipleSortableField',
label: 'Columns',
choices: [['all_columns', 'all_columns']],
choices: [],
description: 'Columns to display',
},
all_columns_x: {
type: 'SelectField',
label: 'X',
choices: [['all_columns_x', 'all_columns_x']],
default: '',
choices: [],
default: null,
description: 'Columns to display',
},
all_columns_y: {
type: 'SelectField',
label: 'Y',
choices: [['all_columns_y', 'all_columns_y']],
default: '',
choices: [],
default: null,
description: 'Columns to display',
},
@ -944,7 +942,7 @@ export const fields = {
['', 'default'],
['now', 'now'],
],
default: '',
default: null,
description: 'Defines the origin where time buckets start, ' +
'accepts natural dates as in `now`, `sunday` or `1970-01-01`',
},
@ -971,6 +969,10 @@ export const fields = {
'6 hour',
'1 day',
'7 days',
'week',
'week_starting_sunday',
'week_ending_saturday',
'month',
]),
description: 'The time granularity for the visualization. Note that you ' +
'can type and use simple natural language as in `10 seconds`, ' +
@ -1024,8 +1026,8 @@ export const fields = {
granularity_sqla: {
type: 'SelectField',
label: 'Time Column',
default: 'granularity_sqla',
choices: [['granularity_sqla', 'granularity_sqla']],
default: null,
choices: [],
description: 'The time column for the visualization. Note that you ' +
'can define arbitrary expression that return a DATETIME ' +
'column in the table or. Also note that the ' +
@ -1035,7 +1037,7 @@ export const fields = {
time_grain: {
label: 'Time Grain',
choices: ['grains-choices'],
choices: [],
default: 'Time Column',
description: 'The time granularity for the visualization. This ' +
'applies a date transformation to alter ' +
@ -1047,7 +1049,7 @@ export const fields = {
resample_rule: {
type: 'FreeFormSelectField',
label: 'Resample Rule',
default: '',
default: null,
choices: formatSelectOptions(['', '1T', '1H', '1D', '7D', '1M', '1AS']),
description: 'Pandas resample rule',
},
@ -1055,7 +1057,7 @@ export const fields = {
resample_how: {
type: 'SelectField',
label: 'Resample How',
default: '',
default: null,
choices: formatSelectOptions(['', 'mean', 'sum', 'median']),
description: 'Pandas resample how',
},
@ -1063,7 +1065,7 @@ export const fields = {
resample_fillmethod: {
type: 'SelectField',
label: 'Resample Fill Method',
default: '',
default: null,
choices: formatSelectOptions(['', 'ffill', 'bfill']),
description: 'Pandas resample fill method',
},
@ -1129,7 +1131,7 @@ export const fields = {
number_format: {
type: 'SelectField',
label: 'Number format',
default: '.3s',
default: D3_TIME_FORMAT_OPTIONS[0],
choices: D3_TIME_FORMAT_OPTIONS,
description: D3_FORMAT_DOCS,
},
@ -1137,7 +1139,7 @@ export const fields = {
row_limit: {
type: 'SelectField',
label: 'Row limit',
default: '',
default: null,
choices: formatSelectOptions(ROW_LIMIT_OPTIONS),
},
@ -1152,8 +1154,8 @@ export const fields = {
timeseries_limit_metric: {
type: 'SelectField',
label: 'Sort By',
choices: [['', ''], ['timeseries_limit_metric', 'timeseries_limit_metric']],
default: '',
choices: [],
default: null,
description: 'Metric used to define the top series',
},
@ -1169,7 +1171,7 @@ export const fields = {
rolling_periods: {
type: 'IntegerField',
label: 'Periods',
validators: ['todo: [validators.optional()]'],
validators: [],
description: 'Defines the size of the rolling window function, ' +
'relative to the time granularity selected',
},
@ -1177,8 +1179,8 @@ export const fields = {
series: {
type: 'SelectField',
label: 'Series',
choices: formatSelectOptions(['', 'series']),
default: '',
choices: [],
default: null,
description: 'Defines the grouping of entities. ' +
'Each series is shown as a specific color on the chart and ' +
'has a legend toggle',
@ -1187,32 +1189,32 @@ export const fields = {
entity: {
type: 'SelectField',
label: 'Entity',
choices: formatSelectOptions(['', 'entity']),
default: '',
choices: [],
default: null,
description: 'This define the element to be plotted on the chart',
},
x: {
type: 'SelectField',
label: 'X Axis',
choices: formatSelectOptions(['', 'metrics assigned to x']),
default: '',
choices: [],
default: null,
description: 'Metric assigned to the [X] axis',
},
y: {
type: 'SelectField',
label: 'Y Axis',
choices: formatSelectOptions(['', 'metrics assigned to x']),
default: '',
choices: [],
default: null,
description: 'Metric assigned to the [Y] axis',
},
size: {
type: 'SelectField',
label: 'Bubble Size',
default: '',
choices: formatSelectOptions(['', 'bubble-size']),
default: null,
choices: [],
},
url: {
@ -1271,7 +1273,7 @@ export const fields = {
type: 'SelectField',
label: 'Table Timestamp Format',
default: 'smart_date',
choices: formatSelectOptions(TIME_STAMP_OPTIONS),
choices: TIME_STAMP_OPTIONS,
description: 'Timestamp Format',
},
@ -1287,7 +1289,7 @@ export const fields = {
type: 'SelectCustomMultiField',
label: 'X axis format',
default: 'smart_date',
choices: formatSelectOptions(TIME_STAMP_OPTIONS),
choices: TIME_STAMP_OPTIONS,
description: D3_FORMAT_DOCS,
},
@ -1477,7 +1479,7 @@ export const fields = {
type: 'IntegerField',
label: 'Period Ratio',
default: '',
validators: 'todo: [validators.optional()]',
validators: [],
description: '[integer] Number of period to compare against, ' +
'this is relative to the granularity selected',
},
@ -1494,7 +1496,7 @@ export const fields = {
time_compare: {
type: 'TextField',
label: 'Time Shift',
default: '',
default: null,
description: 'Overlay a timeseries from a ' +
'relative time period. Expects relative time delta ' +
'in natural language (example: 24 hours, 7 days, ' +
@ -1510,7 +1512,7 @@ export const fields = {
mapbox_label: {
type: 'SelectMultipleSortableField',
label: 'label',
choices: "todo: formatSelectOptions(['count'] + datasource.column_names)",
choices: [],
description: '`count` is COUNT(*) if a group by is used. ' +
'Numerical columns will be aggregated with the aggregator. ' +
'Non-numerical columns will be used to label points. ' +
@ -1555,8 +1557,8 @@ export const fields = {
point_radius: {
type: 'SelectField',
label: 'Point Radius',
default: 'Auto',
choices: "todo: formatSelectOptions(['Auto'] + datasource.column_names)",
default: null,
choices: [],
description: 'The radius of individual points (ones that are not in a cluster). ' +
'Either a numerical column or `Auto`, which scales the point based ' +
'on the largest cluster',
@ -1582,7 +1584,7 @@ export const fields = {
type: 'IntegerField',
label: 'Zoom',
default: 11,
validators: 'todo: [validators.optional()]',
validators: [],
description: 'Zoom level of the map',
places: 8,
},
@ -1646,25 +1648,10 @@ export const defaultViz = {
};
export const initialState = {
isDatasourceMetaLoading: false,
datasources: null,
datasource_id: null,
datasource_type: null,
timeColumnOpts: [],
timeGrainOpts: [],
groupByColumnOpts: [],
metricsOpts: [],
columnOpts: [],
orderingOpts: [],
filterColumnOpts: [],
fields,
viz: defaultViz,
};
export const defaultOpts = {
timeColumnOpts: [],
timeGrainOpts: [],
groupByColumnOpts: [],
metricsOpts: [],
filterColumnOpts: [],
columnOpts: [],
orderingOpts: [],
};

View File

@ -13,7 +13,7 @@ const defaultProps = {
datasource_id: 1,
datasource_type: 'type',
actions: {
setFormOpts: () => {
fetchFieldOptions: () => {
// noop
},
},

View File

@ -8,6 +8,7 @@ import { shallow } from 'enzyme';
import SelectField from '../../../../javascripts/explorev2/components/SelectField';
const defaultProps = {
choices: [[10, 10], [20, 20]],
name: 'row_limit',
label: 'Row Limit',
onChange: sinon.spy(),

View File

@ -2106,6 +2106,9 @@ class Caravel(BaseCaravelView):
.first()
)
datasources = db.session.query(datasource_class).all()
datasources = sorted(datasources, key=lambda ds: ds.full_name)
# Check if datasource exists
if not datasource:
return json_error_response(DATASOURCE_MISSING_ERR)
@ -2113,23 +2116,38 @@ class Caravel(BaseCaravelView):
if not self.datasource_access(datasource):
return json_error_response(DATASOURCE_ACCESS_ERR)
gb_cols = [(col, col) for col in datasource.groupby_column_names]
order_by_choices = []
for s in sorted(datasource.num_cols):
order_by_choices.append(s + ' [asc]')
order_by_choices.append(s + ' [desc]')
column_opts = {
"groupby_cols": datasource.groupby_column_names,
"metrics": datasource.metrics_combo,
"filter_cols": datasource.filterable_column_names,
"columns": datasource.column_names,
"ordering_cols": order_by_choices
order_by_choices.append((json.dumps([s, True]), s + ' [asc]'))
order_by_choices.append((json.dumps([s, False]), s + ' [desc]'))
field_options = {
'datasource': [(d.id, d.full_name) for d in datasources],
'metrics': datasource.metrics_combo,
'order_by_cols': order_by_choices,
'metric': datasource.metrics_combo,
'secondary_metric': datasource.metrics_combo,
'groupby': gb_cols,
'columns': gb_cols,
'all_columns': datasource.column_names,
'all_columns_x': datasource.column_names,
'all_columns_y': datasource.column_names,
'granularity_sqla': datasource.dttm_cols,
'timeseries_limit_metric': [('', '')] + datasource.metrics_combo,
'series': gb_cols,
'entity': gb_cols,
'x': datasource.metrics_combo,
'y': datasource.metrics_combo,
'size': datasource.metrics_combo,
'mapbox_label': datasource.column_names,
'point_radius': ["Auto"] + datasource.column_names,
}
form_data = dict(
column_opts.items() + datasource.time_column_grains.items()
)
return Response(
json.dumps(form_data), mimetype="application/json")
json.dumps({'field_options': field_options}),
mimetype="application/json"
)
@has_access
@expose("/queries/<last_updated_ms>")

View File

@ -317,6 +317,7 @@ class BaseViz(object):
if not payload:
is_cached = False
cache_timeout = self.cache_timeout
payload = {
'cache_timeout': cache_timeout,
'cache_key': cache_key,

View File

@ -387,6 +387,11 @@ class CoreTests(CaravelTestCase):
elif backend == 'postgresql':
self.assertEqual(len(data.get('indexes')), 5)
def test_fetch_datasource_metadata(self):
self.login(username='admin')
url = '/caravel/fetch_datasource_metadata?datasource_type=table&datasource_id=1';
resp = json.loads(self.get_resp(url))
self.assertEqual(len(resp['field_options']), 19)
if __name__ == '__main__':