mirror of https://github.com/apache/superset.git
feat: deprecate /superset/extra_table_metadata migrate to api v1 (#19921)
* feat: deprecate /superset/extra_table_metadata migrate to api v1 * use can_read to table_extra_metadata * troubleshoot sqlite * fix test * fix test * fix test * fix frontend test on sqllab
This commit is contained in:
parent
e9032e95ec
commit
4fdf230a56
|
@ -1045,7 +1045,7 @@ function getTableMetadata(table, query, dispatch) {
|
|||
function getTableExtendedMetadata(table, query, dispatch) {
|
||||
return SupersetClient.get({
|
||||
endpoint: encodeURI(
|
||||
`/superset/extra_table_metadata/${query.dbId}/` +
|
||||
`/api/v1/database/${query.dbId}/table_extra/` +
|
||||
`${encodeURIComponent(table.name)}/${encodeURIComponent(
|
||||
table.schema,
|
||||
)}/`,
|
||||
|
|
|
@ -415,10 +415,10 @@ describe('async actions', () => {
|
|||
fetchMock.delete(updateTableSchemaEndpoint, {});
|
||||
fetchMock.post(updateTableSchemaEndpoint, JSON.stringify({ id: 1 }));
|
||||
|
||||
const getTableMetadataEndpoint = 'glob:*/api/v1/database/*';
|
||||
const getTableMetadataEndpoint = 'glob:**/api/v1/database/*/table/*/*/';
|
||||
fetchMock.get(getTableMetadataEndpoint, {});
|
||||
const getExtraTableMetadataEndpoint =
|
||||
'glob:*/superset/extra_table_metadata/*';
|
||||
'glob:**/api/v1/database/*/table_extra/*/*/';
|
||||
fetchMock.get(getExtraTableMetadataEndpoint, {});
|
||||
|
||||
let isFeatureEnabledMock;
|
||||
|
|
|
@ -147,7 +147,7 @@ export const DndFilterSelect = (props: DndFilterSelectProps) => {
|
|||
|
||||
if (!isSqllabView && dbId && name && schema) {
|
||||
SupersetClient.get({
|
||||
endpoint: `/superset/extra_table_metadata/${dbId}/${name}/${schema}/`,
|
||||
endpoint: `/api/v1/database/${dbId}/table_extra/${name}/${schema}/`,
|
||||
})
|
||||
.then(({ json }: { json: Record<string, any> }) => {
|
||||
if (json && json.partitions) {
|
||||
|
|
|
@ -137,7 +137,7 @@ class AdhocFilterControl extends React.Component {
|
|||
|
||||
if (!isSqllabView && dbId && name && schema) {
|
||||
SupersetClient.get({
|
||||
endpoint: `/superset/extra_table_metadata/${dbId}/${name}/${schema}/`,
|
||||
endpoint: `/api/v1/database/${dbId}/table_extra/${name}/${schema}/`,
|
||||
})
|
||||
.then(({ json }) => {
|
||||
if (json && json.partitions) {
|
||||
|
|
|
@ -112,6 +112,7 @@ MODEL_API_RW_METHOD_PERMISSION_MAP = {
|
|||
"schemas": "read",
|
||||
"select_star": "read",
|
||||
"table_metadata": "read",
|
||||
"table_extra_metadata": "read",
|
||||
"test_connection": "read",
|
||||
"validate_parameters": "read",
|
||||
"favorite_status": "read",
|
||||
|
|
|
@ -63,6 +63,7 @@ from superset.databases.schemas import (
|
|||
get_export_ids_schema,
|
||||
SchemasResponseSchema,
|
||||
SelectStarResponseSchema,
|
||||
TableExtraMetadataResponseSchema,
|
||||
TableMetadataResponseSchema,
|
||||
)
|
||||
from superset.databases.utils import get_table_metadata
|
||||
|
@ -71,7 +72,7 @@ from superset.errors import ErrorLevel, SupersetError, SupersetErrorType
|
|||
from superset.extensions import security_manager
|
||||
from superset.models.core import Database
|
||||
from superset.superset_typing import FlaskResponse
|
||||
from superset.utils.core import error_msg_from_exception
|
||||
from superset.utils.core import error_msg_from_exception, parse_js_uri_path_item
|
||||
from superset.views.base_api import (
|
||||
BaseSupersetModelRestApi,
|
||||
requires_form_data,
|
||||
|
@ -89,6 +90,7 @@ class DatabaseRestApi(BaseSupersetModelRestApi):
|
|||
RouteMethod.EXPORT,
|
||||
RouteMethod.IMPORT,
|
||||
"table_metadata",
|
||||
"table_extra_metadata",
|
||||
"select_star",
|
||||
"schemas",
|
||||
"test_connection",
|
||||
|
@ -197,6 +199,7 @@ class DatabaseRestApi(BaseSupersetModelRestApi):
|
|||
DatabaseRelatedObjectsResponse,
|
||||
DatabaseTestConnectionSchema,
|
||||
DatabaseValidateParametersSchema,
|
||||
TableExtraMetadataResponseSchema,
|
||||
TableMetadataResponseSchema,
|
||||
SelectStarResponseSchema,
|
||||
SchemasResponseSchema,
|
||||
|
@ -527,6 +530,69 @@ class DatabaseRestApi(BaseSupersetModelRestApi):
|
|||
self.incr_stats("success", self.table_metadata.__name__)
|
||||
return self.response(200, **table_info)
|
||||
|
||||
@expose("/<int:pk>/table_extra/<table_name>/<schema_name>/", methods=["GET"])
|
||||
@protect()
|
||||
@check_datasource_access
|
||||
@safe
|
||||
@statsd_metrics
|
||||
@event_logger.log_this_with_context(
|
||||
action=lambda self, *args, **kwargs: f"{self.__class__.__name__}"
|
||||
f".table_extra_metadata",
|
||||
log_to_statsd=False,
|
||||
)
|
||||
def table_extra_metadata(
|
||||
self, database: Database, table_name: str, schema_name: str
|
||||
) -> FlaskResponse:
|
||||
"""Table schema info
|
||||
---
|
||||
get:
|
||||
summary: >-
|
||||
Get table extra metadata
|
||||
description: >-
|
||||
Response depends on each DB engine spec normally focused on partitions
|
||||
parameters:
|
||||
- in: path
|
||||
schema:
|
||||
type: integer
|
||||
name: pk
|
||||
description: The database id
|
||||
- in: path
|
||||
schema:
|
||||
type: string
|
||||
name: table_name
|
||||
description: Table name
|
||||
- in: path
|
||||
schema:
|
||||
type: string
|
||||
name: schema_name
|
||||
description: Table schema
|
||||
responses:
|
||||
200:
|
||||
description: Table extra metadata information
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/TableExtraMetadataResponseSchema"
|
||||
400:
|
||||
$ref: '#/components/responses/400'
|
||||
401:
|
||||
$ref: '#/components/responses/401'
|
||||
404:
|
||||
$ref: '#/components/responses/404'
|
||||
422:
|
||||
$ref: '#/components/responses/422'
|
||||
500:
|
||||
$ref: '#/components/responses/500'
|
||||
"""
|
||||
self.incr_stats("init", self.table_metadata.__name__)
|
||||
|
||||
parsed_schema = parse_js_uri_path_item(schema_name, eval_undefined=True)
|
||||
table_name = parse_js_uri_path_item(table_name) # type: ignore
|
||||
payload = database.db_engine_spec.extra_table_metadata(
|
||||
database, table_name, parsed_schema
|
||||
)
|
||||
return self.response(200, **payload)
|
||||
|
||||
@expose("/<int:pk>/select_star/<table_name>/", methods=["GET"])
|
||||
@expose("/<int:pk>/select_star/<table_name>/<schema_name>/", methods=["GET"])
|
||||
@protect()
|
||||
|
|
|
@ -527,6 +527,12 @@ class TableMetadataResponseSchema(Schema):
|
|||
selectStar = fields.String(description="SQL select star")
|
||||
|
||||
|
||||
class TableExtraMetadataResponseSchema(Schema):
|
||||
metadata = fields.Dict()
|
||||
partitions = fields.Dict()
|
||||
clustering = fields.Dict()
|
||||
|
||||
|
||||
class SelectStarResponseSchema(Schema):
|
||||
result = fields.String(description="SQL select star")
|
||||
|
||||
|
|
|
@ -641,7 +641,7 @@ class BaseEngineSpec: # pylint: disable=too-many-public-methods
|
|||
cls,
|
||||
database: "Database",
|
||||
table_name: str,
|
||||
schema_name: str,
|
||||
schema_name: Optional[str],
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Returns engine-specific table metadata
|
||||
|
|
|
@ -268,7 +268,7 @@ class BigQueryEngineSpec(BaseEngineSpec):
|
|||
|
||||
@classmethod
|
||||
def extra_table_metadata(
|
||||
cls, database: "Database", table_name: str, schema_name: str
|
||||
cls, database: "Database", table_name: str, schema_name: Optional[str]
|
||||
) -> Dict[str, Any]:
|
||||
indexes = database.get_indexes(table_name, schema_name)
|
||||
if not indexes:
|
||||
|
|
|
@ -97,7 +97,7 @@ class GSheetsEngineSpec(SqliteEngineSpec):
|
|||
cls,
|
||||
database: "Database",
|
||||
table_name: str,
|
||||
schema_name: str,
|
||||
schema_name: Optional[str],
|
||||
) -> Dict[str, Any]:
|
||||
engine = cls.get_engine(database, schema=schema_name)
|
||||
with closing(engine.raw_connection()) as conn:
|
||||
|
|
|
@ -899,7 +899,7 @@ class PrestoEngineSpec(BaseEngineSpec): # pylint: disable=too-many-public-metho
|
|||
|
||||
@classmethod
|
||||
def extra_table_metadata(
|
||||
cls, database: "Database", table_name: str, schema_name: str
|
||||
cls, database: "Database", table_name: str, schema_name: Optional[str]
|
||||
) -> Dict[str, Any]:
|
||||
metadata = {}
|
||||
|
||||
|
@ -931,7 +931,7 @@ class PrestoEngineSpec(BaseEngineSpec): # pylint: disable=too-many-public-metho
|
|||
|
||||
@classmethod
|
||||
def get_create_view(
|
||||
cls, database: "Database", schema: str, table: str
|
||||
cls, database: "Database", schema: Optional[str], table: str
|
||||
) -> Optional[str]:
|
||||
"""
|
||||
Return a CREATE VIEW statement, or `None` if not a view.
|
||||
|
|
|
@ -2154,6 +2154,12 @@ class Superset(BaseSupersetView): # pylint: disable=too-many-public-methods
|
|||
def extra_table_metadata( # pylint: disable=no-self-use
|
||||
self, database_id: int, table_name: str, schema: str
|
||||
) -> FlaskResponse:
|
||||
logger.warning(
|
||||
"%s.extra_table_metadata "
|
||||
"This API endpoint is deprecated and will be removed in version 3.0.0",
|
||||
self.__class__.__name__,
|
||||
)
|
||||
|
||||
parsed_schema = utils.parse_js_uri_path_item(schema, eval_undefined=True)
|
||||
table_name = utils.parse_js_uri_path_item(table_name) # type: ignore
|
||||
mydb = db.session.query(Database).filter_by(id=database_id).one()
|
||||
|
|
|
@ -775,10 +775,30 @@ class TestDatabaseApi(SupersetTestCase):
|
|||
Database API: Test get invalid table from table metadata
|
||||
"""
|
||||
example_db = get_example_database()
|
||||
uri = f"api/v1/database/{example_db.id}/wrong_table/null/"
|
||||
uri = f"api/v1/database/{example_db.id}/table/wrong_table/null/"
|
||||
self.login(username="admin")
|
||||
rv = self.client.get(uri)
|
||||
self.assertEqual(rv.status_code, 404)
|
||||
data = json.loads(rv.data.decode("utf-8"))
|
||||
if example_db.backend == "sqlite":
|
||||
self.assertEqual(rv.status_code, 200)
|
||||
self.assertEqual(
|
||||
data,
|
||||
{
|
||||
"columns": [],
|
||||
"comment": None,
|
||||
"foreignKeys": [],
|
||||
"indexes": [],
|
||||
"name": "wrong_table",
|
||||
"primaryKey": {"constrained_columns": None, "name": None},
|
||||
"selectStar": "SELECT\nFROM wrong_table\nLIMIT 100\nOFFSET 0",
|
||||
},
|
||||
)
|
||||
elif example_db.backend == "mysql":
|
||||
self.assertEqual(rv.status_code, 422)
|
||||
self.assertEqual(data, {"message": "`wrong_table`"})
|
||||
else:
|
||||
self.assertEqual(rv.status_code, 422)
|
||||
self.assertEqual(data, {"message": "wrong_table"})
|
||||
|
||||
def test_get_table_metadata_no_db_permission(self):
|
||||
"""
|
||||
|
@ -790,6 +810,46 @@ class TestDatabaseApi(SupersetTestCase):
|
|||
rv = self.client.get(uri)
|
||||
self.assertEqual(rv.status_code, 404)
|
||||
|
||||
@pytest.mark.usefixtures("load_birth_names_dashboard_with_slices")
|
||||
def test_get_table_extra_metadata(self):
|
||||
"""
|
||||
Database API: Test get table extra metadata info
|
||||
"""
|
||||
example_db = get_example_database()
|
||||
self.login(username="admin")
|
||||
uri = f"api/v1/database/{example_db.id}/table_extra/birth_names/null/"
|
||||
rv = self.client.get(uri)
|
||||
self.assertEqual(rv.status_code, 200)
|
||||
response = json.loads(rv.data.decode("utf-8"))
|
||||
self.assertEqual(response, {})
|
||||
|
||||
def test_get_invalid_database_table_extra_metadata(self):
|
||||
"""
|
||||
Database API: Test get invalid database from table extra metadata
|
||||
"""
|
||||
database_id = 1000
|
||||
self.login(username="admin")
|
||||
uri = f"api/v1/database/{database_id}/table_extra/some_table/some_schema/"
|
||||
rv = self.client.get(uri)
|
||||
self.assertEqual(rv.status_code, 404)
|
||||
|
||||
uri = "api/v1/database/some_database/table_extra/some_table/some_schema/"
|
||||
rv = self.client.get(uri)
|
||||
self.assertEqual(rv.status_code, 404)
|
||||
|
||||
def test_get_invalid_table_table_extra_metadata(self):
|
||||
"""
|
||||
Database API: Test get invalid table from table extra metadata
|
||||
"""
|
||||
example_db = get_example_database()
|
||||
uri = f"api/v1/database/{example_db.id}/table_extra/wrong_table/null/"
|
||||
self.login(username="admin")
|
||||
rv = self.client.get(uri)
|
||||
data = json.loads(rv.data.decode("utf-8"))
|
||||
|
||||
self.assertEqual(rv.status_code, 200)
|
||||
self.assertEqual(data, {})
|
||||
|
||||
@pytest.mark.usefixtures("load_birth_names_dashboard_with_slices")
|
||||
def test_get_select_star(self):
|
||||
"""
|
||||
|
|
Loading…
Reference in New Issue