Merge branch 'lyftga'

This commit is contained in:
Maxime Beauchemin 2019-04-24 21:55:12 -07:00
commit 929fb6bbb8
14 changed files with 46 additions and 16 deletions

View File

@ -38,8 +38,8 @@ export default () => describe('top-level controls', () => {
.forEach((slice) => { .forEach((slice) => {
const sliceRequest = `getJson_${slice.slice_id}`; const sliceRequest = `getJson_${slice.slice_id}`;
sliceRequests.push(`@${sliceRequest}`); sliceRequests.push(`@${sliceRequest}`);
const formData = `{"slice_id":${slice.slice_id},"viz_type":"${slice.form_data.viz_type}"}`; const formData = `{"slice_id":${slice.slice_id}}`;
cy.route('GET', `/superset/explore_json/?form_data=${formData}`).as(sliceRequest); cy.route('POST', `/superset/explore_json/?form_data=${formData}`).as(sliceRequest);
const forceRefresh = `postJson_${slice.slice_id}_force`; const forceRefresh = `postJson_${slice.slice_id}_force`;
forceRefreshRequests.push(`@${forceRefresh}`); forceRefreshRequests.push(`@${forceRefresh}`);

View File

@ -28,9 +28,9 @@ export default () => describe('edit mode', () => {
const bootstrapData = JSON.parse(data[0].dataset.bootstrap); const bootstrapData = JSON.parse(data[0].dataset.bootstrap);
const dashboard = bootstrapData.dashboard_data; const dashboard = bootstrapData.dashboard_data;
const boxplotChartId = dashboard.slices.find(slice => (slice.form_data.viz_type === 'box_plot')).slice_id; const boxplotChartId = dashboard.slices.find(slice => (slice.form_data.viz_type === 'box_plot')).slice_id;
const formData = `{"slice_id":${boxplotChartId},"viz_type":"box_plot"}`; const formData = `{"slice_id":${boxplotChartId}}`;
const boxplotRequest = `/superset/explore_json/?form_data=${formData}`; const boxplotRequest = `/superset/explore_json/?form_data=${formData}`;
cy.route('GET', boxplotRequest).as('boxplotRequest'); cy.route('POST', boxplotRequest).as('boxplotRequest');
}); });
cy.get('.dashboard-header').contains('Edit dashboard').click(); cy.get('.dashboard-header').contains('Edit dashboard').click();

View File

@ -39,9 +39,9 @@ export default () => describe('dashboard filter', () => {
it('should apply filter', () => { it('should apply filter', () => {
const aliases = []; const aliases = [];
const formData = `{"slice_id":${filterId},"viz_type":"filter_box"}`; const formData = `{"slice_id":${filterId}}`;
const filterRoute = `/superset/explore_json/?form_data=${formData}`; const filterRoute = `/superset/explore_json/?form_data=${formData}`;
cy.route('GET', filterRoute).as('fetchFilter'); cy.route('POST', filterRoute).as('fetchFilter');
cy.wait('@fetchFilter'); cy.wait('@fetchFilter');
sliceIds sliceIds
.filter(id => (parseInt(id, 10) !== filterId)) .filter(id => (parseInt(id, 10) !== filterId))

View File

@ -34,8 +34,8 @@ export default () => describe('load', () => {
// then define routes and create alias for each requests // then define routes and create alias for each requests
slices.forEach((slice) => { slices.forEach((slice) => {
const alias = `getJson_${slice.slice_id}`; const alias = `getJson_${slice.slice_id}`;
const formData = `{"slice_id":${slice.slice_id},"viz_type":"${slice.form_data.viz_type}"}`; const formData = `{"slice_id":${slice.slice_id}}`;
cy.route('GET', `/superset/explore_json/?form_data=${formData}`).as(alias); cy.route('POST', `/superset/explore_json/?form_data=${formData}`).as(alias);
aliases.push(`@${alias}`); aliases.push(`@${alias}`);
}); });
}); });

View File

@ -56,9 +56,9 @@ export default () => describe('save', () => {
cy.wait('@copyRequest'); cy.wait('@copyRequest');
// should have box_plot chart // should have box_plot chart
const formData = `{"slice_id":${boxplotChartId},"viz_type":"box_plot"}`; const formData = `{"slice_id":${boxplotChartId}}`;
const boxplotRequest = `/superset/explore_json/?form_data=${formData}`; const boxplotRequest = `/superset/explore_json/?form_data=${formData}`;
cy.route('GET', boxplotRequest).as('boxplotRequest'); cy.route('POST', boxplotRequest).as('boxplotRequest');
cy.wait('@boxplotRequest'); cy.wait('@boxplotRequest');
cy.get('.grid-container .box_plot').should('be.exist'); cy.get('.grid-container .box_plot').should('be.exist');

View File

@ -20,6 +20,7 @@ import PropTypes from 'prop-types';
import React from 'react'; import React from 'react';
import { Alert } from 'react-bootstrap'; import { Alert } from 'react-bootstrap';
import { isFeatureEnabled, FeatureFlag } from 'src/featureFlags';
import { Logger, LOG_ACTIONS_RENDER_CHART_CONTAINER } from '../logger/LogUtils'; import { Logger, LOG_ACTIONS_RENDER_CHART_CONTAINER } from '../logger/LogUtils';
import Loading from '../components/Loading'; import Loading from '../components/Loading';
import RefreshChartOverlay from '../components/RefreshChartOverlay'; import RefreshChartOverlay from '../components/RefreshChartOverlay';
@ -70,7 +71,7 @@ class Chart extends React.PureComponent {
} }
componentDidMount() { componentDidMount() {
if (this.props.triggerQuery) { if (this.props.triggerQuery) {
if (this.props.chartId > 0) { if (this.props.chartId > 0 && isFeatureEnabled(FeatureFlag.CLIENT_CACHE)) {
// Load saved chart with a GET request // Load saved chart with a GET request
this.props.actions.getSavedChart( this.props.actions.getSavedChart(
this.props.formData, this.props.formData,

View File

@ -21,6 +21,7 @@
/* eslint no-param-reassign: ["error", { "props": false }] */ /* eslint no-param-reassign: ["error", { "props": false }] */
import { t } from '@superset-ui/translation'; import { t } from '@superset-ui/translation';
import { SupersetClient } from '@superset-ui/connection'; import { SupersetClient } from '@superset-ui/connection';
import { isFeatureEnabled, FeatureFlag } from 'src/featureFlags';
import { getExploreUrlAndPayload, getAnnotationJsonUrl } from '../explore/exploreUtils'; import { getExploreUrlAndPayload, getAnnotationJsonUrl } from '../explore/exploreUtils';
import { requiresQuery, ANNOTATION_SOURCE_TYPES } from '../modules/AnnotationTypes'; import { requiresQuery, ANNOTATION_SOURCE_TYPES } from '../modules/AnnotationTypes';
import { addDangerToast } from '../messageToasts/actions'; import { addDangerToast } from '../messageToasts/actions';
@ -194,7 +195,9 @@ export function exploreJSON(formData, force = false, timeout = 60, key, method)
}; };
} }
const clientMethod = method === 'GET' ? SupersetClient.get : SupersetClient.post; const clientMethod = method === 'GET' && isFeatureEnabled(FeatureFlag.CLIENT_CACHE)
? SupersetClient.get
: SupersetClient.post;
const queryPromise = clientMethod(querySettings) const queryPromise = clientMethod(querySettings)
.then(({ json }) => { .then(({ json }) => {
dispatch(logEvent(LOG_ACTIONS_LOAD_CHART, { dispatch(logEvent(LOG_ACTIONS_LOAD_CHART, {

View File

@ -222,7 +222,7 @@ class Header extends React.PureComponent {
colorScheme, colorScheme,
colorNamespace, colorNamespace,
); );
const labelColors = scale.getColorMap(); const labelColors = colorScheme ? scale.getColorMap() : {};
const data = { const data = {
positions, positions,
expanded_slices: expandedSlices, expanded_slices: expandedSlices,

View File

@ -110,7 +110,7 @@ class SaveModal extends React.PureComponent {
colorScheme, colorScheme,
colorNamespace, colorNamespace,
); );
const labelColors = scale.getColorMap(); const labelColors = colorScheme ? scale.getColorMap() : {};
const data = { const data = {
positions, positions,
css, css,

View File

@ -21,6 +21,7 @@
export enum FeatureFlag { export enum FeatureFlag {
SCOPED_FILTER = 'SCOPED_FILTER', SCOPED_FILTER = 'SCOPED_FILTER',
OMNIBAR = 'OMNIBAR', OMNIBAR = 'OMNIBAR',
CLIENT_CACHE = 'CLIENT_CACHE',
} }
export type FeatureFlagMap = { export type FeatureFlagMap = {

View File

@ -200,7 +200,10 @@ LANGUAGES = {
# For example, DEFAULT_FEATURE_FLAGS = { 'FOO': True, 'BAR': False } here # For example, DEFAULT_FEATURE_FLAGS = { 'FOO': True, 'BAR': False } here
# and FEATURE_FLAGS = { 'BAR': True, 'BAZ': True } in superset_config.py # and FEATURE_FLAGS = { 'BAR': True, 'BAZ': True } in superset_config.py
# will result in combined feature flags of { 'FOO': True, 'BAR': True, 'BAZ': True } # will result in combined feature flags of { 'FOO': True, 'BAR': True, 'BAZ': True }
DEFAULT_FEATURE_FLAGS = {} DEFAULT_FEATURE_FLAGS = {
# Experimental feature introducing a client (browser) cache
'CLIENT_CACHE': False,
}
# A function that receives a dict of all feature flags # A function that receives a dict of all feature flags
# (DEFAULT_FEATURE_FLAGS merged with FEATURE_FLAGS) # (DEFAULT_FEATURE_FLAGS merged with FEATURE_FLAGS)

View File

@ -749,6 +749,10 @@ class Database(Model, AuditMixinNullable, ImportMixin):
def table_cache_timeout(self): def table_cache_timeout(self):
return self.metadata_cache_timeout.get('table_cache_timeout') return self.metadata_cache_timeout.get('table_cache_timeout')
@property
def default_schemas(self):
return self.get_extra().get('default_schemas', [])
@classmethod @classmethod
def get_password_masked_url_from_uri(cls, uri): def get_password_masked_url_from_uri(cls, uri):
url = make_url(uri) url = make_url(uri)

View File

@ -1590,6 +1590,16 @@ class Superset(BaseSupersetView):
table_names = [tn for tn in table_names if substr in tn] table_names = [tn for tn in table_names if substr in tn]
view_names = [vn for vn in view_names if substr in vn] view_names = [vn for vn in view_names if substr in vn]
if not schema and database.default_schemas:
def get_schema(tbl_or_view_name):
return tbl_or_view_name.split('.')[0] if '.' in tbl_or_view_name else None
user_schema = g.user.email.split('@')[0]
valid_schemas = set(database.default_schemas + [user_schema])
table_names = [tn for tn in table_names if get_schema(tn) in valid_schemas]
view_names = [vn for vn in view_names if get_schema(vn) in valid_schemas]
max_items = config.get('MAX_TABLE_NAMES') or len(table_names) max_items = config.get('MAX_TABLE_NAMES') or len(table_names)
total_items = len(table_names) + len(view_names) total_items = len(table_names) + len(view_names)
max_tables = len(table_names) max_tables = len(table_names)

View File

@ -1112,16 +1112,22 @@ class NVD3TimeSeriesViz(NVD3Viz):
series_title = series_title + (title_suffix,) series_title = series_title + (title_suffix,)
values = [] values = []
non_nan_cnt = 0
for ds in df.index: for ds in df.index:
if ds in ys: if ds in ys:
d = { d = {
'x': ds, 'x': ds,
'y': ys[ds], 'y': ys[ds],
} }
if not np.isnan(ys[ds]):
non_nan_cnt += 1
else: else:
d = {} d = {}
values.append(d) values.append(d)
if non_nan_cnt == 0:
continue
d = { d = {
'key': series_title, 'key': series_title,
'values': values, 'values': values,
@ -1224,7 +1230,9 @@ class NVD3TimeSeriesViz(NVD3Viz):
comparison_type = fd.get('comparison_type') or 'values' comparison_type = fd.get('comparison_type') or 'values'
df = self.process_data(df) df = self.process_data(df)
if comparison_type == 'values': if comparison_type == 'values':
chart_data = self.to_series(df) # Filter out series with all NaN
chart_data = self.to_series(df.dropna(axis=1, how='all'))
for i, (label, df2) in enumerate(self._extra_chart_data): for i, (label, df2) in enumerate(self._extra_chart_data):
chart_data.extend( chart_data.extend(
self.to_series( self.to_series(