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 d79ad5622aeeae8307117491570d1989e1ea6f47
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      | 12 ++++----
 .../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, 77 insertions(+), 24 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..b4c4fe9841 100644
--- a/superset-frontend/src/components/DatabaseSelector/index.tsx
+++ b/superset-frontend/src/components/DatabaseSelector/index.tsx
@@ -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,7 +282,7 @@ 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);
@@ -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,7 +322,7 @@ export default function DatabaseSelector({
     },
   });
 
-  const catalogOptions = catalogData || EMPTY_CATALOG_OPTIONS;
+  const catalogOptions = catalogData?.catalogs || EMPTY_CATALOG_OPTIONS;
 
   function changeDatabase(
     value: { label: string; value: number },
diff --git a/superset-frontend/src/hooks/apiResources/catalogs.ts 
b/superset-frontend/src/hooks/apiResources/catalogs.ts
index 5203797156..7b2a4861b4 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.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):

Reply via email to