From eff63d72cd97d997892edc0c063a738b4dd9685e Mon Sep 17 00:00:00 2001 From: Beto Dealmeida Date: Sat, 31 Aug 2024 19:19:44 -0400 Subject: [PATCH] feat: Oauth2 in DatabaseSelector --- .../src/components/DatabaseSelector/index.tsx | 29 ++++++++++++++++--- .../ErrorMessage/OAuth2RedirectMessage.tsx | 2 +- .../ValidatedInputField.tsx | 5 +++- .../src/hooks/apiResources/catalogs.ts | 11 +++++-- .../src/hooks/apiResources/queryApi.test.ts | 2 +- .../src/hooks/apiResources/queryApi.ts | 1 + .../src/hooks/apiResources/schemas.ts | 11 +++++-- 7 files changed, 50 insertions(+), 11 deletions(-) diff --git a/superset-frontend/src/components/DatabaseSelector/index.tsx b/superset-frontend/src/components/DatabaseSelector/index.tsx index 321d753bc5c51..3b677316a47eb 100644 --- a/superset-frontend/src/components/DatabaseSelector/index.tsx +++ b/superset-frontend/src/components/DatabaseSelector/index.tsx @@ -24,10 +24,11 @@ import { useRef, useCallback, } from 'react'; -import { styled, SupersetClient, t } from '@superset-ui/core'; +import { styled, SupersetClient, SupersetError, t } from '@superset-ui/core'; import type { LabeledValue as AntdLabeledValue } from 'antd/lib/select'; import rison from 'rison'; import { AsyncSelect, Select } from 'src/components'; +import ErrorMessageWithStackTrace from 'src/components/ErrorMessage/ErrorMessageWithStackTrace'; import Label from 'src/components/Label'; import { FormLabel } from 'src/components/Form'; import RefreshLabel from 'src/components/RefreshLabel'; @@ -154,6 +155,7 @@ export default function DatabaseSelector({ }: DatabaseSelectorProps) { const showCatalogSelector = !!db?.allow_multi_catalog; const [currentDb, setCurrentDb] = useState(); + const [errorPayload, setErrorPayload] = useState(); const [currentCatalog, setCurrentCatalog] = useState< CatalogOption | null | undefined >(catalog ? { label: catalog, value: catalog, title: catalog } : undefined); @@ -267,6 +269,7 @@ export default function DatabaseSelector({ dbId: currentDb?.value, catalog: currentCatalog?.value, onSuccess: (schemas, isFetched) => { + setErrorPayload(null); if (schemas.length === 1) { changeSchema(schemas[0]); } else if ( @@ -279,7 +282,13 @@ export default function DatabaseSelector({ addSuccessToast('List refreshed'); } }, - onError: () => handleError(t('There was an error loading the schemas')), + onError: error => { + if (error?.errors) { + setErrorPayload(error?.errors?.[0]); + } else { + handleError(t('There was an error loading the schemas')); + } + }, }); const schemaOptions = schemaData || EMPTY_SCHEMA_OPTIONS; @@ -299,6 +308,7 @@ export default function DatabaseSelector({ } = useCatalogs({ dbId: showCatalogSelector ? currentDb?.value : undefined, onSuccess: (catalogs, isFetched) => { + setErrorPayload(null); if (!showCatalogSelector) { changeCatalog(null); } else if (catalogs.length === 1) { @@ -315,9 +325,13 @@ export default function DatabaseSelector({ addSuccessToast('List refreshed'); } }, - onError: () => { + onError: error => { if (showCatalogSelector) { - handleError(t('There was an error loading the catalogs')); + if (error?.errors) { + setErrorPayload(error?.errors?.[0]); + } else { + handleError(t('There was an error loading the catalogs')); + } } }, }); @@ -423,9 +437,16 @@ export default function DatabaseSelector({ ); } + function renderError() { + return errorPayload ? ( + + ) : null; + } + return ( {renderDatabaseSelect()} + {renderError()} {showCatalogSelector && renderCatalogSelect()} {renderSchemaSelect()} diff --git a/superset-frontend/src/components/ErrorMessage/OAuth2RedirectMessage.tsx b/superset-frontend/src/components/ErrorMessage/OAuth2RedirectMessage.tsx index 3f5a4b3201d68..6b1c6731b908a 100644 --- a/superset-frontend/src/components/ErrorMessage/OAuth2RedirectMessage.tsx +++ b/superset-frontend/src/components/ErrorMessage/OAuth2RedirectMessage.tsx @@ -162,7 +162,7 @@ function OAuth2RedirectMessage({ > provide authorization {' '} - in order to run this query. + in order to run this operation. ); diff --git a/superset-frontend/src/features/databases/DatabaseModal/DatabaseConnectionForm/ValidatedInputField.tsx b/superset-frontend/src/features/databases/DatabaseModal/DatabaseConnectionForm/ValidatedInputField.tsx index 2899a545debe9..cb90034477e94 100644 --- a/superset-frontend/src/features/databases/DatabaseModal/DatabaseConnectionForm/ValidatedInputField.tsx +++ b/superset-frontend/src/features/databases/DatabaseModal/DatabaseConnectionForm/ValidatedInputField.tsx @@ -22,16 +22,19 @@ import { FieldPropTypes } from '../../types'; const FIELD_TEXT_MAP = { account: { + label: 'Account', helpText: t( 'Copy the identifier of the account you are trying to connect to.', ), placeholder: t('e.g. xy12345.us-east-2.aws'), }, warehouse: { + label: 'Warehouse', placeholder: t('e.g. compute_wh'), className: 'form-group-w-50', }, role: { + label: 'Role', placeholder: t('e.g. AccountAdmin'), className: 'form-group-w-50', }, @@ -54,7 +57,7 @@ export const validatedInputField = ({ errorMessage={validationErrors?.[field]} placeholder={FIELD_TEXT_MAP[field].placeholder} helpText={FIELD_TEXT_MAP[field].helpText} - label={field} + label={FIELD_TEXT_MAP[field].label || field} onChange={changeMethods.onParametersChange} className={FIELD_TEXT_MAP[field].className || field} /> diff --git a/superset-frontend/src/hooks/apiResources/catalogs.ts b/superset-frontend/src/hooks/apiResources/catalogs.ts index 52037971563b2..d5bbe9c697c71 100644 --- a/superset-frontend/src/hooks/apiResources/catalogs.ts +++ b/superset-frontend/src/hooks/apiResources/catalogs.ts @@ -17,6 +17,7 @@ * under the License. */ import { useCallback, useEffect } from 'react'; +import { ClientErrorObject } from '@superset-ui/core'; import useEffectEvent from 'src/hooks/useEffectEvent'; import { api, JsonResponse } from './queryApi'; @@ -30,7 +31,7 @@ export type FetchCatalogsQueryParams = { dbId?: string | number; forceRefresh: boolean; onSuccess?: (data: CatalogOption[], isRefetched: boolean) => void; - onError?: () => void; + onError?: (error: ClientErrorObject) => void; }; type Params = Omit; @@ -77,6 +78,12 @@ export function useCatalogs(options: Params) { }, ); + useEffect(() => { + if (result.isError) { + onError?.(result.error); + } + }, [result.isError, result.error, onError]); + const fetchData = useEffectEvent( (dbId: FetchCatalogsQueryParams['dbId'], forceRefresh = false) => { if (dbId && (!result.currentData || forceRefresh)) { @@ -85,7 +92,7 @@ export function useCatalogs(options: Params) { onSuccess?.(data || EMPTY_CATALOGS, forceRefresh); } if (isError) { - onError?.(); + onError?.(result.error); } }); } diff --git a/superset-frontend/src/hooks/apiResources/queryApi.test.ts b/superset-frontend/src/hooks/apiResources/queryApi.test.ts index 4fb71387752d9..9f395600ce75e 100644 --- a/superset-frontend/src/hooks/apiResources/queryApi.test.ts +++ b/superset-frontend/src/hooks/apiResources/queryApi.test.ts @@ -82,7 +82,7 @@ test('supersetClientQuery should return error when unsuccessful', async () => { getBaseQueryApiMock(store), {}, ); - expect(result.error).toEqual({ error: expectedError }); + expect(result.error).toEqual({ error: expectedError, errors: [] }); }); test('supersetClientQuery should return parsed response by parseMethod', async () => { diff --git a/superset-frontend/src/hooks/apiResources/queryApi.ts b/superset-frontend/src/hooks/apiResources/queryApi.ts index b0c0bf5781b37..d09174c9b87d2 100644 --- a/superset-frontend/src/hooks/apiResources/queryApi.ts +++ b/superset-frontend/src/hooks/apiResources/queryApi.ts @@ -64,6 +64,7 @@ export const supersetClientQuery: BaseQueryFn< getClientErrorObject(response).then(errorObj => ({ error: { error: errorObj?.message || errorObj?.error || response.statusText, + errors: errorObj?.errors || [], // used by status: response.status, }, })), diff --git a/superset-frontend/src/hooks/apiResources/schemas.ts b/superset-frontend/src/hooks/apiResources/schemas.ts index e60e62a7fbcb1..7c2a7f6129872 100644 --- a/superset-frontend/src/hooks/apiResources/schemas.ts +++ b/superset-frontend/src/hooks/apiResources/schemas.ts @@ -17,6 +17,7 @@ * under the License. */ import { useCallback, useEffect } from 'react'; +import { ClientErrorObject } from '@superset-ui/core'; import useEffectEvent from 'src/hooks/useEffectEvent'; import { api, JsonResponse } from './queryApi'; @@ -31,7 +32,7 @@ export type FetchSchemasQueryParams = { catalog?: string; forceRefresh: boolean; onSuccess?: (data: SchemaOption[], isRefetched: boolean) => void; - onError?: () => void; + onError?: (error: ClientErrorObject) => void; }; type Params = Omit; @@ -81,6 +82,12 @@ export function useSchemas(options: Params) { }, ); + useEffect(() => { + if (result.isError) { + onError?.(result.error); + } + }, [result.isError, result.error, onError]); + const fetchData = useEffectEvent( ( dbId: FetchSchemasQueryParams['dbId'], @@ -94,7 +101,7 @@ export function useSchemas(options: Params) { onSuccess?.(data || EMPTY_SCHEMAS, forceRefresh); } if (isError) { - onError?.(); + onError?.(result.error); } }, );