You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by mi...@apache.org on 2026/08/05 01:32:20 UTC
(superset) branch master updated: fix(sqllab): retry table metadata preview after OAuth2 redirect (#42390)
This is an automated email from the ASF dual-hosted git repository.
aminghadersohi pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/superset.git
The following commit(s) were added to refs/heads/master by this push:
new 22909bb039e fix(sqllab): retry table metadata preview after OAuth2 redirect (#42390)
22909bb039e is described below
commit 22909bb039e5447d72b13f7660e578bf4754d019
Author: Amin Ghadersohi <am...@gmail.com>
AuthorDate: Tue Aug 4 20:32:13 2026 -0500
fix(sqllab): retry table metadata preview after OAuth2 redirect (#42390)
Co-authored-by: Claude <no...@anthropic.com>
---
.../components/TablePreview/TablePreview.test.tsx | 108 +++++++++++++++++++++
.../src/SqlLab/components/TablePreview/index.tsx | 33 +++++--
.../ErrorMessage/OAuth2RedirectMessage.test.tsx | 1 +
.../ErrorMessage/OAuth2RedirectMessage.tsx | 1 +
4 files changed, 135 insertions(+), 8 deletions(-)
diff --git a/superset-frontend/src/SqlLab/components/TablePreview/TablePreview.test.tsx b/superset-frontend/src/SqlLab/components/TablePreview/TablePreview.test.tsx
index 07787441f68..32010ea9786 100644
--- a/superset-frontend/src/SqlLab/components/TablePreview/TablePreview.test.tsx
+++ b/superset-frontend/src/SqlLab/components/TablePreview/TablePreview.test.tsx
@@ -18,7 +18,9 @@
*/
import { type ReactChild } from 'react';
import fetchMock from 'fetch-mock';
+import { ErrorTypeEnum } from '@superset-ui/core';
import { table, initialState } from 'src/SqlLab/fixtures';
+import setupErrorMessages from 'src/setup/setupErrorMessages';
import {
render,
waitFor,
@@ -81,6 +83,17 @@ const mockedProps = {
tableName: table.name,
};
+const OAUTH2_TAB_ID = 'tab-1';
+const oauth2Error = {
+ message: 'The database is currently not authenticated',
+ error_type: ErrorTypeEnum.OAUTH2_REDIRECT,
+ level: 'warning',
+ extra: {
+ url: 'https://example.com/oauth2/authorize',
+ tab_id: OAUTH2_TAB_ID,
+ },
+};
+
test('renders columns', async () => {
const { getAllByTestId, queryByText } = render(
<TablePreview {...mockedProps} />,
@@ -139,6 +152,101 @@ test('renders preview', async () => {
);
});
+test('renders an OAuth2 authorization prompt when metadata errors with OAUTH2_REDIRECT', async () => {
+ // Register the OAuth2 redirect component so ErrorMessageWithStackTrace can
+ // resolve it for the OAUTH2_REDIRECT error type, mirroring app setup.
+ setupErrorMessages();
+ fetchMock.removeRoutes();
+ fetchMock.get(getTableMetadataEndpoint, {
+ status: 403,
+ body: { errors: [oauth2Error] },
+ });
+ fetchMock.get(getExtraTableMetadataEndpoint, {});
+
+ render(<TablePreview {...mockedProps} />, { useRedux: true, initialState });
+
+ // The structured SupersetError must flow through so the OAuth2 redirect link
+ // renders (and the crud-source retry can re-fetch once the dance completes).
+ const authLink = await screen.findByRole('link', {
+ name: 'provide authorization',
+ });
+ expect(authLink).toHaveAttribute(
+ 'href',
+ 'https://example.com/oauth2/authorize',
+ );
+});
+
+test('surfaces an OAUTH2_REDIRECT from the extended metadata request when the main request fails generically', async () => {
+ setupErrorMessages();
+ fetchMock.removeRoutes();
+ fetchMock.get(getTableMetadataEndpoint, {
+ status: 500,
+ body: { message: 'Something went wrong' },
+ });
+ fetchMock.get(getExtraTableMetadataEndpoint, {
+ status: 403,
+ body: { errors: [oauth2Error] },
+ });
+
+ render(<TablePreview {...mockedProps} />, { useRedux: true, initialState });
+
+ // The generic failure on the main request must not mask the actionable
+ // OAuth2 payload coming from the extended metadata request.
+ expect(
+ await screen.findByRole('link', { name: 'provide authorization' }),
+ ).toBeInTheDocument();
+});
+
+test('repopulates the preview once the OAuth2 dance completes', async () => {
+ setupErrorMessages();
+ fetchMock.removeRoutes();
+ fetchMock.get(getTableMetadataEndpoint, {
+ status: 403,
+ body: { errors: [oauth2Error] },
+ });
+ fetchMock.get(getExtraTableMetadataEndpoint, {});
+
+ const { getAllByTestId } = render(<TablePreview {...mockedProps} />, {
+ useRedux: true,
+ initialState,
+ });
+ await screen.findByRole('link', { name: 'provide authorization' });
+
+ // The second tab finished authorizing: the database now answers with the
+ // metadata, and the completion broadcast must drive the refetch.
+ fetchMock.removeRoutes();
+ fetchMock.get(getTableMetadataEndpoint, table);
+ fetchMock.get(getExtraTableMetadataEndpoint, {});
+ fireEvent(
+ window,
+ Object.assign(new Event('storage'), {
+ key: 'oauth2_auth_complete',
+ newValue: JSON.stringify({ tabId: OAUTH2_TAB_ID }),
+ }),
+ );
+
+ await waitFor(() =>
+ expect(getAllByTestId('mock-record-row')).toHaveLength(
+ table.columns.length,
+ ),
+ );
+});
+
+test('renders the error message when metadata fails without a structured error', async () => {
+ fetchMock.removeRoutes();
+ fetchMock.get(getTableMetadataEndpoint, {
+ status: 500,
+ body: { message: 'Something went wrong' },
+ });
+ fetchMock.get(getExtraTableMetadataEndpoint, {});
+
+ render(<TablePreview {...mockedProps} />, { useRedux: true, initialState });
+
+ // Without an errors[] array there is no SupersetError to hand to the
+ // registry, so the plain message must still surface to the user.
+ expect(await screen.findByText('Something went wrong')).toBeInTheDocument();
+});
+
// eslint-disable-next-line no-restricted-globals -- TODO: Migrate from describe blocks
describe('table actions', () => {
test('refreshes table metadata when triggered', async () => {
diff --git a/superset-frontend/src/SqlLab/components/TablePreview/index.tsx b/superset-frontend/src/SqlLab/components/TablePreview/index.tsx
index bc53ab7f61e..6aab1e5deb6 100644
--- a/superset-frontend/src/SqlLab/components/TablePreview/index.tsx
+++ b/superset-frontend/src/SqlLab/components/TablePreview/index.tsx
@@ -34,7 +34,11 @@ import {
import AutoSizer from 'react-virtualized-auto-sizer';
import { Icons } from '@superset-ui/core/components/Icons';
import type { SqlLabRootState } from 'src/SqlLab/types';
-import { CopyToClipboard, FilterableTable } from 'src/components';
+import {
+ CopyToClipboard,
+ ErrorMessageWithStackTrace,
+ FilterableTable,
+} from 'src/components';
import Tabs from '@superset-ui/core/components/Tabs';
import {
tableApiUtil,
@@ -257,13 +261,26 @@ const TablePreview: FC<Props> = ({ dbId, catalog, schema, tableName }) => {
}
if (hasMetadataError || metadataExtrError) {
- return (
- <Alert
- type="warning"
- message={
- ((metadataError || metadataExtrError) as ClientErrorObject)?.error
- }
- />
+ // Pass the structured SupersetError through to ErrorMessageWithStackTrace so
+ // that OAuth2 redirect errors (error_type=OAUTH2_REDIRECT) render the
+ // "Authorization needed" link and auto-retry once the OAuth2 dance completes.
+ // The source="crud" value drives the tag-invalidation retry in
+ // OAuth2RedirectMessage.tsx (which invalidates the TableMetadatas tag).
+ // Errors without a structured errors[] array (raw 500s, timeouts, network
+ // failures) fall back to the plain message so it isn't swallowed.
+ // Both requests can fail at once, so pick whichever carries a structured
+ // payload rather than always preferring the main request — otherwise a
+ // generic failure there would mask an actionable OAUTH2_REDIRECT from the
+ // extended-metadata request.
+ const clientErrors = [metadataError, metadataExtrError].filter(
+ Boolean,
+ ) as ClientErrorObject[];
+ const errorPayload = clientErrors.find(({ errors }) => errors?.length)
+ ?.errors?.[0];
+ return errorPayload ? (
+ <ErrorMessageWithStackTrace error={errorPayload} source="crud" />
+ ) : (
+ <Alert type="warning" message={clientErrors[0]?.error} />
);
}
if (!data) {
diff --git a/superset-frontend/src/components/ErrorMessage/OAuth2RedirectMessage.test.tsx b/superset-frontend/src/components/ErrorMessage/OAuth2RedirectMessage.test.tsx
index 89726c8af9d..e19492fcc7d 100644
--- a/superset-frontend/src/components/ErrorMessage/OAuth2RedirectMessage.test.tsx
+++ b/superset-frontend/src/components/ErrorMessage/OAuth2RedirectMessage.test.tsx
@@ -271,6 +271,7 @@ describe('OAuth2RedirectMessage Component', () => {
{ type: 'Schemas', id: 'LIST' },
{ type: 'Catalogs', id: 'LIST' },
'Tables',
+ 'TableMetadatas',
]);
});
});
diff --git a/superset-frontend/src/components/ErrorMessage/OAuth2RedirectMessage.tsx b/superset-frontend/src/components/ErrorMessage/OAuth2RedirectMessage.tsx
index 5466c2adfe7..a03bcf0a2b9 100644
--- a/superset-frontend/src/components/ErrorMessage/OAuth2RedirectMessage.tsx
+++ b/superset-frontend/src/components/ErrorMessage/OAuth2RedirectMessage.tsx
@@ -126,6 +126,7 @@ export function OAuth2RedirectMessage({
{ type: 'Schemas', id: 'LIST' },
{ type: 'Catalogs', id: 'LIST' },
'Tables',
+ 'TableMetadatas',
]),
);
} else {