From 5b51ae8bdcbda608fc4b580cd2bf882d4c06ff89 Mon Sep 17 00:00:00 2001 From: Beto Dealmeida Date: Mon, 28 Jul 2025 18:37:23 -0400 Subject: [PATCH] feat: improve file upload checkbox during DB creation --- .../databases/DatabaseModal/ExtraOptions.tsx | 50 +++++++++++-------- .../databases/DatabaseModal/index.tsx | 7 +++ superset-frontend/src/views/CRUD/hooks.ts | 6 ++- superset/commands/database/test_connection.py | 4 +- superset/databases/api.py | 16 +++++- 5 files changed, 58 insertions(+), 25 deletions(-) diff --git a/superset-frontend/src/features/databases/DatabaseModal/ExtraOptions.tsx b/superset-frontend/src/features/databases/DatabaseModal/ExtraOptions.tsx index fe616437d35..7dd87fbb2bf 100644 --- a/superset-frontend/src/features/databases/DatabaseModal/ExtraOptions.tsx +++ b/superset-frontend/src/features/databases/DatabaseModal/ExtraOptions.tsx @@ -50,6 +50,7 @@ const ExtraOptions = ({ onExtraInputChange, onExtraEditorChange, extraExtension, + testedEngineInfo, }: { db: DatabaseObject | null; onInputChange: ( @@ -62,13 +63,14 @@ const ExtraOptions = ({ ) => void; onExtraEditorChange: Function; extraExtension: DatabaseConnectionExtension | undefined; + testedEngineInfo?: any; }) => { const expandableModalIsOpen = !!db?.expose_in_sqllab; const createAsOpen = !!(db?.allow_ctas || db?.allow_cvas); - const isFileUploadSupportedByEngine = - db?.engine_information?.supports_file_upload; - const supportsDynamicCatalog = - db?.engine_information?.supports_dynamic_catalog; + // Use tested engine info if available, otherwise fall back to initial engine info + const engineInfo = testedEngineInfo || db?.engine_information; + const isFileUploadSupportedByEngine = engineInfo?.supports_file_upload; + const supportsDynamicCatalog = engineInfo?.supports_dynamic_catalog; // JSON.parse will deep parse engine_params // if it's an object, and we want to keep it a string @@ -528,23 +530,29 @@ const ExtraOptions = ({ /> - {isFileUploadSupportedByEngine && ( - -
- - {t('Allow file uploads to database')} - -
-
- )} + +
+ + {t('Allow file uploads to database')} + + {!isFileUploadSupportedByEngine && ( + + )} +
+
{isFileUploadSupportedByEngine && !!db?.allow_file_upload && (
diff --git a/superset-frontend/src/features/databases/DatabaseModal/index.tsx b/superset-frontend/src/features/databases/DatabaseModal/index.tsx index 79fac7b8e91..b8b6f340c0f 100644 --- a/superset-frontend/src/features/databases/DatabaseModal/index.tsx +++ b/superset-frontend/src/features/databases/DatabaseModal/index.tsx @@ -607,6 +607,7 @@ const DatabaseModal: FunctionComponent = ({ const [editNewDb, setEditNewDb] = useState(false); const [isLoading, setLoading] = useState(false); const [testInProgress, setTestInProgress] = useState(false); + const [testedEngineInfo, setTestedEngineInfo] = useState(null); const [passwords, setPasswords] = useState>({}); const [sshTunnelPasswords, setSSHTunnelPasswords] = useState< Record @@ -735,6 +736,9 @@ const DatabaseModal: FunctionComponent = ({ addSuccessToast(errorMsg); setHasValidated(true); }, + (engineInfo: any) => { + setTestedEngineInfo(engineInfo); + }, ); }; @@ -797,6 +801,7 @@ const DatabaseModal: FunctionComponent = ({ setSSHTunnelPrivateKeyPasswords({}); setConfirmedOverwrite(false); setUseSSHTunneling(undefined); + setTestedEngineInfo(null); onHide(); }; @@ -1771,6 +1776,7 @@ const DatabaseModal: FunctionComponent = ({ , ) => { @@ -2020,6 +2026,7 @@ const DatabaseModal: FunctionComponent = ({ { const { target } = e; onChange(ActionType.InputChange, { diff --git a/superset-frontend/src/views/CRUD/hooks.ts b/superset-frontend/src/views/CRUD/hooks.ts index 5a5721205ee..f36ba7877ae 100644 --- a/superset-frontend/src/views/CRUD/hooks.ts +++ b/superset-frontend/src/views/CRUD/hooks.ts @@ -712,14 +712,18 @@ export const testDatabaseConnection = ( connection: Partial, handleErrorMsg: (errorMsg: string) => void, addSuccessToast: (arg0: string) => void, + onEngineInfo?: (engineInfo: any) => void, ) => { SupersetClient.post({ endpoint: 'api/v1/database/test_connection/', body: JSON.stringify(connection), headers: { 'Content-Type': 'application/json' }, }).then( - () => { + (response) => { addSuccessToast(t('Connection looks good!')); + if (onEngineInfo && response?.json?.engine_information) { + onEngineInfo(response.json.engine_information); + } }, createErrorHandler((errMsg: Record | string) => { handleErrorMsg(t('ERROR: %s', parsedErrorMessage(errMsg))); diff --git a/superset/commands/database/test_connection.py b/superset/commands/database/test_connection.py index 1e5fb8db44d..920e2627023 100644 --- a/superset/commands/database/test_connection.py +++ b/superset/commands/database/test_connection.py @@ -91,7 +91,7 @@ class TestConnectionDatabaseCommand(BaseCommand): def run( # noqa: C901 self, - ) -> None: # pylint: disable=too-many-statements,too-many-branches + ) -> Database: # pylint: disable=too-many-statements,too-many-branches self.validate() ex_str = "" ssh_tunnel = self._properties.get("ssh_tunnel") @@ -168,6 +168,8 @@ class TestConnectionDatabaseCommand(BaseCommand): action=get_log_connection_action("test_connection_success", ssh_tunnel), engine=database.db_engine_spec.__name__, ) + + return database except (NoSuchModuleError, ModuleNotFoundError) as ex: event_logger.log_with_context( diff --git a/superset/databases/api.py b/superset/databases/api.py index c9b882dd6b9..593a9e74596 100644 --- a/superset/databases/api.py +++ b/superset/databases/api.py @@ -1258,6 +1258,17 @@ class DatabaseRestApi(BaseSupersetModelRestApi): properties: message: type: string + engine_information: + type: object + properties: + supports_file_upload: + type: boolean + disable_ssh_tunneling: + type: boolean + supports_dynamic_catalog: + type: boolean + supports_oauth2: + type: boolean 400: $ref: '#/components/responses/400' 422: @@ -1271,8 +1282,9 @@ class DatabaseRestApi(BaseSupersetModelRestApi): except ValidationError as error: return self.response_400(message=error.messages) try: - TestConnectionDatabaseCommand(item).run() - return self.response(200, message="OK") + database = TestConnectionDatabaseCommand(item).run() + engine_information = database.db_engine_spec.get_public_information() + return self.response(200, message="OK", engine_information=engine_information) except (SSHTunnelingNotEnabledError, SSHTunnelDatabasePortError) as ex: return self.response_400(message=str(ex))