This is an automated email from the ASF dual-hosted git repository. beto pushed a commit to branch improve_perms in repository https://gitbox.apache.org/repos/asf/superset.git
commit 25200430bfe2195761869f480a5f92c6cfdff4b0 Author: Beto Dealmeida <[email protected]> AuthorDate: Thu May 11 10:45:02 2023 -0700 feat: split database information --- .../src/features/databases/DatabaseModal/index.tsx | 1 + superset-frontend/src/views/CRUD/hooks.ts | 7 +- superset/constants.py | 1 + superset/databases/api.py | 24 +++++-- superset/databases/schemas.py | 83 ++++++++++++++++++++++ 5 files changed, 111 insertions(+), 5 deletions(-) diff --git a/superset-frontend/src/features/databases/DatabaseModal/index.tsx b/superset-frontend/src/features/databases/DatabaseModal/index.tsx index 4a80612369..059d106fdb 100644 --- a/superset-frontend/src/features/databases/DatabaseModal/index.tsx +++ b/superset-frontend/src/features/databases/DatabaseModal/index.tsx @@ -542,6 +542,7 @@ const DatabaseModal: FunctionComponent<DatabaseModalProps> = ({ 'database', t('database'), addDangerToast, + 'connection', ); const [tabKey, setTabKey] = useState<string>(DEFAULT_TAB_KEY); diff --git a/superset-frontend/src/views/CRUD/hooks.ts b/superset-frontend/src/views/CRUD/hooks.ts index 91af5817b4..65aae1d928 100644 --- a/superset-frontend/src/views/CRUD/hooks.ts +++ b/superset-frontend/src/views/CRUD/hooks.ts @@ -224,6 +224,7 @@ export function useSingleViewResource<D extends object = any>( resourceName: string, resourceLabel: string, // resourceLabel for translations handleErrorMsg: (errorMsg: string) => void, + path_suffix = '', ) { const [state, setState] = useState<SingleViewResourceState<D>>({ loading: false, @@ -242,8 +243,12 @@ export function useSingleViewResource<D extends object = any>( loading: true, }); + const endpoint = + path_suffix !== '' + ? `/api/v1/${resourceName}/${resourceID}/{path_suffix}` + : `/api/v1/${resourceName}/${resourceID}`; return SupersetClient.get({ - endpoint: `/api/v1/${resourceName}/${resourceID}`, + endpoint, }) .then( ({ json = {} }) => { diff --git a/superset/constants.py b/superset/constants.py index 958592b62f..e4bad9f8aa 100644 --- a/superset/constants.py +++ b/superset/constants.py @@ -154,6 +154,7 @@ MODEL_API_RW_METHOD_PERMISSION_MAP = { "add_objects": "write", "delete_object": "write", "copy_dash": "write", + "get_connection": "write", } EXTRA_FORM_DATA_APPEND_KEYS = { diff --git a/superset/databases/api.py b/superset/databases/api.py index 95b5089652..0540bf37a8 100644 --- a/superset/databases/api.py +++ b/superset/databases/api.py @@ -61,6 +61,7 @@ from superset.databases.filters import DatabaseFilter, DatabaseUploadEnabledFilt from superset.databases.schemas import ( database_schemas_query_schema, database_tables_query_schema, + DatabaseConnectionSchema, DatabaseFunctionNamesResponse, DatabasePostSchema, DatabasePutSchema, @@ -122,6 +123,7 @@ class DatabaseRestApi(BaseSupersetModelRestApi): "validate_sql", "delete_ssh_tunnel", "schemas_access_for_file_upload", + "get_connection", } resource_name = "database" class_permission_name = "Database" @@ -144,12 +146,8 @@ class DatabaseRestApi(BaseSupersetModelRestApi): "driver", "force_ctas_schema", "impersonate_user", - "masked_encrypted_extra", "extra", - "parameters", - "parameters_schema", "server_cert", - "sqlalchemy_uri", "is_managed_externally", "engine_information", ] @@ -237,6 +235,24 @@ class DatabaseRestApi(BaseSupersetModelRestApi): ValidateSQLResponse, ) + @expose("/<int:pk>/connection", methods=("GET",)) + @protect() + @safe + def get_connection(self, pk: int) -> Response: + """Get database connection info.""" + database = DatabaseDAO.find_by_id(pk) + database_connection_schema = DatabaseConnectionSchema() + response = { + "id": pk, + "result": database_connection_schema.dump(database, many=False), + } + try: + if ssh_tunnel := DatabaseDAO.get_ssh_tunnel(pk): + response["result"]["ssh_tunnel"] = ssh_tunnel.data + return self.response(200, **response) + except SupersetException as ex: + return self.response(ex.status, message=ex.message) + @expose("/<int:pk>", methods=("GET",)) @protect() @safe diff --git a/superset/databases/schemas.py b/superset/databases/schemas.py index e1e38667c0..00e8c3ca53 100644 --- a/superset/databases/schemas.py +++ b/superset/databases/schemas.py @@ -880,3 +880,86 @@ class DatabaseSchemaAccessForFileUploadResponse(Schema): "information" }, ) + + +class DatabaseConnectionSchema(Schema): + """ + Schema with database connection information. + + This is only for admins (who have ``can_create`` on ``Database``). + """ + + allow_ctas = fields.Boolean(metadata={"description": allow_ctas_description}) + allow_cvas = fields.Boolean(metadata={"description": allow_cvas_description}) + allow_dml = fields.Boolean(metadata={"description": allow_dml_description}) + allow_file_upload = fields.Boolean( + metadata={"description": allow_file_upload_description} + ) + allow_run_async = fields.Boolean( + metadata={"description": allow_run_async_description} + ) + backend = fields.String( + allow_none=True, metadata={"description": "SQLAlchemy engine to use"} + ) + cache_timeout = fields.Integer( + metadata={"description": cache_timeout_description}, allow_none=True + ) + configuration_method = fields.String( + metadata={"description": configuration_method_description}, + ) + database_name = fields.String( + metadata={"description": database_name_description}, + allow_none=True, + validate=Length(1, 250), + ) + driver = fields.String( + allow_none=True, metadata={"description": "SQLAlchemy driver to use"} + ) + engine_information = fields.Dict(keys=fields.String(), values=fields.Raw()) + expose_in_sqllab = fields.Boolean( + metadata={"description": expose_in_sqllab_description} + ) + extra = fields.String( + metadata={"description": extra_description}, validate=extra_validator + ) + force_ctas_schema = fields.String( + metadata={"description": force_ctas_schema_description}, + allow_none=True, + validate=Length(0, 250), + ) + id = fields.Integer(metadata={"description": "Database ID (for updates)"}) + impersonate_user = fields.Boolean( + metadata={"description": impersonate_user_description} + ) + is_managed_externally = fields.Boolean(allow_none=True, dump_default=False) + server_cert = fields.String( + metadata={"description": server_cert_description}, + allow_none=True, + validate=server_cert_validator, + ) + uuid = fields.String(required=False) + ssh_tunnel = fields.Nested(DatabaseSSHTunnel, allow_none=True) + masked_encrypted_extra = fields.String( + metadata={"description": encrypted_extra_description}, + validate=encrypted_extra_validator, + allow_none=True, + ) + parameters = fields.Dict( + keys=fields.String(), + values=fields.Raw(), + metadata={"description": "DB-specific parameters for configuration"}, + ) + parameters_schema = fields.Dict( + keys=fields.String(), + values=fields.Raw(), + metadata={ + "description": ( + "JSONSchema for configuring the database by " + "parameters instead of SQLAlchemy URI" + ), + }, + ) + sqlalchemy_uri = fields.String( + metadata={"description": sqlalchemy_uri_description}, + validate=[Length(1, 1024), sqlalchemy_uri_validator], + )
