Compare commits

..

2 Commits

Author SHA1 Message Date
Beto Dealmeida
5b51ae8bdc feat: improve file upload checkbox during DB creation 2025-07-28 18:37:23 -04:00
Beto Dealmeida
22b3cc0480 chore: bump BigQuery dialect to 1.15.0 (#34371) 2025-07-28 16:39:18 -04:00
8 changed files with 63 additions and 60 deletions

View File

@@ -111,7 +111,7 @@ athena = ["pyathena[pandas]>=2, <3"]
aurora-data-api = ["preset-sqlalchemy-aurora-data-api>=0.2.8,<0.3"] aurora-data-api = ["preset-sqlalchemy-aurora-data-api>=0.2.8,<0.3"]
bigquery = [ bigquery = [
"pandas-gbq>=0.19.1", "pandas-gbq>=0.19.1",
"sqlalchemy-bigquery>=1.6.1", "sqlalchemy-bigquery>=1.15.0",
"google-cloud-bigquery>=3.10.0", "google-cloud-bigquery>=3.10.0",
] ]
clickhouse = ["clickhouse-connect>=0.5.14, <1.0"] clickhouse = ["clickhouse-connect>=0.5.14, <1.0"]

View File

@@ -11,9 +11,7 @@ apispec==6.6.1
apsw==3.50.1.0 apsw==3.50.1.0
# via shillelagh # via shillelagh
async-timeout==4.0.3 async-timeout==4.0.3
# via # via -r requirements/base.in
# -r requirements/base.in
# redis
attrs==25.3.0 attrs==25.3.0
# via # via
# cattrs # cattrs
@@ -99,11 +97,6 @@ email-validator==2.2.0
# via flask-appbuilder # via flask-appbuilder
et-xmlfile==2.0.0 et-xmlfile==2.0.0
# via openpyxl # via openpyxl
exceptiongroup==1.3.0
# via
# cattrs
# trio
# trio-websocket
flask==2.3.3 flask==2.3.3
# via # via
# apache-superset (pyproject.toml) # apache-superset (pyproject.toml)
@@ -161,7 +154,6 @@ greenlet==3.1.1
# via # via
# apache-superset (pyproject.toml) # apache-superset (pyproject.toml)
# shillelagh # shillelagh
# sqlalchemy
gunicorn==23.0.0 gunicorn==23.0.0
# via apache-superset (pyproject.toml) # via apache-superset (pyproject.toml)
h11==0.16.0 h11==0.16.0
@@ -318,7 +310,7 @@ python-dateutil==2.9.0.post0
# holidays # holidays
# pandas # pandas
# shillelagh # shillelagh
python-dotenv==1.1.1 python-dotenv==1.1.0
# via apache-superset (pyproject.toml) # via apache-superset (pyproject.toml)
python-geohash==0.8.5 python-geohash==0.8.5
# via apache-superset (pyproject.toml) # via apache-superset (pyproject.toml)
@@ -403,11 +395,9 @@ typing-extensions==4.14.0
# apache-superset (pyproject.toml) # apache-superset (pyproject.toml)
# alembic # alembic
# cattrs # cattrs
# exceptiongroup
# limits # limits
# pyopenssl # pyopenssl
# referencing # referencing
# rich
# selenium # selenium
# shillelagh # shillelagh
tzdata==2025.2 tzdata==2025.2

View File

@@ -20,10 +20,6 @@ apsw==3.50.1.0
# shillelagh # shillelagh
astroid==3.3.10 astroid==3.3.10
# via pylint # via pylint
async-timeout==4.0.3
# via
# -c requirements/base.txt
# redis
attrs==25.3.0 attrs==25.3.0
# via # via
# -c requirements/base.txt # -c requirements/base.txt
@@ -180,13 +176,6 @@ et-xmlfile==2.0.0
# via # via
# -c requirements/base.txt # -c requirements/base.txt
# openpyxl # openpyxl
exceptiongroup==1.3.0
# via
# -c requirements/base.txt
# cattrs
# pytest
# trio
# trio-websocket
filelock==3.12.2 filelock==3.12.2
# via virtualenv # via virtualenv
flask==2.3.3 flask==2.3.3
@@ -324,7 +313,6 @@ greenlet==3.1.1
# apache-superset # apache-superset
# gevent # gevent
# shillelagh # shillelagh
# sqlalchemy
grpcio==1.71.0 grpcio==1.71.0
# via # via
# apache-superset # apache-superset
@@ -682,7 +670,7 @@ python-dateutil==2.9.0.post0
# pyhive # pyhive
# shillelagh # shillelagh
# trino # trino
python-dotenv==1.1.1 python-dotenv==1.1.0
# via # via
# -c requirements/base.txt # -c requirements/base.txt
# apache-superset # apache-superset
@@ -807,7 +795,7 @@ sqlalchemy==1.4.54
# shillelagh # shillelagh
# sqlalchemy-bigquery # sqlalchemy-bigquery
# sqlalchemy-utils # sqlalchemy-utils
sqlalchemy-bigquery==1.12.0 sqlalchemy-bigquery==1.15.0
# via apache-superset # via apache-superset
sqlalchemy-utils==0.38.3 sqlalchemy-utils==0.38.3
# via # via
@@ -830,11 +818,6 @@ tabulate==0.9.0
# via # via
# -c requirements/base.txt # -c requirements/base.txt
# apache-superset # apache-superset
tomli==2.2.1
# via
# coverage
# pylint
# pytest
tomlkit==0.13.3 tomlkit==0.13.3
# via pylint # via pylint
tqdm==4.67.1 tqdm==4.67.1
@@ -857,13 +840,10 @@ typing-extensions==4.14.0
# -c requirements/base.txt # -c requirements/base.txt
# alembic # alembic
# apache-superset # apache-superset
# astroid
# cattrs # cattrs
# exceptiongroup
# limits # limits
# pyopenssl # pyopenssl
# referencing # referencing
# rich
# selenium # selenium
# shillelagh # shillelagh
tzdata==2025.2 tzdata==2025.2

View File

@@ -50,6 +50,7 @@ const ExtraOptions = ({
onExtraInputChange, onExtraInputChange,
onExtraEditorChange, onExtraEditorChange,
extraExtension, extraExtension,
testedEngineInfo,
}: { }: {
db: DatabaseObject | null; db: DatabaseObject | null;
onInputChange: ( onInputChange: (
@@ -62,13 +63,14 @@ const ExtraOptions = ({
) => void; ) => void;
onExtraEditorChange: Function; onExtraEditorChange: Function;
extraExtension: DatabaseConnectionExtension | undefined; extraExtension: DatabaseConnectionExtension | undefined;
testedEngineInfo?: any;
}) => { }) => {
const expandableModalIsOpen = !!db?.expose_in_sqllab; const expandableModalIsOpen = !!db?.expose_in_sqllab;
const createAsOpen = !!(db?.allow_ctas || db?.allow_cvas); const createAsOpen = !!(db?.allow_ctas || db?.allow_cvas);
const isFileUploadSupportedByEngine = // Use tested engine info if available, otherwise fall back to initial engine info
db?.engine_information?.supports_file_upload; const engineInfo = testedEngineInfo || db?.engine_information;
const supportsDynamicCatalog = const isFileUploadSupportedByEngine = engineInfo?.supports_file_upload;
db?.engine_information?.supports_dynamic_catalog; const supportsDynamicCatalog = engineInfo?.supports_dynamic_catalog;
// JSON.parse will deep parse engine_params // JSON.parse will deep parse engine_params
// if it's an object, and we want to keep it a string // if it's an object, and we want to keep it a string
@@ -528,23 +530,29 @@ const ExtraOptions = ({
/> />
</div> </div>
</StyledInputContainer> </StyledInputContainer>
{isFileUploadSupportedByEngine && ( <StyledInputContainer
<StyledInputContainer css={!db?.allow_file_upload ? no_margin_bottom : {}}
css={!db?.allow_file_upload ? no_margin_bottom : {}} >
> <div className="input-container">
<div className="input-container"> <Checkbox
<Checkbox id="allow_file_upload"
id="allow_file_upload" name="allow_file_upload"
name="allow_file_upload" indeterminate={false}
indeterminate={false} checked={!!db?.allow_file_upload}
checked={!!db?.allow_file_upload} disabled={!isFileUploadSupportedByEngine}
onChange={onInputChange} onChange={onInputChange}
> >
{t('Allow file uploads to database')} {t('Allow file uploads to database')}
</Checkbox> </Checkbox>
</div> {!isFileUploadSupportedByEngine && (
</StyledInputContainer> <InfoTooltip
)} tooltip={t(
'File upload is not supported for this database engine'
)}
/>
)}
</div>
</StyledInputContainer>
{isFileUploadSupportedByEngine && !!db?.allow_file_upload && ( {isFileUploadSupportedByEngine && !!db?.allow_file_upload && (
<StyledInputContainer css={no_margin_bottom}> <StyledInputContainer css={no_margin_bottom}>
<div className="control-label"> <div className="control-label">

View File

@@ -607,6 +607,7 @@ const DatabaseModal: FunctionComponent<DatabaseModalProps> = ({
const [editNewDb, setEditNewDb] = useState<boolean>(false); const [editNewDb, setEditNewDb] = useState<boolean>(false);
const [isLoading, setLoading] = useState<boolean>(false); const [isLoading, setLoading] = useState<boolean>(false);
const [testInProgress, setTestInProgress] = useState<boolean>(false); const [testInProgress, setTestInProgress] = useState<boolean>(false);
const [testedEngineInfo, setTestedEngineInfo] = useState<any>(null);
const [passwords, setPasswords] = useState<Record<string, string>>({}); const [passwords, setPasswords] = useState<Record<string, string>>({});
const [sshTunnelPasswords, setSSHTunnelPasswords] = useState< const [sshTunnelPasswords, setSSHTunnelPasswords] = useState<
Record<string, string> Record<string, string>
@@ -735,6 +736,9 @@ const DatabaseModal: FunctionComponent<DatabaseModalProps> = ({
addSuccessToast(errorMsg); addSuccessToast(errorMsg);
setHasValidated(true); setHasValidated(true);
}, },
(engineInfo: any) => {
setTestedEngineInfo(engineInfo);
},
); );
}; };
@@ -797,6 +801,7 @@ const DatabaseModal: FunctionComponent<DatabaseModalProps> = ({
setSSHTunnelPrivateKeyPasswords({}); setSSHTunnelPrivateKeyPasswords({});
setConfirmedOverwrite(false); setConfirmedOverwrite(false);
setUseSSHTunneling(undefined); setUseSSHTunneling(undefined);
setTestedEngineInfo(null);
onHide(); onHide();
}; };
@@ -1771,6 +1776,7 @@ const DatabaseModal: FunctionComponent<DatabaseModalProps> = ({
<ExtraOptions <ExtraOptions
extraExtension={dbConfigExtraExtension} extraExtension={dbConfigExtraExtension}
db={db as DatabaseObject} db={db as DatabaseObject}
testedEngineInfo={testedEngineInfo}
onInputChange={( onInputChange={(
e: CheckboxChangeEvent | React.ChangeEvent<HTMLInputElement>, e: CheckboxChangeEvent | React.ChangeEvent<HTMLInputElement>,
) => { ) => {
@@ -2020,6 +2026,7 @@ const DatabaseModal: FunctionComponent<DatabaseModalProps> = ({
<ExtraOptions <ExtraOptions
extraExtension={dbConfigExtraExtension} extraExtension={dbConfigExtraExtension}
db={db as DatabaseObject} db={db as DatabaseObject}
testedEngineInfo={testedEngineInfo}
onInputChange={(e: CheckboxChangeEvent) => { onInputChange={(e: CheckboxChangeEvent) => {
const { target } = e; const { target } = e;
onChange(ActionType.InputChange, { onChange(ActionType.InputChange, {

View File

@@ -712,14 +712,18 @@ export const testDatabaseConnection = (
connection: Partial<DatabaseObject>, connection: Partial<DatabaseObject>,
handleErrorMsg: (errorMsg: string) => void, handleErrorMsg: (errorMsg: string) => void,
addSuccessToast: (arg0: string) => void, addSuccessToast: (arg0: string) => void,
onEngineInfo?: (engineInfo: any) => void,
) => { ) => {
SupersetClient.post({ SupersetClient.post({
endpoint: 'api/v1/database/test_connection/', endpoint: 'api/v1/database/test_connection/',
body: JSON.stringify(connection), body: JSON.stringify(connection),
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
}).then( }).then(
() => { (response) => {
addSuccessToast(t('Connection looks good!')); addSuccessToast(t('Connection looks good!'));
if (onEngineInfo && response?.json?.engine_information) {
onEngineInfo(response.json.engine_information);
}
}, },
createErrorHandler((errMsg: Record<string, string[] | string> | string) => { createErrorHandler((errMsg: Record<string, string[] | string> | string) => {
handleErrorMsg(t('ERROR: %s', parsedErrorMessage(errMsg))); handleErrorMsg(t('ERROR: %s', parsedErrorMessage(errMsg)));

View File

@@ -91,7 +91,7 @@ class TestConnectionDatabaseCommand(BaseCommand):
def run( # noqa: C901 def run( # noqa: C901
self, self,
) -> None: # pylint: disable=too-many-statements,too-many-branches ) -> Database: # pylint: disable=too-many-statements,too-many-branches
self.validate() self.validate()
ex_str = "" ex_str = ""
ssh_tunnel = self._properties.get("ssh_tunnel") ssh_tunnel = self._properties.get("ssh_tunnel")
@@ -168,6 +168,8 @@ class TestConnectionDatabaseCommand(BaseCommand):
action=get_log_connection_action("test_connection_success", ssh_tunnel), action=get_log_connection_action("test_connection_success", ssh_tunnel),
engine=database.db_engine_spec.__name__, engine=database.db_engine_spec.__name__,
) )
return database
except (NoSuchModuleError, ModuleNotFoundError) as ex: except (NoSuchModuleError, ModuleNotFoundError) as ex:
event_logger.log_with_context( event_logger.log_with_context(

View File

@@ -1258,6 +1258,17 @@ class DatabaseRestApi(BaseSupersetModelRestApi):
properties: properties:
message: message:
type: string 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: 400:
$ref: '#/components/responses/400' $ref: '#/components/responses/400'
422: 422:
@@ -1271,8 +1282,9 @@ class DatabaseRestApi(BaseSupersetModelRestApi):
except ValidationError as error: except ValidationError as error:
return self.response_400(message=error.messages) return self.response_400(message=error.messages)
try: try:
TestConnectionDatabaseCommand(item).run() database = TestConnectionDatabaseCommand(item).run()
return self.response(200, message="OK") engine_information = database.db_engine_spec.get_public_information()
return self.response(200, message="OK", engine_information=engine_information)
except (SSHTunnelingNotEnabledError, SSHTunnelDatabasePortError) as ex: except (SSHTunnelingNotEnabledError, SSHTunnelDatabasePortError) as ex:
return self.response_400(message=str(ex)) return self.response_400(message=str(ex))