feat: Oauth2 in DatabaseSelector (#30082)
This commit is contained in:
parent
0415ed34ce
commit
09dfe2f2ab
|
|
@ -24,10 +24,11 @@ import {
|
||||||
useRef,
|
useRef,
|
||||||
useCallback,
|
useCallback,
|
||||||
} from 'react';
|
} 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 type { LabeledValue as AntdLabeledValue } from 'antd/lib/select';
|
||||||
import rison from 'rison';
|
import rison from 'rison';
|
||||||
import { AsyncSelect, Select } from 'src/components';
|
import { AsyncSelect, Select } from 'src/components';
|
||||||
|
import ErrorMessageWithStackTrace from 'src/components/ErrorMessage/ErrorMessageWithStackTrace';
|
||||||
import Label from 'src/components/Label';
|
import Label from 'src/components/Label';
|
||||||
import { FormLabel } from 'src/components/Form';
|
import { FormLabel } from 'src/components/Form';
|
||||||
import RefreshLabel from 'src/components/RefreshLabel';
|
import RefreshLabel from 'src/components/RefreshLabel';
|
||||||
|
|
@ -154,6 +155,7 @@ export default function DatabaseSelector({
|
||||||
}: DatabaseSelectorProps) {
|
}: DatabaseSelectorProps) {
|
||||||
const showCatalogSelector = !!db?.allow_multi_catalog;
|
const showCatalogSelector = !!db?.allow_multi_catalog;
|
||||||
const [currentDb, setCurrentDb] = useState<DatabaseValue | undefined>();
|
const [currentDb, setCurrentDb] = useState<DatabaseValue | undefined>();
|
||||||
|
const [errorPayload, setErrorPayload] = useState<SupersetError | null>();
|
||||||
const [currentCatalog, setCurrentCatalog] = useState<
|
const [currentCatalog, setCurrentCatalog] = useState<
|
||||||
CatalogOption | null | undefined
|
CatalogOption | null | undefined
|
||||||
>(catalog ? { label: catalog, value: catalog, title: catalog } : undefined);
|
>(catalog ? { label: catalog, value: catalog, title: catalog } : undefined);
|
||||||
|
|
@ -267,6 +269,7 @@ export default function DatabaseSelector({
|
||||||
dbId: currentDb?.value,
|
dbId: currentDb?.value,
|
||||||
catalog: currentCatalog?.value,
|
catalog: currentCatalog?.value,
|
||||||
onSuccess: (schemas, isFetched) => {
|
onSuccess: (schemas, isFetched) => {
|
||||||
|
setErrorPayload(null);
|
||||||
if (schemas.length === 1) {
|
if (schemas.length === 1) {
|
||||||
changeSchema(schemas[0]);
|
changeSchema(schemas[0]);
|
||||||
} else if (
|
} else if (
|
||||||
|
|
@ -279,7 +282,13 @@ export default function DatabaseSelector({
|
||||||
addSuccessToast('List refreshed');
|
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;
|
const schemaOptions = schemaData || EMPTY_SCHEMA_OPTIONS;
|
||||||
|
|
@ -299,6 +308,7 @@ export default function DatabaseSelector({
|
||||||
} = useCatalogs({
|
} = useCatalogs({
|
||||||
dbId: showCatalogSelector ? currentDb?.value : undefined,
|
dbId: showCatalogSelector ? currentDb?.value : undefined,
|
||||||
onSuccess: (catalogs, isFetched) => {
|
onSuccess: (catalogs, isFetched) => {
|
||||||
|
setErrorPayload(null);
|
||||||
if (!showCatalogSelector) {
|
if (!showCatalogSelector) {
|
||||||
changeCatalog(null);
|
changeCatalog(null);
|
||||||
} else if (catalogs.length === 1) {
|
} else if (catalogs.length === 1) {
|
||||||
|
|
@ -315,9 +325,13 @@ export default function DatabaseSelector({
|
||||||
addSuccessToast('List refreshed');
|
addSuccessToast('List refreshed');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onError: () => {
|
onError: error => {
|
||||||
if (showCatalogSelector) {
|
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 ? (
|
||||||
|
<ErrorMessageWithStackTrace error={errorPayload} source="crud" />
|
||||||
|
) : null;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DatabaseSelectorWrapper data-test="DatabaseSelector">
|
<DatabaseSelectorWrapper data-test="DatabaseSelector">
|
||||||
{renderDatabaseSelect()}
|
{renderDatabaseSelect()}
|
||||||
|
{renderError()}
|
||||||
{showCatalogSelector && renderCatalogSelect()}
|
{showCatalogSelector && renderCatalogSelect()}
|
||||||
{renderSchemaSelect()}
|
{renderSchemaSelect()}
|
||||||
</DatabaseSelectorWrapper>
|
</DatabaseSelectorWrapper>
|
||||||
|
|
|
||||||
|
|
@ -162,7 +162,7 @@ function OAuth2RedirectMessage({
|
||||||
>
|
>
|
||||||
provide authorization
|
provide authorization
|
||||||
</a>{' '}
|
</a>{' '}
|
||||||
in order to run this query.
|
in order to run this operation.
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -22,16 +22,19 @@ import { FieldPropTypes } from '../../types';
|
||||||
|
|
||||||
const FIELD_TEXT_MAP = {
|
const FIELD_TEXT_MAP = {
|
||||||
account: {
|
account: {
|
||||||
|
label: 'Account',
|
||||||
helpText: t(
|
helpText: t(
|
||||||
'Copy the identifier of the account you are trying to connect to.',
|
'Copy the identifier of the account you are trying to connect to.',
|
||||||
),
|
),
|
||||||
placeholder: t('e.g. xy12345.us-east-2.aws'),
|
placeholder: t('e.g. xy12345.us-east-2.aws'),
|
||||||
},
|
},
|
||||||
warehouse: {
|
warehouse: {
|
||||||
|
label: 'Warehouse',
|
||||||
placeholder: t('e.g. compute_wh'),
|
placeholder: t('e.g. compute_wh'),
|
||||||
className: 'form-group-w-50',
|
className: 'form-group-w-50',
|
||||||
},
|
},
|
||||||
role: {
|
role: {
|
||||||
|
label: 'Role',
|
||||||
placeholder: t('e.g. AccountAdmin'),
|
placeholder: t('e.g. AccountAdmin'),
|
||||||
className: 'form-group-w-50',
|
className: 'form-group-w-50',
|
||||||
},
|
},
|
||||||
|
|
@ -54,7 +57,7 @@ export const validatedInputField = ({
|
||||||
errorMessage={validationErrors?.[field]}
|
errorMessage={validationErrors?.[field]}
|
||||||
placeholder={FIELD_TEXT_MAP[field].placeholder}
|
placeholder={FIELD_TEXT_MAP[field].placeholder}
|
||||||
helpText={FIELD_TEXT_MAP[field].helpText}
|
helpText={FIELD_TEXT_MAP[field].helpText}
|
||||||
label={field}
|
label={FIELD_TEXT_MAP[field].label || field}
|
||||||
onChange={changeMethods.onParametersChange}
|
onChange={changeMethods.onParametersChange}
|
||||||
className={FIELD_TEXT_MAP[field].className || field}
|
className={FIELD_TEXT_MAP[field].className || field}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
import { useCallback, useEffect } from 'react';
|
import { useCallback, useEffect } from 'react';
|
||||||
|
import { ClientErrorObject } from '@superset-ui/core';
|
||||||
import useEffectEvent from 'src/hooks/useEffectEvent';
|
import useEffectEvent from 'src/hooks/useEffectEvent';
|
||||||
import { api, JsonResponse } from './queryApi';
|
import { api, JsonResponse } from './queryApi';
|
||||||
|
|
||||||
|
|
@ -30,7 +31,7 @@ export type FetchCatalogsQueryParams = {
|
||||||
dbId?: string | number;
|
dbId?: string | number;
|
||||||
forceRefresh: boolean;
|
forceRefresh: boolean;
|
||||||
onSuccess?: (data: CatalogOption[], isRefetched: boolean) => void;
|
onSuccess?: (data: CatalogOption[], isRefetched: boolean) => void;
|
||||||
onError?: () => void;
|
onError?: (error: ClientErrorObject) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
type Params = Omit<FetchCatalogsQueryParams, 'forceRefresh'>;
|
type Params = Omit<FetchCatalogsQueryParams, 'forceRefresh'>;
|
||||||
|
|
@ -77,6 +78,12 @@ export function useCatalogs(options: Params) {
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (result.isError) {
|
||||||
|
onError?.(result.error as ClientErrorObject);
|
||||||
|
}
|
||||||
|
}, [result.isError, result.error, onError]);
|
||||||
|
|
||||||
const fetchData = useEffectEvent(
|
const fetchData = useEffectEvent(
|
||||||
(dbId: FetchCatalogsQueryParams['dbId'], forceRefresh = false) => {
|
(dbId: FetchCatalogsQueryParams['dbId'], forceRefresh = false) => {
|
||||||
if (dbId && (!result.currentData || forceRefresh)) {
|
if (dbId && (!result.currentData || forceRefresh)) {
|
||||||
|
|
@ -85,7 +92,7 @@ export function useCatalogs(options: Params) {
|
||||||
onSuccess?.(data || EMPTY_CATALOGS, forceRefresh);
|
onSuccess?.(data || EMPTY_CATALOGS, forceRefresh);
|
||||||
}
|
}
|
||||||
if (isError) {
|
if (isError) {
|
||||||
onError?.();
|
onError?.(result.error as ClientErrorObject);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -82,7 +82,7 @@ test('supersetClientQuery should return error when unsuccessful', async () => {
|
||||||
getBaseQueryApiMock(store),
|
getBaseQueryApiMock(store),
|
||||||
{},
|
{},
|
||||||
);
|
);
|
||||||
expect(result.error).toEqual({ error: expectedError });
|
expect(result.error).toEqual({ error: expectedError, errors: [] });
|
||||||
});
|
});
|
||||||
|
|
||||||
test('supersetClientQuery should return parsed response by parseMethod', async () => {
|
test('supersetClientQuery should return parsed response by parseMethod', async () => {
|
||||||
|
|
|
||||||
|
|
@ -64,6 +64,7 @@ export const supersetClientQuery: BaseQueryFn<
|
||||||
getClientErrorObject(response).then(errorObj => ({
|
getClientErrorObject(response).then(errorObj => ({
|
||||||
error: {
|
error: {
|
||||||
error: errorObj?.message || errorObj?.error || response.statusText,
|
error: errorObj?.message || errorObj?.error || response.statusText,
|
||||||
|
errors: errorObj?.errors || [], // used by <ErrorMessageWithStackTrace />
|
||||||
status: response.status,
|
status: response.status,
|
||||||
},
|
},
|
||||||
})),
|
})),
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@
|
||||||
* under the License.
|
* under the License.
|
||||||
*/
|
*/
|
||||||
import { useCallback, useEffect } from 'react';
|
import { useCallback, useEffect } from 'react';
|
||||||
|
import { ClientErrorObject } from '@superset-ui/core';
|
||||||
import useEffectEvent from 'src/hooks/useEffectEvent';
|
import useEffectEvent from 'src/hooks/useEffectEvent';
|
||||||
import { api, JsonResponse } from './queryApi';
|
import { api, JsonResponse } from './queryApi';
|
||||||
|
|
||||||
|
|
@ -31,7 +32,7 @@ export type FetchSchemasQueryParams = {
|
||||||
catalog?: string;
|
catalog?: string;
|
||||||
forceRefresh: boolean;
|
forceRefresh: boolean;
|
||||||
onSuccess?: (data: SchemaOption[], isRefetched: boolean) => void;
|
onSuccess?: (data: SchemaOption[], isRefetched: boolean) => void;
|
||||||
onError?: () => void;
|
onError?: (error: ClientErrorObject) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
type Params = Omit<FetchSchemasQueryParams, 'forceRefresh'>;
|
type Params = Omit<FetchSchemasQueryParams, 'forceRefresh'>;
|
||||||
|
|
@ -81,6 +82,12 @@ export function useSchemas(options: Params) {
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (result.isError) {
|
||||||
|
onError?.(result.error as ClientErrorObject);
|
||||||
|
}
|
||||||
|
}, [result.isError, result.error, onError]);
|
||||||
|
|
||||||
const fetchData = useEffectEvent(
|
const fetchData = useEffectEvent(
|
||||||
(
|
(
|
||||||
dbId: FetchSchemasQueryParams['dbId'],
|
dbId: FetchSchemasQueryParams['dbId'],
|
||||||
|
|
@ -94,7 +101,7 @@ export function useSchemas(options: Params) {
|
||||||
onSuccess?.(data || EMPTY_SCHEMAS, forceRefresh);
|
onSuccess?.(data || EMPTY_SCHEMAS, forceRefresh);
|
||||||
}
|
}
|
||||||
if (isError) {
|
if (isError) {
|
||||||
onError?.();
|
onError?.(result.error as ClientErrorObject);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue