From c99843b13abf781ffa8d34da1ace460aed17bf3a Mon Sep 17 00:00:00 2001 From: Vitor Avila <96086495+Vitor-Avila@users.noreply.github.com> Date: Thu, 24 Jul 2025 10:45:31 -0300 Subject: [PATCH] fix: Hide View in SQL Lab for users without access (#34293) --- .../components/controls/ViewQuery.test.tsx | 38 ++++++++++++++++++- .../explore/components/controls/ViewQuery.tsx | 10 ++++- 2 files changed, 45 insertions(+), 3 deletions(-) diff --git a/superset-frontend/src/explore/components/controls/ViewQuery.test.tsx b/superset-frontend/src/explore/components/controls/ViewQuery.test.tsx index 9a18020c342..76da555d746 100644 --- a/superset-frontend/src/explore/components/controls/ViewQuery.test.tsx +++ b/superset-frontend/src/explore/components/controls/ViewQuery.test.tsx @@ -25,6 +25,7 @@ import { } from 'spec/helpers/testing-library'; import fetchMock from 'fetch-mock'; import copyTextToClipboard from 'src/utils/copy'; +import { RootState } from 'src/dashboard/types'; import ViewQuery, { ViewQueryProps } from './ViewQuery'; const mockHistoryPush = jest.fn(); @@ -37,8 +38,29 @@ jest.mock('react-router-dom', () => ({ jest.mock('src/utils/copy'); -function setup(props: ViewQueryProps) { - return render(, { useRouter: true, useRedux: true }); +const mockState = ( + roles: Record = { + Admin: [['menu_access', 'SQL Lab']], + }, +) => + ({ + user: { + firstName: 'Test', + isActive: true, + isAnonymous: false, + lastName: 'User', + username: 'testuser', + permissions: {} as Record, + roles, + }, + }) as Partial; + +function setup(props: ViewQueryProps, state: Partial = mockState()) { + return render(, { + useRouter: true, + useRedux: true, + initialState: state, + }); } const mockProps = { @@ -156,3 +178,15 @@ test('opens SQL Lab in a new tab when View in SQL Lab button is clicked with met '_blank', ); }); + +test('hides View in SQL Lab button when user does not have SQL Lab access', () => { + setup( + mockProps, + mockState({ + Basic: [['menu_access', 'Dashboard']], + }), + ); + + expect(screen.queryByText('View in SQL Lab')).not.toBeInTheDocument(); + expect(screen.getByText('Copy')).toBeInTheDocument(); // Copy button should still be visible +}); diff --git a/superset-frontend/src/explore/components/controls/ViewQuery.tsx b/superset-frontend/src/explore/components/controls/ViewQuery.tsx index dca607e8461..ab6de84ce22 100644 --- a/superset-frontend/src/explore/components/controls/ViewQuery.tsx +++ b/superset-frontend/src/explore/components/controls/ViewQuery.tsx @@ -24,11 +24,14 @@ import { useEffect, useState, } from 'react'; +import { useSelector } from 'react-redux'; import rison from 'rison'; import { styled, SupersetClient, t } from '@superset-ui/core'; import { Icons, Switch, Button, Skeleton } from '@superset-ui/core/components'; import { CopyToClipboard } from 'src/components'; +import { RootState } from 'src/dashboard/types'; import { CopyButton } from 'src/explore/components/DataTableControl'; +import { findPermission } from 'src/utils/findPermission'; import CodeSyntaxHighlighter, { SupportedLanguage, preloadLanguages, @@ -89,6 +92,9 @@ const ViewQuery: FC = props => { const [showFormatSQL, setShowFormatSQL] = useState(true); const history = useHistory(); const currentSQL = (showFormatSQL ? formattedSQL : sql) ?? sql; + const canAccessSQLLab = useSelector((state: RootState) => + findPermission('menu_access', 'SQL Lab', state.user?.roles), + ); // Preload the language when component mounts to ensure smooth experience useEffect(() => { @@ -162,7 +168,9 @@ const ViewQuery: FC = props => { } /> - + {canAccessSQLLab && ( + + )}