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 62291a997a91e35719ffa7f0c4e577d5179c83b3 Author: Beto Dealmeida <[email protected]> AuthorDate: Mon Jul 8 17:54:18 2024 -0400 feat: auto-select default catalog and schema --- .../src/components/DatabaseSelector/index.tsx | 16 ++++++++--- .../src/hooks/apiResources/catalogs.ts | 32 ++++++++++++++++++---- .../src/hooks/apiResources/schemas.ts | 32 ++++++++++++++++++---- superset/databases/api.py | 11 ++++---- superset/databases/schemas.py | 8 ++++++ 5 files changed, 80 insertions(+), 19 deletions(-) diff --git a/superset-frontend/src/components/DatabaseSelector/index.tsx b/superset-frontend/src/components/DatabaseSelector/index.tsx index 4cf8411568..8b8dbee982 100644 --- a/superset-frontend/src/components/DatabaseSelector/index.tsx +++ b/superset-frontend/src/components/DatabaseSelector/index.tsx @@ -266,12 +266,17 @@ 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) + !schemas.find( + schemaOption => schemaRef.current === schemaOption.value, + ) && + defaultSchema ) { + changeSchema(defaultSchema); + } else { changeSchema(undefined); } @@ -298,7 +303,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) { @@ -306,8 +311,11 @@ export default function DatabaseSelector({ } else if ( !catalogs.find( catalogOption => catalogRef.current === catalogOption.value, - ) + ) && + defaultCatalog ) { + changeCatalog(defaultCatalog); + } else { changeCatalog(undefined); } diff --git a/superset-frontend/src/hooks/apiResources/catalogs.ts b/superset-frontend/src/hooks/apiResources/catalogs.ts index 5203797156..d98302bf45 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) => ({ + catalog: json.result.sort().map((value: string) => ({ value, label: value, title: value, })), + defaultCatalog: + json.result.default !== null + ? ({ + value: json.result.default, + label: json.result.default, + title: json.result.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..05aebbb006 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.result.default !== null + ? ({ + value: json.result.default, + label: json.result.default, + title: json.result.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/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):
