From commits-return-1666-archive-asf-public=cust-asf.ponee.io@superset.incubator.apache.org Mon Oct 15 21:52:14 2018 Return-Path: X-Original-To: archive-asf-public@cust-asf.ponee.io Delivered-To: archive-asf-public@cust-asf.ponee.io Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by mx-eu-01.ponee.io (Postfix) with SMTP id 14F5B18067C for ; Mon, 15 Oct 2018 21:52:11 +0200 (CEST) Received: (qmail 43944 invoked by uid 500); 15 Oct 2018 19:52:11 -0000 Mailing-List: contact commits-help@superset.incubator.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@superset.incubator.apache.org Delivered-To: mailing list commits@superset.incubator.apache.org Received: (qmail 43935 invoked by uid 99); 15 Oct 2018 19:52:11 -0000 Received: from ec2-52-202-80-70.compute-1.amazonaws.com (HELO gitbox.apache.org) (52.202.80.70) by apache.org (qpsmtpd/0.29) with ESMTP; Mon, 15 Oct 2018 19:52:11 +0000 Received: by gitbox.apache.org (ASF Mail Server at gitbox.apache.org, from userid 33) id 7D25881F02; Mon, 15 Oct 2018 19:52:10 +0000 (UTC) Date: Mon, 15 Oct 2018 19:52:13 +0000 To: "commits@superset.apache.org" Subject: [incubator-superset] 04/13: [superset-client] replace all chart ajax calls with SupersetClient MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit From: ccwilliams@apache.org In-Reply-To: <153963312961.26155.11736443246480780967@gitbox.apache.org> References: <153963312961.26155.11736443246480780967@gitbox.apache.org> X-Git-Host: gitbox.apache.org X-Git-Repo: incubator-superset X-Git-Refname: refs/heads/chris--ajax-charts X-Git-Reftype: branch X-Git-Rev: e0ba5f24b7311a92f984e371852046a5f1c1b52e X-Git-NotificationType: diff X-Git-Multimail-Version: 1.5.dev Auto-Submitted: auto-generated Message-Id: <20181015195210.7D25881F02@gitbox.apache.org> This is an automated email from the ASF dual-hosted git repository. ccwilliams pushed a commit to branch chris--ajax-charts in repository https://gitbox.apache.org/repos/asf/incubator-superset.git commit e0ba5f24b7311a92f984e371852046a5f1c1b52e Author: Chris Williams AuthorDate: Wed Sep 12 11:42:47 2018 -0700 [superset-client] replace all chart ajax calls with SupersetClient --- superset/assets/src/chart/Chart.jsx | 25 +- superset/assets/src/chart/chartAction.js | 99 ++++--- .../src/explore/components/ExploreChartPanel.jsx | 2 +- .../explore/components/ExploreViewContainer.jsx | 15 +- .../components/controls/AnnotationLayer.jsx | 287 ++++++++++----------- .../assets/src/explore/reducers/getInitialState.js | 9 +- 6 files changed, 231 insertions(+), 206 deletions(-) diff --git a/superset/assets/src/chart/Chart.jsx b/superset/assets/src/chart/Chart.jsx index 0d55027..80d2513 100644 --- a/superset/assets/src/chart/Chart.jsx +++ b/superset/assets/src/chart/Chart.jsx @@ -1,8 +1,10 @@ import React from 'react'; import PropTypes from 'prop-types'; +import Mustache from 'mustache'; import { Tooltip } from 'react-bootstrap'; import dompurify from 'dompurify'; +import { d3format } from '../modules/utils'; import ChartBody from './ChartBody'; import Loading from '../components/Loading'; import { Logger, LOG_ACTIONS_RENDER_CHART } from '../logger'; @@ -31,7 +33,6 @@ const propTypes = { chartUpdateEndTime: PropTypes.number, chartUpdateStartTime: PropTypes.number, latestQueryFormData: PropTypes.object, - queryRequest: PropTypes.object, queryResponse: PropTypes.object, lastRendered: PropTypes.number, triggerQuery: PropTypes.bool, @@ -166,10 +167,30 @@ class Chart extends React.PureComponent { ); } + d3format(col, number) { + const { datasource } = this.props; + const format = (datasource.column_formats && datasource.column_formats[col]) || '0.3s'; + + return d3format(format, number); + } + error(e) { this.props.actions.chartRenderingFailed(e, this.props.chartId); } + verboseMetricName(metric) { + return this.props.datasource.verbose_map[metric] || metric; + } + + // eslint-disable-next-line camelcase + render_template(s) { + const context = { + width: this.width(), + height: this.height(), + }; + return Mustache.render(s, context); + } + renderTooltip() { if (this.state.tooltip) { return ( @@ -177,7 +198,7 @@ class Chart extends React.PureComponent { className="chart-tooltip" id="chart-tooltip" placement="right" - positionTop={this.state.tooltip.y + 30} + positionTop={this.state.tooltip.y - 10} positionLeft={this.state.tooltip.x + 30} arrowOffsetTop={10} > diff --git a/superset/assets/src/chart/chartAction.js b/superset/assets/src/chart/chartAction.js index 8adbdc0..cb835c5 100644 --- a/superset/assets/src/chart/chartAction.js +++ b/superset/assets/src/chart/chartAction.js @@ -1,16 +1,16 @@ -import URI from 'urijs'; - +/* global window, AbortController */ +/* eslint no-undef: 'error' */ +import { SupersetClient } from '@superset-ui/core'; import { getExploreUrlAndPayload, getAnnotationJsonUrl } from '../explore/exploreUtils'; import { requiresQuery, ANNOTATION_SOURCE_TYPES } from '../modules/AnnotationTypes'; +import { addDangerToast } from '../messageToasts/actions'; import { Logger, LOG_ACTIONS_LOAD_CHART } from '../logger'; import { COMMON_ERR_MESSAGES } from '../utils/common'; import { t } from '../locales'; -const $ = (window.$ = require('jquery')); - export const CHART_UPDATE_STARTED = 'CHART_UPDATE_STARTED'; -export function chartUpdateStarted(queryRequest, latestQueryFormData, key) { - return { type: CHART_UPDATE_STARTED, queryRequest, latestQueryFormData, key }; +export function chartUpdateStarted(queryController, latestQueryFormData, key) { + return { type: CHART_UPDATE_STARTED, queryController, latestQueryFormData, key }; } export const CHART_UPDATE_SUCCEEDED = 'CHART_UPDATE_SUCCEEDED'; @@ -54,8 +54,8 @@ export function annotationQuerySuccess(annotation, queryResponse, key) { } export const ANNOTATION_QUERY_STARTED = 'ANNOTATION_QUERY_STARTED'; -export function annotationQueryStarted(annotation, queryRequest, key) { - return { type: ANNOTATION_QUERY_STARTED, annotation, queryRequest, key }; +export function annotationQueryStarted(annotation, queryController, key) { + return { type: ANNOTATION_QUERY_STARTED, annotation, queryController, key }; } export const ANNOTATION_QUERY_FAILED = 'ANNOTATION_QUERY_FAILED'; @@ -85,18 +85,21 @@ export function runAnnotationQuery(annotation, timeout = 60, formData = null, ke ); const isNative = annotation.sourceType === ANNOTATION_SOURCE_TYPES.NATIVE; const url = getAnnotationJsonUrl(annotation.value, sliceFormData, isNative); - const queryRequest = $.ajax({ + const controller = new AbortController(); + const { signal } = controller; + + dispatch(annotationQueryStarted(annotation, controller, sliceKey)); + + return SupersetClient.get({ url, - dataType: 'json', + signal, timeout: timeout * 1000, - }); - dispatch(annotationQueryStarted(annotation, queryRequest, sliceKey)); - return queryRequest - .then(queryResponse => dispatch(annotationQuerySuccess(annotation, queryResponse, sliceKey))) + }) + .then(({ json }) => dispatch(annotationQuerySuccess(annotation, json, sliceKey))) .catch((err) => { if (err.statusText === 'timeout') { dispatch(annotationQueryFailed(annotation, { error: 'Query Timeout' }, sliceKey)); - } else if ((err.responseJSON.error || '').toLowerCase().startsWith('no data')) { + } else if ((err.responseJSON.error || '').toLowerCase().includes('no data')) { dispatch(annotationQuerySuccess(annotation, err, sliceKey)); } else if (err.statusText !== 'abort') { dispatch(annotationQueryFailed(annotation, err.responseJSON, sliceKey)); @@ -135,30 +138,30 @@ export function runQuery(formData, force = false, timeout = 60, key) { force, }); const logStart = Logger.getTimestamp(); - const queryRequest = $.ajax({ - type: 'POST', + const controller = new AbortController(); + const { signal } = controller; + + dispatch(chartUpdateStarted(controller, payload, key)); + + const queryPromise = SupersetClient.post({ url, - dataType: 'json', - data: { - form_data: JSON.stringify(payload), - }, + postPayload: { form_data: payload }, + signal, timeout: timeout * 1000, - }); - const queryPromise = Promise.resolve(dispatch(chartUpdateStarted(queryRequest, payload, key))) - .then(() => queryRequest) - .then((queryResponse) => { + }) + .then(({ json }) => { Logger.append(LOG_ACTIONS_LOAD_CHART, { slice_id: key, - is_cached: queryResponse.is_cached, + is_cached: json.is_cached, force_refresh: force, - row_count: queryResponse.rowcount, + row_count: json.rowcount, datasource: formData.datasource, start_offset: logStart, duration: Logger.getTimestamp() - logStart, has_extra_filters: formData.extra_filters && formData.extra_filters.length > 0, viz_type: formData.viz_type, }); - return dispatch(chartUpdateSucceeded(queryResponse, key)); + return dispatch(chartUpdateSucceeded(json, key)); }) .catch((err) => { Logger.append(LOG_ACTIONS_LOAD_CHART, { @@ -170,7 +173,7 @@ export function runQuery(formData, force = false, timeout = 60, key) { }); if (err.statusText === 'timeout') { dispatch(chartUpdateTimeout(err.statusText, timeout, key)); - } else if (err.statusText === 'abort') { + } else if (err.statusText === 'AbortError') { dispatch(chartUpdateStopped(key)); } else { let errObject; @@ -193,7 +196,9 @@ export function runQuery(formData, force = false, timeout = 60, key) { dispatch(chartUpdateFailed(errObject, key)); } }); + const annotationLayers = formData.annotation_layers || []; + return Promise.all([ queryPromise, dispatch(triggerQuery(false, key)), @@ -203,29 +208,21 @@ export function runQuery(formData, force = false, timeout = 60, key) { }; } -export const SQLLAB_REDIRECT_FAILED = 'SQLLAB_REDIRECT_FAILED'; -export function sqllabRedirectFailed(error, key) { - return { type: SQLLAB_REDIRECT_FAILED, error, key }; -} - export function redirectSQLLab(formData) { - return function (dispatch) { - const { url, payload } = getExploreUrlAndPayload({ formData, endpointType: 'query' }); - $.ajax({ - type: 'POST', - url, - data: { - form_data: JSON.stringify(payload), - }, - success: (response) => { - const redirectUrl = new URI(window.location); - redirectUrl - .pathname('/superset/sqllab') - .search({ datasourceKey: formData.datasource, sql: response.query }); - window.open(redirectUrl.href(), '_blank'); - }, - error: (xhr, status, error) => dispatch(sqllabRedirectFailed(error, formData.slice_id)), - }); + return (dispatch) => { + const { url } = getExploreUrlAndPayload({ formData, endpointType: 'query' }); + return SupersetClient.get({ url }) + .then(({ json }) => { + const redirectUrl = new URL(window.location); + redirectUrl.pathname = '/superset/sqllab'; + for (const key of redirectUrl.searchParams.keys()) { + redirectUrl.searchParams.delete(key); + } + redirectUrl.searchParams.set('datasourceKey', formData.datasource); + redirectUrl.searchParams.set('sql', json.query); + window.open(redirectUrl.href, '_blank'); + }) + .catch(() => dispatch(addDangerToast(t('An error occurred while loading the SQL')))); }; } diff --git a/superset/assets/src/explore/components/ExploreChartPanel.jsx b/superset/assets/src/explore/components/ExploreChartPanel.jsx index bcda75d..9d30284 100644 --- a/superset/assets/src/explore/components/ExploreChartPanel.jsx +++ b/superset/assets/src/explore/components/ExploreChartPanel.jsx @@ -62,7 +62,7 @@ class ExploreChartPanel extends React.PureComponent { latestQueryFormData={chart.latestQueryFormData} lastRendered={chart.lastRendered} queryResponse={chart.queryResponse} - queryRequest={chart.queryRequest} + queryController={chart.queryController} triggerQuery={chart.triggerQuery} /> ); diff --git a/superset/assets/src/explore/components/ExploreViewContainer.jsx b/superset/assets/src/explore/components/ExploreViewContainer.jsx index 34f165d..fc95cef 100644 --- a/superset/assets/src/explore/components/ExploreViewContainer.jsx +++ b/superset/assets/src/explore/components/ExploreViewContainer.jsx @@ -54,6 +54,9 @@ class ExploreViewContainer extends React.Component { this.addHistory = this.addHistory.bind(this); this.handleResize = this.handleResize.bind(this); this.handlePopstate = this.handlePopstate.bind(this); + this.onStop = this.onStop.bind(this); + this.onQuery = this.onQuery.bind(this); + this.toggleModal = this.toggleModal.bind(this); } componentDidMount() { @@ -124,7 +127,9 @@ class ExploreViewContainer extends React.Component { } onStop() { - return this.props.chart.queryRequest.abort(); + if (this.props.chart && this.props.chart.queryController) { + this.props.chart.queryController.abort(); + } } getWidth() { @@ -262,7 +267,7 @@ class ExploreViewContainer extends React.Component { > {this.state.showModal && ( @@ -271,9 +276,9 @@ class ExploreViewContainer extends React.Component {
x).length; } - handleAnnotationType(annotationType) { this.setState({ annotationType, @@ -199,31 +210,25 @@ export default class AnnotationLayer extends React.PureComponent { fetchOptions(annotationType, sourceType, isLoadingOptions) { if (isLoadingOptions === true) { if (sourceType === ANNOTATION_SOURCE_TYPES.NATIVE) { - $.ajax({ - type: 'GET', - url: '/annotationlayermodelview/api/read?', - }).then((data) => { - const layers = data ? data.result.map(layer => ({ - value: layer.id, - label: layer.name, - })) : []; + SupersetClient.get({ endpoint: '/annotationlayermodelview/api/read?' }).then(({ json }) => { + const layers = json + ? json.result.map(layer => ({ + value: layer.id, + label: layer.name, + })) + : []; this.setState({ isLoadingOptions: false, valueOptions: layers, }); }); } else if (requiresQuery(sourceType)) { - $.ajax({ - type: 'GET', - url: '/superset/user_slices', - }).then(data => + SupersetClient.get({ endpoint: '/superset/user_slices' }).then(({ json }) => this.setState({ isLoadingOptions: false, - valueOptions: data.filter( - x => getSupportedSourceTypes(annotationType) - .find(v => v === x.viz_type)) - .map(x => ({ value: x.id, label: x.title, slice: x }), - ), + valueOptions: json + .filter(x => getSupportedSourceTypes(annotationType).find(v => v === x.viz_type)) + .map(x => ({ value: x.id, label: x.title, slice: x })), }), ); } else { @@ -266,26 +271,26 @@ export default class AnnotationLayer extends React.PureComponent { } renderValueConfiguration() { - const { annotationType, sourceType, value, - valueOptions, isLoadingOptions } = this.state; + const { annotationType, sourceType, value, valueOptions, isLoadingOptions } = this.state; let label = ''; let description = ''; if (requiresQuery(sourceType)) { if (sourceType === ANNOTATION_SOURCE_TYPES.NATIVE) { - label = t('Annotation Layer'); - description = t('Select the Annotation Layer you would like to use.'); + label = 'Annotation Layer'; + description = 'Select the Annotation Layer you would like to use.'; } else { - label = t('Chart'); + label = label = t('Chart'); description = `Use a pre defined Superset Chart as a source for annotations and overlays. - 'your chart must be one of these visualization types: - '[${getSupportedSourceTypes(annotationType) - .map(x => ((x in vizTypes && 'label' in vizTypes[x]) ? vizTypes[x].label : '')).join(', ')}]'`; + your chart must be one of these visualization types: + [${getSupportedSourceTypes(annotationType) + .map(x => (x in vizTypes && 'label' in vizTypes[x] ? vizTypes[x].label : '')) + .join(', ')}]`; } } else if (annotationType === AnnotationTypes.FORMULA) { - label = t('Formula'); - description = t(`Expects a formula with depending time parameter 'x' + label = 'Formula'; + description = `Expects a formula with depending time parameter 'x' in milliseconds since epoch. mathjs is used to evaluate the formulas. - Example: '2x+5'`); + Example: '2x+5'`; } if (requiresQuery(sourceType)) { return ( @@ -300,10 +305,11 @@ export default class AnnotationLayer extends React.PureComponent { isLoading={isLoadingOptions} value={value} onChange={this.handleValue} - validationErrors={!value ? [t('Mandatory')] : []} + validationErrors={!value ? ['Mandatory'] : []} /> ); - } if (annotationType === AnnotationTypes.FORMULA) { + } + if (annotationType === AnnotationTypes.FORMULA) { return ( ); } @@ -322,37 +328,43 @@ export default class AnnotationLayer extends React.PureComponent { } renderSliceConfiguration() { - const { annotationType, sourceType, value, valueOptions, overrides, titleColumn, - timeColumn, intervalEndColumn, descriptionColumns } = this.state; + const { + annotationType, + sourceType, + value, + valueOptions, + overrides, + titleColumn, + timeColumn, + intervalEndColumn, + descriptionColumns, + } = this.state; const slice = (valueOptions.find(x => x.value === value) || {}).slice; if (sourceType !== ANNOTATION_SOURCE_TYPES.NATIVE && slice) { - const columns = (slice.data.groupby || []).concat( - (slice.data.all_columns || [])).map(x => ({ value: x, label: x })); - const timeColumnOptions = slice.data.include_time ? - [{ value: '__timestamp', label: '__timestamp' }].concat(columns) : columns; + const columns = (slice.data.groupby || []) + .concat(slice.data.all_columns || []) + .map(x => ({ value: x, label: x })); + const timeColumnOptions = slice.data.include_time + ? [{ value: '__timestamp', label: '__timestamp' }].concat(columns) + : columns; return (
{ - }} + onSelect={() => {}} title="Annotation Slice Configuration" - info={ - `This section allows you to configure how to use the slice - to generate annotations.` - } + info={`This section allows you to configure how to use the slice + to generate annotations.`} > - { - ( - annotationType === AnnotationTypes.EVENT || - annotationType === AnnotationTypes.INTERVAL - ) && + {(annotationType === AnnotationTypes.EVENT || + annotationType === AnnotationTypes.INTERVAL) && ( this.setState({ timeColumn: v })} /> - } - { - annotationType === AnnotationTypes.INTERVAL && + )} + {annotationType === AnnotationTypes.INTERVAL && ( this.setState({ intervalEndColumn: v })} /> - } + )} this.setState({ titleColumn: v })} /> - { - annotationType !== AnnotationTypes.TIME_SERIES && + {annotationType !== AnnotationTypes.TIME_SERIES && ( this.setState({ descriptionColumns: v })} /> - } + )}
); } - return (''); + return ''; } renderDisplayConfiguration() { const { color, opacity, style, width, showMarkers, hideLine, annotationType } = this.state; const colorScheme = [...getScheme(this.props.colorScheme)]; - if (color && color !== AUTOMATIC_COLOR && - !colorScheme.find(x => x.toLowerCase() === color.toLowerCase())) { + if ( + color && + color !== AUTOMATIC_COLOR && + !colorScheme.find(x => x.toLowerCase() === color.toLowerCase()) + ) { colorScheme.push(color); } return ( @@ -493,12 +502,12 @@ export default class AnnotationLayer extends React.PureComponent { this.setState({ style: v })} @@ -506,12 +515,12 @@ export default class AnnotationLayer extends React.PureComponent { this.setState({ opacity: v })} @@ -530,7 +539,7 @@ export default class AnnotationLayer extends React.PureComponent { bsSize="xsmall" onClick={() => this.setState({ color: AUTOMATIC_COLOR })} > - {t('Automatic Color')} + Automatic Color
@@ -541,42 +550,36 @@ export default class AnnotationLayer extends React.PureComponent { value={width} onChange={v => this.setState({ width: v })} /> - {annotationType === AnnotationTypes.TIME_SERIES && - this.setState({ showMarkers: v })} - /> - } - {annotationType === AnnotationTypes.TIME_SERIES && - this.setState({ hideLine: v })} - /> - } + {annotationType === AnnotationTypes.TIME_SERIES && ( + this.setState({ showMarkers: v })} + /> + )} + {annotationType === AnnotationTypes.TIME_SERIES && ( + this.setState({ hideLine: v })} + /> + )} ); } render() { - const { isNew, name, annotationType, - sourceType, show } = this.state; + const { isNew, name, annotationType, sourceType, show } = this.state; const isValid = this.isValidForm(); return (
- { - this.props.error && - - ERROR: {this.props.error} - - } + {this.props.error && ERROR: {this.props.error}}
({ value: x, label: getAnnotationTypeLabel(x) }))} + options={getSupportedAnnotationTypes(this.props.vizType).map(x => ({ + value: x, + label: getAnnotationTypeLabel(x), + }))} value={annotationType} onChange={this.handleAnnotationType} /> - {!!getSupportedSourceTypes(annotationType).length && + {!!getSupportedSourceTypes(annotationType).length && ( ({ value: x, label: getAnnotationSourceTypeLabels(x) }))} + options={getSupportedSourceTypes(annotationType).map(x => ({ + value: x, + label: getAnnotationSourceTypeLabels(x), + }))} value={sourceType} onChange={this.handleAnnotationSourceType} /> - } - { this.renderValueConfiguration() } + )} + {this.renderValueConfiguration()}
- { this.renderSliceConfiguration() } - { this.renderDisplayConfiguration() } + {this.renderSliceConfiguration()} + {this.renderDisplayConfiguration()}
-
- -
@@ -656,5 +652,6 @@ export default class AnnotationLayer extends React.PureComponent { ); } } + AnnotationLayer.propTypes = propTypes; AnnotationLayer.defaultProps = defaultProps; diff --git a/superset/assets/src/explore/reducers/getInitialState.js b/superset/assets/src/explore/reducers/getInitialState.js index 28910bf..dec455c 100644 --- a/superset/assets/src/explore/reducers/getInitialState.js +++ b/superset/assets/src/explore/reducers/getInitialState.js @@ -5,9 +5,10 @@ import { now } from '../../modules/dates'; import { getChartKey } from '../exploreUtils'; import { getControlsState, getFormDataFromControls } from '../store'; -export default function (bootstrapData) { +export default function getInitialState(bootstrapData) { const controls = getControlsState(bootstrapData, bootstrapData.form_data); const rawFormData = { ...bootstrapData.form_data }; + const bootstrappedState = { ...bootstrapData, common: { @@ -20,11 +21,15 @@ export default function (bootstrapData) { isDatasourceMetaLoading: false, isStarred: false, }; + const slice = bootstrappedState.slice; + const sliceFormData = slice ? getFormDataFromControls(getControlsState(bootstrapData, slice.form_data)) : null; + const chartKey = getChartKey(bootstrappedState); + return { featureFlags: bootstrapData.common.feature_flags, charts: { @@ -36,7 +41,7 @@ export default function (bootstrapData) { chartUpdateStartTime: now(), latestQueryFormData: getFormDataFromControls(controls), sliceFormData, - queryRequest: null, + queryController: null, queryResponse: null, triggerQuery: true, lastRendered: 0,