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 d2d70dc9317ec4ac121a64e0c7ad36ca1970dc10
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      | 62 ++++++++++++----------
 .../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, 104 insertions(+), 47 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..9f0266cf94 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();
@@ -233,25 +233,6 @@ export default function DatabaseSelector({
     [formMode, getDbList, sqlLabMode, onEmptyResults],
   );
 
-  useEffect(() => {
-    setCurrentDb(current =>
-      current?.id !== db?.id
-        ? db
-          ? {
-              label: (
-                <SelectLabel
-                  backend={db.backend}
-                  databaseName={db.database_name}
-                />
-              ),
-              value: db.id,
-              ...db,
-            }
-          : undefined
-        : current,
-    );
-  }, [db]);
-
   function changeSchema(schema: SchemaOption | undefined) {
     setCurrentSchema(schema);
     if (onSchemaChange && schema?.value !== schemaRef.current) {
@@ -266,13 +247,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 +263,12 @@ export default function DatabaseSelector({
     onError: () => handleError(t('There was an error loading the schemas')),
   });
 
-  const schemaOptions = schemaData || EMPTY_SCHEMA_OPTIONS;
+  console.log('schema', schemaData);
+  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 +280,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 +290,7 @@ export default function DatabaseSelector({
           catalogOption => catalogRef.current === catalogOption.value,
         )
       ) {
-        changeCatalog(undefined);
+        changeCatalog(defaultCatalog || undefined);
       }
 
       if (showCatalogSelector && isFetched) {
@@ -322,7 +304,29 @@ export default function DatabaseSelector({
     },
   });
 
-  const catalogOptions = catalogData || EMPTY_CATALOG_OPTIONS;
+  console.log('catalogs', catalogData);
+  const catalogOptions = currentCatalog ? catalogData?.catalogs || 
EMPTY_CATALOG_OPTIONS : EMPTY_CATALOG_OPTIONS;
+
+  useEffect(() => {
+    setCurrentDb(current =>
+      current?.id !== db?.id
+        ? db
+          ? {
+              label: (
+                <SelectLabel
+                  backend={db.backend}
+                  databaseName={db.database_name}
+                />
+              ),
+              value: db.id,
+              ...db,
+            }
+          : undefined
+        : current,
+    );
+    setCurrentCatalog(catalogData?.defaultCatalog || undefined);
+    setCurrentSchema(schemaData?.defaultSchema || undefined);
+  }, [db, catalogData, schemaData]);
 
   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..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):

Reply via email to