mirror of https://github.com/apache/superset.git
[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:
parent
4530047c76
commit
51c0470f0b
|
@ -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?
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
|
|
|
@ -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 });
|
||||
},
|
||||
|
|
|
@ -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: [],
|
||||
};
|
||||
|
|
|
@ -13,7 +13,7 @@ const defaultProps = {
|
|||
datasource_id: 1,
|
||||
datasource_type: 'type',
|
||||
actions: {
|
||||
setFormOpts: () => {
|
||||
fetchFieldOptions: () => {
|
||||
// noop
|
||||
},
|
||||
},
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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>")
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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__':
|
||||
|
|
Loading…
Reference in New Issue