diff --git a/superset-frontend/spec/javascripts/chart/chartActions_spec.js b/superset-frontend/spec/javascripts/chart/chartActions_spec.js index 5c6750940..15566dd50 100644 --- a/superset-frontend/spec/javascripts/chart/chartActions_spec.js +++ b/superset-frontend/spec/javascripts/chart/chartActions_spec.js @@ -55,7 +55,13 @@ describe('chart actions', () => { .callsFake(() => ({ get: () => fakeMetadata })); buildQueryRegistryStub = sinon .stub(chartlib, 'getChartBuildQueryRegistry') - .callsFake(() => ({ get: () => () => ({ some_param: 'fake query!' }) })); + .callsFake(() => ({ + get: () => () => ({ + some_param: 'fake query!', + result_type: 'full', + result_format: 'json', + }), + })); }); afterEach(() => { @@ -76,7 +82,11 @@ describe('chart actions', () => { expect(fetchMock.calls(V1_URL)).toHaveLength(1); expect(fetchMock.calls(V1_URL)[0][1].body).toBe( - JSON.stringify({ some_param: 'fake query!' }), + JSON.stringify({ + some_param: 'fake query!', + result_type: 'full', + result_format: 'json', + }), ); expect(dispatch.args[0][0].type).toBe(actions.CHART_UPDATE_STARTED); }); @@ -153,6 +163,7 @@ describe('chart actions', () => { return actionThunk(dispatch).then(() => { // chart update, trigger query, update form data, fail + expect(fetchMock.calls(MOCK_URL)).toHaveLength(1); expect(dispatch.callCount).toBe(5); expect(dispatch.args[4][0].type).toBe(actions.CHART_UPDATE_TIMEOUT); setupDefaultFetchMock(); diff --git a/superset-frontend/src/chart/chartAction.js b/superset-frontend/src/chart/chartAction.js index 3b46ae149..e022872ae 100644 --- a/superset-frontend/src/chart/chartAction.js +++ b/superset-frontend/src/chart/chartAction.js @@ -20,11 +20,13 @@ /* eslint no-param-reassign: ["error", { "props": false }] */ import moment from 'moment'; import { t } from '@superset-ui/translation'; -import { getChartBuildQueryRegistry } from '@superset-ui/chart'; import { SupersetClient } from '@superset-ui/connection'; -import { isFeatureEnabled, FeatureFlag } from 'src/featureFlags'; import { - shouldUseLegacyApi, + getChartBuildQueryRegistry, + getChartMetadataRegistry, +} from '@superset-ui/chart'; +import { isFeatureEnabled, FeatureFlag } from '../featureFlags'; +import { getExploreUrl, getAnnotationJsonUrl, postForm, @@ -33,6 +35,7 @@ import { requiresQuery, ANNOTATION_SOURCE_TYPES, } from '../modules/AnnotationTypes'; + import { addDangerToast } from '../messageToasts/actions'; import { logEvent } from '../logger/actions'; import { Logger, LOG_ACTIONS_LOAD_CHART } from '../logger/LogUtils'; @@ -99,6 +102,118 @@ export function annotationQueryFailed(annotation, queryResponse, key) { return { type: ANNOTATION_QUERY_FAILED, annotation, queryResponse, key }; } +const shouldUseLegacyApi = formData => { + const { useLegacyApi } = getChartMetadataRegistry().get(formData.viz_type); + return useLegacyApi || false; +}; + +const legacyChartDataRequest = async ( + formData, + resultFormat, + resultType, + force, + method = 'POST', + requestParams = {}, +) => { + const endpointType = ['base', 'csv', 'results', 'samples'].includes( + resultType, + ) + ? resultType + : resultFormat; + const url = getExploreUrl({ + formData, + endpointType, + force, + allowDomainSharding, + method, + requestParams: requestParams.dashboard_id + ? { dashboard_id: requestParams.dashboard_id } + : {}, + }); + const querySettings = { + ...requestParams, + url, + postPayload: { form_data: formData }, + }; + + const clientMethod = + 'GET' && isFeatureEnabled(FeatureFlag.CLIENT_CACHE) + ? SupersetClient.get + : SupersetClient.post; + return clientMethod(querySettings).then(({ json }) => { + // Make the legacy endpoint return a payload that corresponds to the + // V1 chart data endpoint response signature. + return { + result: [json], + }; + }); +}; + +const v1ChartDataRequest = async ( + formData, + resultFormat, + resultType, + force, + requestParams, +) => { + const buildQuery = await getChartBuildQueryRegistry().get(formData.viz_type); + const payload = buildQuery({ + ...formData, + force, + }); + // TODO: remove once these are added to superset-ui/query + payload.result_type = resultType; + payload.result_format = resultFormat; + const querySettings = { + ...requestParams, + endpoint: '/api/v1/chart/data', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(payload), + }; + return SupersetClient.post(querySettings).then(({ json }) => { + return json; + }); +}; + +export async function getChartDataRequest( + formData, + resultFormat = 'json', + resultType = 'full', + force = false, + method = 'POST', + requestParams = {}, +) { + let querySettings = { + ...requestParams, + }; + + if (allowDomainSharding) { + querySettings = { + ...querySettings, + mode: 'cors', + credentials: 'include', + }; + } + + if (shouldUseLegacyApi(formData)) { + return legacyChartDataRequest( + formData, + resultFormat, + resultType, + force, + method, + querySettings, + ); + } + return v1ChartDataRequest( + formData, + resultFormat, + resultType, + force, + querySettings, + ); +} + export function runAnnotationQuery( annotation, timeout = 60, @@ -204,48 +319,6 @@ export function addChart(chart, key) { return { type: ADD_CHART, chart, key }; } -function legacyChartDataRequest( - formData, - force, - method, - dashboardId, - requestParams, -) { - const url = getExploreUrl({ - formData, - endpointType: 'json', - force, - allowDomainSharding, - method, - requestParams: dashboardId ? { dashboard_id: dashboardId } : {}, - }); - const querySettings = { - ...requestParams, - url, - postPayload: { form_data: formData }, - }; - - const clientMethod = - method === 'GET' && isFeatureEnabled(FeatureFlag.CLIENT_CACHE) - ? SupersetClient.get - : SupersetClient.post; - - return clientMethod(querySettings); -} - -async function v1ChartDataRequest(formData, force, requestParams) { - const buildQuery = await getChartBuildQueryRegistry().get(formData.viz_type); - const payload = buildQuery({ ...formData, force }); - const querySettings = { - ...requestParams, - endpoint: '/api/v1/chart/data', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(payload), - }; - - return SupersetClient.post(querySettings); -} - export function exploreJSON( formData, force = false, @@ -258,41 +331,30 @@ export function exploreJSON( const logStart = Logger.getTimestamp(); const controller = new AbortController(); - const useLegacyApi = shouldUseLegacyApi(formData); - - let requestParams = { + const requestParams = { signal: controller.signal, timeout: timeout * 1000, }; + if (dashboardId) requestParams.dashboard_id = dashboardId; - if (allowDomainSharding) { - requestParams = { - ...requestParams, - mode: 'cors', - credentials: 'include', - }; - } - - const queryPromiseRaw = useLegacyApi - ? legacyChartDataRequest( - formData, - force, - method, - dashboardId, - requestParams, - ) - : v1ChartDataRequest(formData, force, requestParams); + const chartDataRequest = getChartDataRequest( + formData, + 'json', + 'full', + force, + method, + requestParams, + ); dispatch(chartUpdateStarted(controller, formData, key)); - const queryPromiseCaught = queryPromiseRaw - .then(({ json }) => { + const chartDataRequestCaught = chartDataRequest + .then(response => { // new API returns an object with an array of restults - // problem: json holds a list of results, when before we were just getting one result. - // `queryResponse` state is used all over the place. + // problem: response holds a list of results, when before we were just getting one result. // How to make the entire app compatible with multiple results? // For now just use the first result. - const result = useLegacyApi ? json : json.result[0]; + const result = response.result[0]; dispatch( logEvent(LOG_ACTIONS_LOAD_CHART, { slice_id: key, @@ -348,7 +410,7 @@ export function exploreJSON( const annotationLayers = formData.annotation_layers || []; return Promise.all([ - queryPromiseCaught, + chartDataRequestCaught, dispatch(triggerQuery(false, key)), dispatch(updateQueryFormData(formData, key)), ...annotationLayers.map(x => diff --git a/superset-frontend/src/explore/components/DisplayQueryButton.jsx b/superset-frontend/src/explore/components/DisplayQueryButton.jsx index 32d5f7630..5f57700d9 100644 --- a/superset-frontend/src/explore/components/DisplayQueryButton.jsx +++ b/superset-frontend/src/explore/components/DisplayQueryButton.jsx @@ -41,7 +41,7 @@ import { SupersetClient } from '@superset-ui/connection'; import getClientErrorObject from '../../utils/getClientErrorObject'; import CopyToClipboard from './../../components/CopyToClipboard'; -import { getExploreUrl } from '../exploreUtils'; +import { getChartDataRequest } from '../../chart/chartAction'; import Loading from '../../components/Loading'; import ModalTrigger from './../../components/ModalTrigger'; @@ -87,33 +87,29 @@ export class DisplayQueryButton extends React.PureComponent { this.openPropertiesModal = this.openPropertiesModal.bind(this); this.closePropertiesModal = this.closePropertiesModal.bind(this); } - beforeOpen(endpointType) { + beforeOpen(resultType) { this.setState({ isLoading: true }); - const url = getExploreUrl({ - formData: this.props.latestQueryFormData, - endpointType, - }); - SupersetClient.post({ - url, - postPayload: { form_data: this.props.latestQueryFormData }, - }) - .then(({ json }) => { + + getChartDataRequest(this.props.latestQueryFormData, 'json', resultType) + .then(response => { + // Currently displaying of only first query is supported + const result = response.result[0]; this.setState({ - language: json.language, - query: json.query, - data: json.data, + language: result.language, + query: result.query, + data: result.data, isLoading: false, error: null, }); }) - .catch(response => + .catch(response => { getClientErrorObject(response).then(({ error, statusText }) => { this.setState({ error: error || statusText || t('Sorry, An error occurred'), isLoading: false, }); - }), - ); + }); + }); } changeFilterText(event) { this.setState({ filterText: event.target.value }); diff --git a/superset-frontend/src/explore/components/ExploreActionButtons.jsx b/superset-frontend/src/explore/components/ExploreActionButtons.jsx index c606108c3..e517d9fed 100644 --- a/superset-frontend/src/explore/components/ExploreActionButtons.jsx +++ b/superset-frontend/src/explore/components/ExploreActionButtons.jsx @@ -48,7 +48,7 @@ export default function ExploreActionButtons({ 'disabled disabledButton': !canDownload, }); const doExportCSV = exportChart.bind(this, latestQueryFormData, 'csv'); - const doExportChart = exportChart.bind(this, latestQueryFormData, 'json'); + const doExportChart = exportChart.bind(this, latestQueryFormData, 'results'); return (