This is an automated email from the ASF dual-hosted git repository. beto pushed a commit to branch default-schema-catalog in repository https://gitbox.apache.org/repos/asf/superset.git
commit 0e1dd8a6001fef8a35816e0b79362e643fd7dfea Author: Beto Dealmeida <[email protected]> AuthorDate: Mon Jul 8 17:54:18 2024 -0400 feat: auto-select default catalog and schema --- .../components/AceEditorWrapper/useKeywords.ts | 2 +- .../src/components/DatabaseSelector/index.tsx | 28 +++++++++---------- .../src/hooks/apiResources/catalogs.ts | 32 ++++++++++++++++++---- .../src/hooks/apiResources/schemas.ts | 32 ++++++++++++++++++---- superset-frontend/src/hooks/apiResources/tables.ts | 4 +-- superset/databases/api.py | 11 ++++---- superset/databases/schemas.py | 8 ++++++ 7 files changed, 85 insertions(+), 32 deletions(-) diff --git a/superset-frontend/src/SqlLab/components/AceEditorWrapper/useKeywords.ts b/superset-frontend/src/SqlLab/components/AceEditorWrapper/useKeywords.ts index df45290f6c..7c031b2e2e 100644 --- a/superset-frontend/src/SqlLab/components/AceEditorWrapper/useKeywords.ts +++ b/superset-frontend/src/SqlLab/components/AceEditorWrapper/useKeywords.ts @@ -164,7 +164,7 @@ export function useKeywords( const schemaKeywords = useMemo( () => - (schemaOptions ?? []).map(s => ({ + (schemaOptions?.schemas ?? []).map(s => ({ name: s.label, value: s.value, score: SCHEMA_AUTOCOMPLETE_SCORE, diff --git a/superset-frontend/src/components/DatabaseSelector/index.tsx b/superset-frontend/src/components/DatabaseSelector/index.tsx index 4cf8411568..8f49e11b70 100644 --- a/superset-frontend/src/components/DatabaseSelector/index.tsx +++ b/superset-frontend/src/components/DatabaseSelector/index.tsx @@ -159,9 +159,9 @@ export default function DatabaseSelector({ >(catalog ? { label: catalog, value: catalog, title: catalog } : undefined); const catalogRef = useRef(catalog); catalogRef.current = catalog; - const [currentSchema, setCurrentSchema] = useState<SchemaOption | undefined>( - schema ? { label: schema, value: schema, title: schema } : undefined, - ); + const [currentSchema, setCurrentSchema] = useState< + SchemaOption | null | undefined + >(schema ? { label: schema, value: schema, title: schema } : undefined); const schemaRef = useRef(schema); schemaRef.current = schema; const { addSuccessToast } = useToasts(); @@ -266,13 +266,13 @@ export default function DatabaseSelector({ } = useSchemas({ dbId: currentDb?.value, catalog: currentCatalog?.value, - onSuccess: (schemas, isFetched) => { + onSuccess: (schemas, isFetched, defaultSchema) => { if (schemas.length === 1) { changeSchema(schemas[0]); } else if ( !schemas.find(schemaOption => schemaRef.current === schemaOption.value) ) { - changeSchema(undefined); + changeSchema(defaultSchema || undefined); } if (isFetched) { @@ -282,11 +282,11 @@ export default function DatabaseSelector({ onError: () => handleError(t('There was an error loading the schemas')), }); - const schemaOptions = schemaData || EMPTY_SCHEMA_OPTIONS; + const schemaOptions = schemaData?.schemas || EMPTY_SCHEMA_OPTIONS; function changeCatalog(catalog: CatalogOption | null | undefined) { setCurrentCatalog(catalog); - setCurrentSchema(undefined); + setCurrentSchema(schemaData?.defaultSchema || undefined); if (onCatalogChange && catalog?.value !== catalogRef.current) { onCatalogChange(catalog?.value); } @@ -298,7 +298,7 @@ export default function DatabaseSelector({ refetch: refetchCatalogs, } = useCatalogs({ dbId: showCatalogSelector ? currentDb?.value : undefined, - onSuccess: (catalogs, isFetched) => { + onSuccess: (catalogs, isFetched, defaultCatalog) => { if (!showCatalogSelector) { changeCatalog(null); } else if (catalogs.length === 1) { @@ -308,7 +308,7 @@ export default function DatabaseSelector({ catalogOption => catalogRef.current === catalogOption.value, ) ) { - changeCatalog(undefined); + changeCatalog(defaultCatalog || undefined); } if (showCatalogSelector && isFetched) { @@ -322,23 +322,23 @@ export default function DatabaseSelector({ }, }); - const catalogOptions = catalogData || EMPTY_CATALOG_OPTIONS; + const catalogOptions = catalogData?.catalogs || EMPTY_CATALOG_OPTIONS; function changeDatabase( value: { label: string; value: number }, database: DatabaseValue, ) { setCurrentDb(database); - setCurrentCatalog(undefined); - setCurrentSchema(undefined); + setCurrentCatalog(catalogData?.defaultCatalog || undefined); + setCurrentSchema(schemaData?.defaultSchema || undefined); if (onDbChange) { onDbChange(database); } if (onCatalogChange) { - onCatalogChange(undefined); + onCatalogChange(catalogData?.defaultCatalog?.value || undefined); } if (onSchemaChange) { - onSchemaChange(undefined); + onSchemaChange(schemaData?.defaultSchema?.value || undefined); } } diff --git a/superset-frontend/src/hooks/apiResources/catalogs.ts b/superset-frontend/src/hooks/apiResources/catalogs.ts index 5203797156..de26b426b8 100644 --- a/superset-frontend/src/hooks/apiResources/catalogs.ts +++ b/superset-frontend/src/hooks/apiResources/catalogs.ts @@ -26,10 +26,19 @@ export type CatalogOption = { title: string; }; +export type CatalogResponse = { + catalogs: CatalogOption[]; + defaultCatalog: CatalogOption | null; +}; + export type FetchCatalogsQueryParams = { dbId?: string | number; forceRefresh: boolean; - onSuccess?: (data: CatalogOption[], isRefetched: boolean) => void; + onSuccess?: ( + data: CatalogOption[], + isRefetched: boolean, + defaultCatalog: CatalogOption | null, + ) => void; onError?: () => void; }; @@ -37,19 +46,28 @@ type Params = Omit<FetchCatalogsQueryParams, 'forceRefresh'>; const catalogApi = api.injectEndpoints({ endpoints: builder => ({ - catalogs: builder.query<CatalogOption[], FetchCatalogsQueryParams>({ + catalogs: builder.query<CatalogResponse, FetchCatalogsQueryParams>({ providesTags: [{ type: 'Catalogs', id: 'LIST' }], query: ({ dbId, forceRefresh }) => ({ endpoint: `/api/v1/database/${dbId}/catalogs/`, urlParams: { force: forceRefresh, }, - transformResponse: ({ json }: JsonResponse) => - json.result.sort().map((value: string) => ({ + transformResponse: ({ json }: JsonResponse) => ({ + catalogs: json.result.sort().map((value: string) => ({ value, label: value, title: value, })), + defaultCatalog: + json.default !== null + ? ({ + value: json.default, + label: json.default, + title: json.default, + } as CatalogOption) + : null, + }), }), serializeQueryArgs: ({ queryArgs: { dbId } }) => ({ dbId, @@ -82,7 +100,11 @@ export function useCatalogs(options: Params) { if (dbId && (!result.currentData || forceRefresh)) { trigger({ dbId, forceRefresh }).then(({ isSuccess, isError, data }) => { if (isSuccess) { - onSuccess?.(data || EMPTY_CATALOGS, forceRefresh); + onSuccess?.( + data.catalogs || EMPTY_CATALOGS, + forceRefresh, + data.defaultCatalog, + ); } if (isError) { onError?.(); diff --git a/superset-frontend/src/hooks/apiResources/schemas.ts b/superset-frontend/src/hooks/apiResources/schemas.ts index e60e62a7fb..945fd34935 100644 --- a/superset-frontend/src/hooks/apiResources/schemas.ts +++ b/superset-frontend/src/hooks/apiResources/schemas.ts @@ -26,11 +26,20 @@ export type SchemaOption = { title: string; }; +export type SchemaResponse = { + schemas: SchemaOption[]; + defaultSchema: SchemaOption | null; +}; + export type FetchSchemasQueryParams = { dbId?: string | number; catalog?: string; forceRefresh: boolean; - onSuccess?: (data: SchemaOption[], isRefetched: boolean) => void; + onSuccess?: ( + data: SchemaOption[], + isRefetched: boolean, + defaultSchema: SchemaOption | null, + ) => void; onError?: () => void; }; @@ -38,7 +47,7 @@ type Params = Omit<FetchSchemasQueryParams, 'forceRefresh'>; const schemaApi = api.injectEndpoints({ endpoints: builder => ({ - schemas: builder.query<SchemaOption[], FetchSchemasQueryParams>({ + schemas: builder.query<SchemaResponse, FetchSchemasQueryParams>({ providesTags: [{ type: 'Schemas', id: 'LIST' }], query: ({ dbId, catalog, forceRefresh }) => ({ endpoint: `/api/v1/database/${dbId}/schemas/`, @@ -47,12 +56,21 @@ const schemaApi = api.injectEndpoints({ force: forceRefresh, ...(catalog !== undefined && { catalog }), }, - transformResponse: ({ json }: JsonResponse) => - json.result.sort().map((value: string) => ({ + transformResponse: ({ json }: JsonResponse) => ({ + schemas: json.result.sort().map((value: string) => ({ value, label: value, title: value, })), + defaultSchema: + json.default !== null + ? ({ + value: json.default, + label: json.default, + title: json.default, + } as SchemaOption) + : null, + }), }), serializeQueryArgs: ({ queryArgs: { dbId, catalog } }) => ({ dbId, @@ -91,7 +109,11 @@ export function useSchemas(options: Params) { trigger({ dbId, catalog, forceRefresh }).then( ({ isSuccess, isError, data }) => { if (isSuccess) { - onSuccess?.(data || EMPTY_SCHEMAS, forceRefresh); + onSuccess?.( + data.schemas || EMPTY_SCHEMAS, + forceRefresh, + data.defaultSchema, + ); } if (isError) { onError?.(); diff --git a/superset-frontend/src/hooks/apiResources/tables.ts b/superset-frontend/src/hooks/apiResources/tables.ts index d90c528b40..4666264d8e 100644 --- a/superset-frontend/src/hooks/apiResources/tables.ts +++ b/superset-frontend/src/hooks/apiResources/tables.ts @@ -157,8 +157,8 @@ export function useTables(options: Params) { catalog: catalog || undefined, }); const schemaOptionsMap = useMemo( - () => new Set(schemaOptions?.map(({ value }) => value)), - [schemaOptions], + () => new Set(schemaOptions?.schemas.map(({ value }) => value)), + [schemaOptions?.schemas], ); const enabled = Boolean( diff --git a/superset/databases/api.py b/superset/databases/api.py index 3a672eb766..1a2fc0e6f9 100644 --- a/superset/databases/api.py +++ b/superset/databases/api.py @@ -665,7 +665,8 @@ class DatabaseRestApi(BaseSupersetModelRestApi): database, catalogs, ) - return self.response(200, result=list(catalogs)) + default_catalog = database.get_default_catalog() + return self.response(200, result=list(catalogs), default=default_catalog) except OperationalError: return self.response( 500, @@ -732,7 +733,9 @@ class DatabaseRestApi(BaseSupersetModelRestApi): catalog, schemas, ) - return self.response(200, result=list(schemas)) + default_catalog = database.get_default_catalog() + default_schema = database.get_default_schema(default_catalog) + return self.response(200, result=list(schemas), default=default_schema) except OperationalError: return self.response( 500, message="There was an error connecting to the database" @@ -1885,9 +1888,7 @@ class DatabaseRestApi(BaseSupersetModelRestApi): @protect() @statsd_metrics @event_logger.log_this_with_context( - action=lambda self, - *args, - **kwargs: f"{self.__class__.__name__}.columnar_upload", + action=lambda self, *args, **kwargs: f"{self.__class__.__name__}.columnar_upload", log_to_statsd=False, ) @requires_form_data diff --git a/superset/databases/schemas.py b/superset/databases/schemas.py index 7dbaf0243a..d099670e91 100644 --- a/superset/databases/schemas.py +++ b/superset/databases/schemas.py @@ -721,12 +721,20 @@ class SchemasResponseSchema(Schema): result = fields.List( fields.String(metadata={"description": "A database schema name"}) ) + default = fields.String( + allow_none=True, + metadata={"description": "The default schema"}, + ) class CatalogsResponseSchema(Schema): result = fields.List( fields.String(metadata={"description": "A database catalog name"}) ) + default = fields.String( + allow_none=True, + metadata={"description": "The default catalog"}, + ) class DatabaseTablesResponse(Schema):
