mirror of
https://github.com/apache/superset.git
synced 2026-05-01 14:04:21 +00:00
Compare commits
1 Commits
fix/check-
...
subdirecto
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
86fe4fc8b2 |
@@ -50,7 +50,6 @@ import Table, {
|
||||
import { RootState } from 'src/dashboard/types';
|
||||
import { usePermissions } from 'src/hooks/usePermissions';
|
||||
import { useToasts } from 'src/components/MessageToasts/withToasts';
|
||||
import { ensureAppRoot } from 'src/utils/pathUtils';
|
||||
import { safeStringify } from 'src/utils/safeStringify';
|
||||
import HeaderWithRadioGroup from '@superset-ui/core/components/Table/header-renderers/HeaderWithRadioGroup';
|
||||
import { useDatasetMetadataBar } from 'src/features/datasets/metadataBar/useDatasetMetadataBar';
|
||||
@@ -248,7 +247,7 @@ export default function DrillDetailPane({
|
||||
if (dashboardId) {
|
||||
payload.form_data = { dashboardId };
|
||||
}
|
||||
SupersetClient.postForm(ensureAppRoot('/api/v1/chart/data'), {
|
||||
SupersetClient.postForm('/api/v1/chart/data', {
|
||||
form_data: safeStringify(payload),
|
||||
}).catch(error => {
|
||||
addDangerToast(
|
||||
|
||||
@@ -48,7 +48,6 @@ import { Logger, LOG_ACTIONS_LOAD_CHART } from 'src/logger/LogUtils';
|
||||
import { allowCrossDomain as domainShardingEnabled } from 'src/utils/hostNamesConfig';
|
||||
import { updateDataMask } from 'src/dataMask/actions';
|
||||
import { waitForAsyncData } from 'src/middleware/asyncEvent';
|
||||
import { ensureAppRoot } from 'src/utils/pathUtils';
|
||||
import { safeStringify } from 'src/utils/safeStringify';
|
||||
import { extendedDayjs } from '@superset-ui/core/utils/dates';
|
||||
import type { Dispatch, Action, AnyAction } from 'redux';
|
||||
@@ -934,7 +933,7 @@ export function redirectSQLLab(
|
||||
requestedQuery: payload,
|
||||
});
|
||||
} else {
|
||||
SupersetClient.postForm(ensureAppRoot(redirectUrl), {
|
||||
SupersetClient.postForm(redirectUrl, {
|
||||
form_data: safeStringify(payload),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -58,7 +58,9 @@ beforeEach(() => {
|
||||
});
|
||||
});
|
||||
|
||||
// Tests for exportChart URL prefix handling in streaming export
|
||||
// Tests for exportChart URL prefix handling in streaming export.
|
||||
// Streaming uses native fetch (not SupersetClient), so exportChart must apply
|
||||
// ensureAppRoot before passing the URL to onStartStreamingExport.
|
||||
test('exportChart v1 API passes prefixed URL to onStartStreamingExport when app root is configured', async () => {
|
||||
const appRoot = '/superset';
|
||||
ensureAppRoot.mockImplementation((path: string) => `${appRoot}${path}`);
|
||||
@@ -111,6 +113,24 @@ test('exportChart v1 API passes nested prefix for deeply nested deployments', as
|
||||
expect(callArgs.exportType).toBe('xlsx');
|
||||
});
|
||||
|
||||
// Regression test for the double-prefix bug: SupersetClient.postForm adds appRoot
|
||||
// internally via getUrl(), so the URL passed must NOT already be prefixed.
|
||||
test('exportChart v1 API calls postForm with unprefixed URL when app root is configured', async () => {
|
||||
const { SupersetClient } = jest.requireMock('@superset-ui/core');
|
||||
const appRoot = '/analytics';
|
||||
ensureAppRoot.mockImplementation((path: string) => `${appRoot}${path}`);
|
||||
|
||||
await exportChart({
|
||||
formData: baseFormData,
|
||||
resultFormat: 'csv',
|
||||
});
|
||||
|
||||
expect(SupersetClient.postForm).toHaveBeenCalledTimes(1);
|
||||
const [url] = SupersetClient.postForm.mock.calls[0];
|
||||
expect(url).toBe('/api/v1/chart/data');
|
||||
expect(url).not.toContain(appRoot);
|
||||
});
|
||||
|
||||
test('exportChart passes csv exportType for CSV exports', async () => {
|
||||
const onStartStreamingExport = jest.fn();
|
||||
|
||||
@@ -143,7 +163,7 @@ test('exportChart passes xlsx exportType for Excel exports', async () => {
|
||||
);
|
||||
});
|
||||
|
||||
test('exportChart legacy API (useLegacyApi=true) passes prefixed URL with app root configured', async () => {
|
||||
test('exportChart legacy API (useLegacyApi=true) passes prefixed URL to onStartStreamingExport when app root is configured', async () => {
|
||||
const appRoot = '/superset';
|
||||
ensureAppRoot.mockImplementation((path: string) => `${appRoot}${path}`);
|
||||
|
||||
@@ -165,6 +185,8 @@ test('exportChart legacy API (useLegacyApi=true) passes prefixed URL with app ro
|
||||
|
||||
expect(onStartStreamingExport).toHaveBeenCalledTimes(1);
|
||||
const callArgs = onStartStreamingExport.mock.calls[0][0];
|
||||
// The legacy blueprint path is /superset/explore_json/; with appRoot=/superset the
|
||||
// full streaming URL is /superset/superset/explore_json/ (appRoot + blueprint prefix).
|
||||
expect(callArgs.url).toBe('/superset/superset/explore_json/?csv=true');
|
||||
expect(callArgs.exportType).toBe('csv');
|
||||
});
|
||||
|
||||
@@ -76,6 +76,7 @@ interface GetExploreUrlParams {
|
||||
allowDomainSharding?: boolean;
|
||||
method?: 'GET' | 'POST';
|
||||
relative?: boolean;
|
||||
includeAppRoot?: boolean;
|
||||
}
|
||||
|
||||
interface BuildV1ChartDataPayloadParams {
|
||||
@@ -223,6 +224,7 @@ export function getExploreUrl({
|
||||
allowDomainSharding = false,
|
||||
method = 'POST',
|
||||
relative = false,
|
||||
includeAppRoot = true,
|
||||
}: GetExploreUrlParams): string | null {
|
||||
if (!formData.datasource) {
|
||||
return null;
|
||||
@@ -242,7 +244,7 @@ export function getExploreUrl({
|
||||
uri = URI(URI(curUrl).search());
|
||||
}
|
||||
|
||||
const directory = getURIDirectory(endpointType);
|
||||
const directory = getURIDirectory(endpointType, includeAppRoot);
|
||||
|
||||
// Building the querystring (search) part of the URI
|
||||
const search = uri.search(true) as Record<string, string>;
|
||||
@@ -370,10 +372,11 @@ export const exportChart = async ({
|
||||
force,
|
||||
allowDomainSharding: false,
|
||||
relative: true,
|
||||
includeAppRoot: false,
|
||||
});
|
||||
payload = formData;
|
||||
} else {
|
||||
url = ensureAppRoot('/api/v1/chart/data');
|
||||
url = '/api/v1/chart/data';
|
||||
payload = await buildV1ChartDataPayload({
|
||||
formData,
|
||||
force,
|
||||
@@ -385,14 +388,16 @@ export const exportChart = async ({
|
||||
|
||||
// Check if streaming export handler is provided (from dashboard Chart.jsx)
|
||||
if (onStartStreamingExport) {
|
||||
// Streaming is handled by the caller - pass URL, payload, and export type
|
||||
// Streaming uses native fetch — apply appRoot prefix here since useStreamingExport
|
||||
// does not go through SupersetClient (which would add it automatically).
|
||||
onStartStreamingExport({
|
||||
url,
|
||||
url: url ? ensureAppRoot(url) : url,
|
||||
payload,
|
||||
exportType: resultFormat,
|
||||
});
|
||||
} else {
|
||||
// Fallback to original behavior for non-streaming exports
|
||||
// SupersetClient.postForm calls getUrl({ endpoint }) internally, which prepends
|
||||
// appRoot — so the URL must NOT be pre-prefixed here.
|
||||
SupersetClient.postForm(url as string, {
|
||||
form_data: safeStringify(payload),
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user