mirror of
https://github.com/apache/superset.git
synced 2026-05-12 19:35:17 +00:00
fix(ui): remove makeUrl() double-prefix bugs under subdirectory deployment (#39503)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: Vitor Avila <96086495+Vitor-Avila@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
8c80caefa3
commit
aa710672ed
@@ -35,7 +35,6 @@ import { CheckboxChangeEvent } from '@superset-ui/core/components/Checkbox/types
|
||||
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { setItem, LocalStorageKeys } from 'src/utils/localStorageHelpers';
|
||||
import { makeUrl } from 'src/utils/pathUtils';
|
||||
import Tabs from '@superset-ui/core/components/Tabs';
|
||||
import {
|
||||
Button,
|
||||
@@ -1824,7 +1823,9 @@ const DatabaseModal: FunctionComponent<DatabaseModalProps> = ({
|
||||
onClick={() => {
|
||||
setLoading(true);
|
||||
fetchAndSetDB();
|
||||
redirectURL(makeUrl(`/sqllab?db=true`));
|
||||
// redirectURL() delegates to history.push; React Router's basename
|
||||
// already prefixes the application root, so pass a relative path.
|
||||
redirectURL('/sqllab?db=true');
|
||||
}}
|
||||
>
|
||||
{t('Query data in SQL Lab')}
|
||||
|
||||
@@ -24,7 +24,6 @@ import { TableTab } from 'src/views/CRUD/types';
|
||||
import { t } from '@apache-superset/core/translation';
|
||||
import { styled } from '@apache-superset/core/theme';
|
||||
import { navigateTo } from 'src/utils/navigationUtils';
|
||||
import { makeUrl } from 'src/utils/pathUtils';
|
||||
import { WelcomeTable } from './types';
|
||||
|
||||
const EmptyContainer = styled.div`
|
||||
@@ -59,7 +58,9 @@ const REDIRECTS = {
|
||||
create: {
|
||||
[WelcomeTable.Charts]: '/chart/add',
|
||||
[WelcomeTable.Dashboards]: '/dashboard/new',
|
||||
[WelcomeTable.SavedQueries]: makeUrl('/sqllab?new=true'),
|
||||
// navigateTo() applies the application root internally; keep this
|
||||
// relative so the prefix isn't added twice.
|
||||
[WelcomeTable.SavedQueries]: '/sqllab?new=true',
|
||||
},
|
||||
viewAll: {
|
||||
[WelcomeTable.Charts]: '/chart/list',
|
||||
|
||||
@@ -44,7 +44,7 @@ import {
|
||||
TelemetryPixel,
|
||||
} from '@superset-ui/core/components';
|
||||
import type { ItemType, MenuItem } from '@superset-ui/core/components/Menu';
|
||||
import { ensureAppRoot, makeUrl } from 'src/utils/pathUtils';
|
||||
import { ensureAppRoot } from 'src/utils/pathUtils';
|
||||
import { isEmbedded } from 'src/dashboard/util/isEmbedded';
|
||||
import { findPermission } from 'src/utils/findPermission';
|
||||
import { isUserAdmin } from 'src/dashboard/util/permissionUtils';
|
||||
@@ -213,7 +213,10 @@ const RightMenu = ({
|
||||
},
|
||||
{
|
||||
label: t('SQL query'),
|
||||
url: makeUrl('/sqllab?new=true'),
|
||||
// Keep the URL relative so isFrontendRoute() matches and Link navigates
|
||||
// via React Router; the <Typography.Link> fallback applies ensureAppRoot
|
||||
// exactly once for non-frontend routes.
|
||||
url: '/sqllab?new=true',
|
||||
icon: <Icons.SearchOutlined data-test={`menu-item-${t('SQL query')}`} />,
|
||||
perm: 'can_sqllab',
|
||||
view: 'Superset',
|
||||
|
||||
@@ -25,11 +25,20 @@ import {
|
||||
fireEvent,
|
||||
waitFor,
|
||||
} from 'spec/helpers/testing-library';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { MemoryRouter, useLocation } from 'react-router-dom';
|
||||
import { QueryParamProvider } from 'use-query-params';
|
||||
import { ReactRouter5Adapter } from 'use-query-params/adapters/react-router-5';
|
||||
import * as getBootstrapData from 'src/utils/getBootstrapData';
|
||||
import SavedQueryList from '.';
|
||||
|
||||
// Renders the current router pathname+search so tests can assert navigation.
|
||||
function LocationDisplay() {
|
||||
const location = useLocation();
|
||||
return (
|
||||
<div data-test="location-display">{`${location.pathname}${location.search}`}</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Increase default timeout
|
||||
jest.setTimeout(30000);
|
||||
|
||||
@@ -88,6 +97,7 @@ const renderList = (props = {}, storeOverrides = {}) =>
|
||||
<MemoryRouter>
|
||||
<QueryParamProvider adapter={ReactRouter5Adapter}>
|
||||
<SavedQueryList user={mockUser} {...props} />
|
||||
<LocationDisplay />
|
||||
</QueryParamProvider>
|
||||
</MemoryRouter>,
|
||||
{
|
||||
@@ -242,4 +252,39 @@ describe('SavedQueryList', () => {
|
||||
// Verify delete buttons are not shown
|
||||
expect(screen.queryByTestId('delete-action')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('"+ Query" button pushes a router-relative path (subdirectory deployment)', async () => {
|
||||
// Simulate SUPERSET_APP_ROOT=/superset. ensureAppRoot/makeUrl read
|
||||
// applicationRoot() dynamically, so mocking it here makes the buggy code
|
||||
// path (makeUrl() around history.push) produce '/superset/sqllab?new=true'
|
||||
// instead of being a no-op. React Router's <Router basename> prefixes the
|
||||
// app root on its own, so history.push MUST receive a path without the
|
||||
// app-root prefix — otherwise navigation lands at /superset/superset/sqllab
|
||||
// and shows a blank page (sc-103661).
|
||||
const applicationRootSpy = jest
|
||||
.spyOn(getBootstrapData, 'applicationRoot')
|
||||
.mockReturnValue('/superset');
|
||||
|
||||
try {
|
||||
renderList();
|
||||
|
||||
await screen.findByTestId('saved_query-list-view');
|
||||
|
||||
const queryButton = await screen.findByRole('button', {
|
||||
name: /query/i,
|
||||
});
|
||||
fireEvent.click(queryButton);
|
||||
|
||||
await waitFor(() => {
|
||||
// The MemoryRouter in renderList uses the default ('/') basename, so
|
||||
// useLocation reflects exactly what history.push received. A correct
|
||||
// router-relative push produces '/sqllab?new=true'; a buggy push that
|
||||
// re-applied the app root would produce '/superset/sqllab?new=true'.
|
||||
const location = screen.getByTestId('location-display').textContent;
|
||||
expect(location).toBe('/sqllab?new=true');
|
||||
});
|
||||
} finally {
|
||||
applicationRootSpy.mockRestore();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -223,7 +223,9 @@ function SavedQueryList({
|
||||
name: t('Query'),
|
||||
buttonStyle: 'primary',
|
||||
onClick: () => {
|
||||
history.push(makeUrl('/sqllab?new=true'));
|
||||
// React Router's basename already includes the application root; passing
|
||||
// a relative path ensures correct navigation under subdirectory deployments.
|
||||
history.push('/sqllab?new=true');
|
||||
},
|
||||
});
|
||||
|
||||
@@ -245,7 +247,9 @@ function SavedQueryList({
|
||||
if (openInNewWindow) {
|
||||
window.open(makeUrl(`/sqllab?savedQueryId=${id}`));
|
||||
} else {
|
||||
history.push(makeUrl(`/sqllab?savedQueryId=${id}`));
|
||||
// React Router's basename already includes the application root; passing
|
||||
// a relative path ensures correct navigation under subdirectory deployments.
|
||||
history.push(`/sqllab?savedQueryId=${id}`);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -338,9 +342,7 @@ function SavedQueryList({
|
||||
row: {
|
||||
original: { id, label },
|
||||
},
|
||||
}: any) => (
|
||||
<Link to={makeUrl(`/sqllab?savedQueryId=${id}`)}>{label}</Link>
|
||||
),
|
||||
}: any) => <Link to={`/sqllab?savedQueryId=${id}`}>{label}</Link>,
|
||||
id: 'label',
|
||||
},
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user