diff --git a/superset/assets/javascripts/explore/components/EmbedCodeButton.jsx b/superset/assets/javascripts/explore/components/EmbedCodeButton.jsx
index 3582229dd80..3efe2bba29d 100644
--- a/superset/assets/javascripts/explore/components/EmbedCodeButton.jsx
+++ b/superset/assets/javascripts/explore/components/EmbedCodeButton.jsx
@@ -35,7 +35,8 @@ export default class EmbedCodeButton extends React.Component {
` width="${this.state.width}"\n` +
` height="${this.state.height}"\n` +
' seamless\n' +
- ' frameBorder="0" scrolling="no"\n' +
+ ' frameBorder="0"\n' +
+ ' scrolling="no"\n' +
` src="${srcLink}"\n` +
'>\n' +
''
diff --git a/superset/assets/javascripts/explorev2/actions/exploreActions.js b/superset/assets/javascripts/explorev2/actions/exploreActions.js
index b5e89a7840b..5373ef5d3b8 100644
--- a/superset/assets/javascripts/explorev2/actions/exploreActions.js
+++ b/superset/assets/javascripts/explorev2/actions/exploreActions.js
@@ -2,16 +2,16 @@
const $ = window.$ = require('jquery');
const FAVESTAR_BASE_URL = '/superset/favstar/slice';
-export const SET_FIELD_OPTIONS = 'SET_FIELD_OPTIONS';
-export function setFieldOptions(options) {
- return { type: SET_FIELD_OPTIONS, options };
-}
-
export const SET_DATASOURCE_TYPE = 'SET_DATASOURCE_TYPE';
export function setDatasourceType(datasourceType) {
return { type: SET_DATASOURCE_TYPE, datasourceType };
}
+export const SET_DATASOURCE = 'SET_DATASOURCE';
+export function setDatasource(datasource) {
+ return { type: SET_DATASOURCE, datasource };
+}
+
export const FETCH_STARTED = 'FETCH_STARTED';
export function fetchStarted() {
return { type: FETCH_STARTED };
@@ -27,7 +27,7 @@ export function fetchFailed(error) {
return { type: FETCH_FAILED, error };
}
-export function fetchFieldOptions(datasourceId, datasourceType) {
+export function fetchDatasourceMetadata(datasourceId, datasourceType) {
return function (dispatch) {
dispatch(fetchStarted());
@@ -38,7 +38,7 @@ export function fetchFieldOptions(datasourceId, datasourceType) {
type: 'GET',
url,
success: (data) => {
- dispatch(setFieldOptions(data.field_options));
+ dispatch(setDatasource(data));
dispatch(fetchSucceeded());
},
error(error) {
diff --git a/superset/assets/javascripts/explorev2/components/ControlPanelsContainer.jsx b/superset/assets/javascripts/explorev2/components/ControlPanelsContainer.jsx
index 02adb78a3c3..c4f11ad435e 100644
--- a/superset/assets/javascripts/explorev2/components/ControlPanelsContainer.jsx
+++ b/superset/assets/javascripts/explorev2/components/ControlPanelsContainer.jsx
@@ -7,6 +7,7 @@ import { Panel, Alert } from 'react-bootstrap';
import visTypes, { sectionsToRender, commonControlPanelSections } from '../stores/visTypes';
import ControlPanelSection from './ControlPanelSection';
import FieldSetRow from './FieldSetRow';
+import FieldSet from './FieldSet';
import Filters from './Filters';
const propTypes = {
@@ -17,48 +18,65 @@ const propTypes = {
form_data: PropTypes.object.isRequired,
y_axis_zero: PropTypes.any,
alert: PropTypes.string,
+ exploreState: PropTypes.object.isRequired,
};
class ControlPanelsContainer extends React.Component {
+ constructor(props) {
+ super(props);
+ this.fieldOverrides = this.fieldOverrides.bind(this);
+ this.getFieldData = this.getFieldData.bind(this);
+ this.removeAlert = this.removeAlert.bind(this);
+ this.onChange = this.onChange.bind(this);
+ }
componentWillMount() {
const datasource_id = this.props.form_data.datasource;
const datasource_type = this.props.datasource_type;
if (datasource_id) {
- this.props.actions.fetchFieldOptions(datasource_id, datasource_type);
+ this.props.actions.fetchDatasourceMetadata(datasource_id, datasource_type);
}
}
-
componentWillReceiveProps(nextProps) {
if (nextProps.form_data.datasource !== this.props.form_data.datasource) {
if (nextProps.form_data.datasource) {
- this.props.actions.fetchFieldOptions(
+ this.props.actions.fetchDatasourceMetadata(
nextProps.form_data.datasource, nextProps.datasource_type);
}
}
}
-
- onChange(name, value, label) {
- this.props.actions.setFieldValue(this.props.datasource_type, name, value, label);
+ onChange(name, value) {
+ this.props.actions.setFieldValue(this.props.datasource_type, name, value);
+ }
+ getFieldData(fs) {
+ const fieldOverrides = this.fieldOverrides();
+ if (!this.props.fields) {
+ return null;
+ }
+ let fieldData = this.props.fields[fs] || {};
+ if (fieldOverrides.hasOwnProperty(fs)) {
+ const overrideData = fieldOverrides[fs];
+ fieldData = Object.assign({}, fieldData, overrideData);
+ }
+ if (fieldData.mapStateToProps) {
+ Object.assign(fieldData, fieldData.mapStateToProps(this.props.exploreState));
+ }
+ return fieldData;
}
-
sectionsToRender() {
return sectionsToRender(this.props.form_data.viz_type, this.props.datasource_type);
}
-
filterSectionsToRender() {
const filterSections = this.props.datasource_type === 'table' ?
[commonControlPanelSections.filters[0]] : commonControlPanelSections.filters;
return filterSections;
}
-
fieldOverrides() {
const viz = visTypes[this.props.form_data.viz_type];
- return viz.fieldOverrides;
+ return viz.fieldOverrides || {};
}
removeAlert() {
this.props.actions.removeControlPanelAlert();
}
-
render() {
return (
@@ -68,7 +86,7 @@ class ControlPanelsContainer extends React.Component {
{this.props.alert}
@@ -81,12 +99,16 @@ class ControlPanelsContainer extends React.Component {
>
{section.fieldSetRows.map((fieldSets, i) => (
(
+
+ ))}
/>
))}
@@ -119,6 +141,7 @@ function mapStateToProps(state) {
alert: state.controlPanelAlert,
isDatasourceMetaLoading: state.isDatasourceMetaLoading,
fields: state.fields,
+ exploreState: state,
};
}
diff --git a/superset/assets/javascripts/explorev2/components/FieldSetRow.jsx b/superset/assets/javascripts/explorev2/components/FieldSetRow.jsx
index dbdaca72039..625c8761e3f 100644
--- a/superset/assets/javascripts/explorev2/components/FieldSetRow.jsx
+++ b/superset/assets/javascripts/explorev2/components/FieldSetRow.jsx
@@ -1,52 +1,23 @@
import React, { PropTypes } from 'react';
-import FieldSet from './FieldSet';
const NUM_COLUMNS = 12;
const propTypes = {
- fields: PropTypes.object.isRequired,
- fieldSets: PropTypes.array.isRequired,
- fieldOverrides: PropTypes.object,
- onChange: PropTypes.func,
- form_data: PropTypes.object.isRequired,
+ fields: PropTypes.arrayOf(PropTypes.object).isRequired,
};
-const defaultProps = {
- fieldOverrides: {},
- onChange: () => {},
-};
-
-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;
- }
- render() {
- const colSize = NUM_COLUMNS / this.props.fieldSets.length;
- return (
-
- {this.props.fieldSets.map((fs) => {
- const fieldData = this.getFieldData(fs);
- return (
-
-
-
- );
- })}
-
- );
- }
+function FieldSetRow(props) {
+ const colSize = NUM_COLUMNS / props.fields.length;
+ return (
+
+ {props.fields.map((field, i) => (
+
+ {field}
+
+ ))}
+
+ );
}
FieldSetRow.propTypes = propTypes;
-FieldSetRow.defaultProps = defaultProps;
+export default FieldSetRow;
diff --git a/superset/assets/javascripts/explorev2/reducers/exploreReducer.js b/superset/assets/javascripts/explorev2/reducers/exploreReducer.js
index 09a057e4a0a..460544ab354 100644
--- a/superset/assets/javascripts/explorev2/reducers/exploreReducer.js
+++ b/superset/assets/javascripts/explorev2/reducers/exploreReducer.js
@@ -39,25 +39,9 @@ export const exploreReducer = function (state, action) {
return Object.assign({}, state,
{ saveModalAlert: `fetching dashboards failed for ${action.userId}` });
},
-
- [actions.SET_FIELD_OPTIONS]() {
- const newState = Object.assign({}, state);
- const optionsByFieldName = action.options;
- const fieldNames = Object.keys(optionsByFieldName);
-
- fieldNames.forEach((fieldName) => {
- if (fieldName === 'filterable_cols') {
- newState.filterColumnOpts = optionsByFieldName[fieldName];
- } else {
- newState.fields[fieldName].choices = optionsByFieldName[fieldName];
- if (fieldName === 'metric' && state.viz.form_data.viz_type === 'dual_line') {
- newState.fields.metric_2.choices = optionsByFieldName[fieldName];
- }
- }
- });
- return Object.assign({}, state, newState);
+ [actions.SET_DATASOURCE]() {
+ return Object.assign({}, state, { datasource: action.datasource });
},
-
[actions.SET_FILTER_COLUMN_OPTS]() {
return Object.assign({}, state, { filterColumnOpts: action.filterColumnOpts });
},
diff --git a/superset/assets/javascripts/explorev2/stores/fields.js b/superset/assets/javascripts/explorev2/stores/fields.js
index cbe195482c1..0866f090d62 100644
--- a/superset/assets/javascripts/explorev2/stores/fields.js
+++ b/superset/assets/javascripts/explorev2/stores/fields.js
@@ -31,13 +31,15 @@ export const fields = {
label: 'Datasource',
clearable: false,
default: null,
- choices: [],
+ mapStateToProps: (state) => ({
+ choices: state.datasources || [],
+ }),
description: '',
},
viz_type: {
type: 'SelectField',
- label: 'Viz',
+ label: 'Visualization Type',
clearable: false,
default: 'table',
choices: formatSelectOptions(Object.keys(visTypes)),
@@ -47,9 +49,10 @@ export const fields = {
metrics: {
type: 'SelectField',
multi: true,
- freeForm: false,
label: 'Metrics',
- choices: [],
+ mapStateToProps: (state) => ({
+ choices: (state.datasource) ? state.datasource.metrics_combo : [],
+ }),
default: [],
description: 'One or many metrics to display',
},
@@ -57,19 +60,22 @@ export const fields = {
order_by_cols: {
type: 'SelectField',
multi: true,
- freeForm: false,
label: 'Ordering',
- choices: [],
default: [],
description: 'One or many metrics to display',
+ mapStateToProps: (state) => ({
+ choices: (state.datasource) ? state.datasource.order_by_choices : [],
+ }),
},
metric: {
type: 'SelectField',
label: 'Metric',
- choices: [],
default: null,
description: 'Choose the metric',
+ mapStateToProps: (state) => ({
+ choices: (state.datasource) ? state.datasource.metrics_combo : [],
+ }),
},
metric_2: {
@@ -219,9 +225,11 @@ export const fields = {
secondary_metric: {
type: 'SelectField',
label: 'Color Metric',
- choices: [],
default: null,
description: 'A metric to use for color',
+ mapStateToProps: (state) => ({
+ choices: (state.datasource) ? state.datasource.metrics_combo : [],
+ }),
},
country_fieldtype: {
@@ -241,17 +249,17 @@ export const fields = {
groupby: {
type: 'SelectField',
multi: true,
- freeForm: false,
label: 'Group by',
- choices: [],
default: [],
description: 'One or many fields to group by',
+ mapStateToProps: (state) => ({
+ choices: (state.datasource) ? state.datasource.gb_cols : [],
+ }),
},
columns: {
type: 'SelectField',
multi: true,
- freeForm: false,
label: 'Columns',
choices: [],
default: [],
@@ -261,32 +269,36 @@ export const fields = {
all_columns: {
type: 'SelectField',
multi: true,
- freeForm: false,
label: 'Columns',
- choices: [],
default: [],
description: 'Columns to display',
+ mapStateToProps: (state) => ({
+ choices: (state.datasource) ? state.datasource.all_cols : [],
+ }),
},
all_columns_x: {
type: 'SelectField',
label: 'X',
- choices: [],
default: null,
description: 'Columns to display',
+ mapStateToProps: (state) => ({
+ choices: (state.datasource) ? state.datasource.all_cols : [],
+ }),
},
all_columns_y: {
type: 'SelectField',
label: 'Y',
- choices: [],
default: null,
description: 'Columns to display',
+ mapStateToProps: (state) => ({
+ choices: (state.datasource) ? state.datasource.all_cols : [],
+ }),
},
druid_time_origin: {
type: 'SelectField',
- multi: false,
freeForm: true,
label: 'Origin',
choices: [
@@ -300,7 +312,6 @@ export const fields = {
bottom_margin: {
type: 'SelectField',
- multi: false,
freeForm: true,
label: 'Bottom Margin',
choices: formatSelectOptions(['auto', 50, 75, 100, 125, 150, 200]),
@@ -310,7 +321,6 @@ export const fields = {
granularity: {
type: 'SelectField',
- multi: false,
freeForm: true,
label: 'Time Granularity',
default: 'one day',
@@ -353,7 +363,6 @@ export const fields = {
link_length: {
type: 'SelectField',
- multi: false,
freeForm: true,
label: 'Link Length',
default: '200',
@@ -363,7 +372,6 @@ export const fields = {
charge: {
type: 'SelectField',
- multi: false,
freeForm: true,
label: 'Charge',
default: '-500',
@@ -386,29 +394,32 @@ export const fields = {
type: 'SelectField',
label: 'Time Column',
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 ' +
'filter below is applied against this column or ' +
'expression',
+ mapStateToProps: (state) => ({
+ choices: (state.datasource) ? state.datasource.all_cols : [],
+ }),
},
time_grain_sqla: {
type: 'SelectField',
label: 'Time Grain',
- choices: [],
default: 'Time Column',
description: 'The time granularity for the visualization. This ' +
'applies a date transformation to alter ' +
'your time column and defines a new time granularity. ' +
'The options here are defined on a per database ' +
'engine basis in the Superset source code.',
+ mapStateToProps: (state) => ({
+ choices: (state.datasource) ? state.datasource.time_grain_sqla : [],
+ }),
},
resample_rule: {
type: 'SelectField',
- multi: false,
freeForm: true,
label: 'Resample Rule',
default: null,
@@ -418,7 +429,6 @@ export const fields = {
resample_how: {
type: 'SelectField',
- multi: false,
freeForm: true,
label: 'Resample How',
default: null,
@@ -428,7 +438,6 @@ export const fields = {
resample_fillmethod: {
type: 'SelectField',
- multi: false,
freeForm: true,
label: 'Resample Fill Method',
default: null,
@@ -438,7 +447,6 @@ export const fields = {
since: {
type: 'SelectField',
- multi: false,
freeForm: true,
label: 'Since',
default: '7 days ago',
@@ -458,7 +466,6 @@ export const fields = {
until: {
type: 'SelectField',
- multi: false,
freeForm: true,
label: 'Until',
default: 'now',
@@ -474,7 +481,6 @@ export const fields = {
max_bubble_size: {
type: 'SelectField',
- multi: false,
freeForm: true,
label: 'Max Bubble Size',
default: '25',
@@ -483,7 +489,6 @@ export const fields = {
whisker_options: {
type: 'SelectField',
- multi: false,
freeForm: true,
label: 'Whisker/outlier options',
default: 'Tukey',
@@ -497,7 +502,7 @@ export const fields = {
},
treemap_ratio: {
- type: 'IntegerField',
+ type: 'TextField',
label: 'Ratio',
default: 0.5 * (1 + Math.sqrt(5)), // d3 default, golden ratio
description: 'Target aspect ratio for treemap tiles.',
@@ -505,7 +510,6 @@ export const fields = {
number_format: {
type: 'SelectField',
- multi: false,
freeForm: true,
label: 'Number format',
default: D3_TIME_FORMAT_OPTIONS[0],
@@ -515,7 +519,6 @@ export const fields = {
row_limit: {
type: 'SelectField',
- multi: false,
freeForm: true,
label: 'Row limit',
default: null,
@@ -524,7 +527,6 @@ export const fields = {
limit: {
type: 'SelectField',
- multi: false,
freeForm: true,
label: 'Series limit',
choices: formatSelectOptions(SERIES_LIMITS),
@@ -535,9 +537,11 @@ export const fields = {
timeseries_limit_metric: {
type: 'SelectField',
label: 'Sort By',
- choices: [],
default: null,
description: 'Metric used to define the top series',
+ mapStateToProps: (state) => ({
+ choices: (state.datasource) ? state.datasource.metrics_combo : [],
+ }),
},
rolling_type: {
@@ -550,7 +554,7 @@ export const fields = {
},
rolling_periods: {
- type: 'IntegerField',
+ type: 'TextField',
label: 'Periods',
validators: [],
description: 'Defines the size of the rolling window function, ' +
@@ -560,42 +564,52 @@ export const fields = {
series: {
type: 'SelectField',
label: 'Series',
- 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',
+ mapStateToProps: (state) => ({
+ choices: (state.datasource) ? state.datasource.gb_cols : [],
+ }),
},
entity: {
type: 'SelectField',
label: 'Entity',
- choices: [],
default: null,
description: 'This define the element to be plotted on the chart',
+ mapStateToProps: (state) => ({
+ choices: (state.datasource) ? state.datasource.gb_cols : [],
+ }),
},
x: {
type: 'SelectField',
label: 'X Axis',
- choices: [],
default: null,
description: 'Metric assigned to the [X] axis',
+ mapStateToProps: (state) => ({
+ choices: (state.datasource) ? state.datasource.gb_cols : [],
+ }),
},
y: {
type: 'SelectField',
label: 'Y Axis',
- choices: [],
default: null,
description: 'Metric assigned to the [Y] axis',
+ mapStateToProps: (state) => ({
+ choices: (state.datasource) ? state.datasource.metrics_combo : [],
+ }),
},
size: {
type: 'SelectField',
label: 'Bubble Size',
default: null,
- choices: [],
+ mapStateToProps: (state) => ({
+ choices: (state.datasource) ? state.datasource.metrics_combo : [],
+ }),
},
url: {
@@ -652,7 +666,6 @@ export const fields = {
table_timestamp_format: {
type: 'SelectField',
- multi: false,
freeForm: true,
label: 'Table Timestamp Format',
default: 'smart_date',
@@ -662,7 +675,6 @@ export const fields = {
series_height: {
type: 'SelectField',
- multi: false,
freeForm: true,
label: 'Series Height',
default: '25',
@@ -672,7 +684,6 @@ export const fields = {
page_length: {
type: 'SelectField',
- multi: false,
freeForm: true,
label: 'Page Length',
default: 0,
@@ -682,7 +693,6 @@ export const fields = {
x_axis_format: {
type: 'SelectField',
- multi: false,
freeForm: true,
label: 'X axis format',
default: 'smart_date',
@@ -692,7 +702,6 @@ export const fields = {
y_axis_format: {
type: 'SelectField',
- multi: false,
freeForm: true,
label: 'Y axis format',
default: '.3s',
@@ -890,7 +899,7 @@ export const fields = {
},
num_period_compare: {
- type: 'IntegerField',
+ type: 'TextField',
label: 'Period Ratio',
default: '',
validators: [],
@@ -926,14 +935,15 @@ export const fields = {
mapbox_label: {
type: 'SelectField',
multi: true,
- freeForm: false,
label: 'label',
- choices: [],
default: [],
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. ' +
'Leave empty to get a count of points in each cluster.',
+ mapStateToProps: (state) => ({
+ choices: (state.datasource) ? state.datasource.all_cols : [],
+ }),
},
mapbox_style: {
@@ -953,7 +963,6 @@ export const fields = {
clustering_radius: {
type: 'SelectField',
- multi: false,
freeForm: true,
label: 'Clustering Radius',
default: '60',
@@ -977,10 +986,12 @@ export const fields = {
type: 'SelectField',
label: 'Point Radius',
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',
+ mapStateToProps: (state) => ({
+ choices: state.fields.point_radius.choices,
+ }),
},
point_radius_unit: {
@@ -992,7 +1003,7 @@ export const fields = {
},
global_opacity: {
- type: 'IntegerField',
+ type: 'TextField',
label: 'Opacity',
default: 1,
description: 'Opacity of all clusters, points, and labels. ' +
@@ -1000,7 +1011,7 @@ export const fields = {
},
viewport_zoom: {
- type: 'IntegerField',
+ type: 'TextField',
label: 'Zoom',
default: 11,
validators: [],
@@ -1009,7 +1020,7 @@ export const fields = {
},
viewport_latitude: {
- type: 'IntegerField',
+ type: 'TextField',
label: 'Default latitude',
default: 37.772123,
description: 'Latitude of default viewport',
@@ -1017,7 +1028,7 @@ export const fields = {
},
viewport_longitude: {
- type: 'IntegerField',
+ type: 'TextField',
label: 'Default longitude',
default: -122.405293,
description: 'Longitude of default viewport',
@@ -1033,7 +1044,6 @@ export const fields = {
mapbox_color: {
type: 'SelectField',
- multi: false,
freeForm: true,
label: 'RGB Color',
default: 'rgb(0, 122, 135)',
diff --git a/superset/assets/spec/javascripts/explore/components/EmbedCodeButton_spec.jsx b/superset/assets/spec/javascripts/explore/components/EmbedCodeButton_spec.jsx
index a171a9a1415..837269b9353 100644
--- a/superset/assets/spec/javascripts/explore/components/EmbedCodeButton_spec.jsx
+++ b/superset/assets/spec/javascripts/explore/components/EmbedCodeButton_spec.jsx
@@ -31,13 +31,17 @@ describe('EmbedCodeButton', () => {
width: '2000',
srcLink: 'http://localhost/endpoint_url',
});
- const embedHTML = `
- `;
+ const embedHTML = (
+ ''
+ );
expect(wrapper.instance().generateEmbedHTML()).to.equal(embedHTML);
});
});
diff --git a/superset/assets/spec/javascripts/explorev2/components/FieldSetRow_spec.js b/superset/assets/spec/javascripts/explorev2/components/FieldSetRow_spec.js
index 4dcf91b366f..6db65c722cb 100644
--- a/superset/assets/spec/javascripts/explorev2/components/FieldSetRow_spec.js
+++ b/superset/assets/spec/javascripts/explorev2/components/FieldSetRow_spec.js
@@ -1,31 +1,18 @@
import React from 'react';
import { expect } from 'chai';
-import { describe, it, beforeEach } from 'mocha';
+import { describe, it } from 'mocha';
import { shallow } from 'enzyme';
-import { fields } from '../../../../javascripts/explorev2/stores/fields';
-import { defaultFormData } from '../../../../javascripts/explorev2/stores/store';
import FieldSetRow from '../../../../javascripts/explorev2/components/FieldSetRow';
-import FieldSet from '../../../../javascripts/explorev2/components/FieldSet';
-
-const defaultProps = {
- fields,
- fieldSets: ['columns', 'metrics'],
- form_data: defaultFormData(),
-};
describe('FieldSetRow', () => {
- let wrapper;
-
- beforeEach(() => {
- wrapper = shallow();
- });
-
- it('renders a single row element', () => {
+ it('renders a single row with one element', () => {
+ const wrapper = shallow(]} />);
expect(wrapper.find('.row')).to.have.lengthOf(1);
+ expect(wrapper.find('.row').find('a')).to.have.lengthOf(1);
});
-
- it('renders a FieldSet for each item in fieldSets array', () => {
- const length = defaultProps.fieldSets.length;
- expect(wrapper.find(FieldSet)).to.have.lengthOf(length);
+ it('renders a single row with two elements', () => {
+ const wrapper = shallow(, ]} />);
+ expect(wrapper.find('.row')).to.have.lengthOf(1);
+ expect(wrapper.find('.row').find('a')).to.have.lengthOf(2);
});
});
diff --git a/superset/models.py b/superset/models.py
index f14cf6d5162..84701a17db2 100644
--- a/superset/models.py
+++ b/superset/models.py
@@ -671,6 +671,34 @@ class Queryable(object):
else:
return "/superset/explore/{obj.type}/{obj.id}/".format(obj=self)
+ @property
+ def data(self):
+ """data representation of the datasource sent to the frontend"""
+ gb_cols = [(col, col) for col in self.groupby_column_names]
+ all_cols = [(c, c) for c in self.column_names]
+ order_by_choices = []
+ for s in sorted(self.column_names):
+ order_by_choices.append((json.dumps([s, True]), s + ' [asc]'))
+ order_by_choices.append((json.dumps([s, False]), s + ' [desc]'))
+
+ d = {
+ 'id': self.id,
+ 'type': self.type,
+ 'name': self.name,
+ 'metrics_combo': self.metrics_combo,
+ 'order_by_choices': order_by_choices,
+ 'gb_cols': gb_cols,
+ 'all_cols': all_cols,
+ 'filterable_cols': self.filterable_column_names,
+ }
+ if (self.type == 'table'):
+ grains = self.database.grains() or []
+ if grains:
+ grains = [(g.name, g.name) for g in grains]
+ d['granularity_sqla'] = [(c, c) for c in self.dttm_cols]
+ d['time_grain_sqla'] = grains
+ return d
+
class Database(Model, AuditMixinNullable):
diff --git a/superset/views.py b/superset/views.py
index 87bcad686c2..fb8d6faa628 100755
--- a/superset/views.py
+++ b/superset/views.py
@@ -2546,65 +2546,24 @@ class Superset(BaseSupersetView):
@expose("/fetch_datasource_metadata")
@log_this
def fetch_datasource_metadata(self):
- session = db.session
datasource_type = request.args.get('datasource_type')
datasource_class = SourceRegistry.sources[datasource_type]
datasource = (
- session.query(datasource_class)
+ db.session.query(datasource_class)
.filter_by(id=request.args.get('datasource_id'))
.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)
+
# Check permission for datasource
if not self.datasource_access(datasource):
return json_error_response(DATASOURCE_ACCESS_ERR)
- gb_cols = [(col, col) for col in datasource.groupby_column_names]
- all_cols = [(c, c) for c in datasource.column_names]
- order_by_choices = []
- for s in sorted(datasource.column_names):
- 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': all_cols,
- 'all_columns_x': all_cols,
- 'all_columns_y': all_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': all_cols,
- 'point_radius': [(c, c) for c in (["Auto"] + datasource.column_names)],
- 'filterable_cols': datasource.filterable_column_names,
- }
-
- if (datasource_type == 'table'):
- grains = datasource.database.grains()
- grain_choices = []
- if grains:
- grain_choices = [(grain.name, grain.name) for grain in grains]
- field_options['granularity_sqla'] = \
- [(c, c) for c in datasource.dttm_cols]
- field_options['time_grain_sqla'] = grain_choices
-
return Response(
- json.dumps({'field_options': field_options}),
+ json.dumps(datasource.data),
mimetype="application/json"
)
diff --git a/tests/core_tests.py b/tests/core_tests.py
index 9c580c15ca4..44d97b88d90 100644
--- a/tests/core_tests.py
+++ b/tests/core_tests.py
@@ -486,10 +486,19 @@ class CoreTests(SupersetTestCase):
def test_fetch_datasource_metadata(self):
self.login(username='admin')
- url = '/superset/fetch_datasource_metadata?datasource_type=table&' \
- 'datasource_id=1'
- resp = json.loads(self.get_resp(url))
- self.assertEqual(len(resp['field_options']), 21)
+ url = (
+ '/superset/fetch_datasource_metadata?'
+ 'datasource_type=table&'
+ 'datasource_id=1'
+ )
+ resp = self.get_json_resp(url)
+ keys = [
+ 'name', 'filterable_cols', 'gb_cols', 'type', 'all_cols',
+ 'order_by_choices', 'metrics_combo', 'granularity_sqla',
+ 'time_grain_sqla', 'id',
+ ]
+ for k in keys:
+ self.assertIn(k, resp.keys())
def test_fetch_all_tables(self):
self.login(username='admin')