mirror of
https://github.com/apache/superset.git
synced 2024-09-12 00:29:39 -04:00
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) {
|
function getTableExtendedMetadata(table, query, dispatch) {
|
||||||
return SupersetClient.get({
|
return SupersetClient.get({
|
||||||
endpoint: encodeURI(
|
endpoint: encodeURI(
|
||||||
`/superset/extra_table_metadata/${query.dbId}/` +
|
`/api/v1/database/${query.dbId}/table_extra/` +
|
||||||
`${encodeURIComponent(table.name)}/${encodeURIComponent(
|
`${encodeURIComponent(table.name)}/${encodeURIComponent(
|
||||||
table.schema,
|
table.schema,
|
||||||
)}/`,
|
)}/`,
|
||||||
|
@ -415,10 +415,10 @@ describe('async actions', () => {
|
|||||||
fetchMock.delete(updateTableSchemaEndpoint, {});
|
fetchMock.delete(updateTableSchemaEndpoint, {});
|
||||||
fetchMock.post(updateTableSchemaEndpoint, JSON.stringify({ id: 1 }));
|
fetchMock.post(updateTableSchemaEndpoint, JSON.stringify({ id: 1 }));
|
||||||
|
|
||||||
const getTableMetadataEndpoint = 'glob:*/api/v1/database/*';
|
const getTableMetadataEndpoint = 'glob:**/api/v1/database/*/table/*/*/';
|
||||||
fetchMock.get(getTableMetadataEndpoint, {});
|
fetchMock.get(getTableMetadataEndpoint, {});
|
||||||
const getExtraTableMetadataEndpoint =
|
const getExtraTableMetadataEndpoint =
|
||||||
'glob:*/superset/extra_table_metadata/*';
|
'glob:**/api/v1/database/*/table_extra/*/*/';
|
||||||
fetchMock.get(getExtraTableMetadataEndpoint, {});
|
fetchMock.get(getExtraTableMetadataEndpoint, {});
|
||||||
|
|
||||||
let isFeatureEnabledMock;
|
let isFeatureEnabledMock;
|
||||||
|
@ -147,7 +147,7 @@ export const DndFilterSelect = (props: DndFilterSelectProps) => {
|
|||||||
|
|
||||||
if (!isSqllabView && dbId && name && schema) {
|
if (!isSqllabView && dbId && name && schema) {
|
||||||
SupersetClient.get({
|
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> }) => {
|
.then(({ json }: { json: Record<string, any> }) => {
|
||||||
if (json && json.partitions) {
|
if (json && json.partitions) {
|
||||||
|
@ -137,7 +137,7 @@ class AdhocFilterControl extends React.Component {
|
|||||||
|
|
||||||
if (!isSqllabView && dbId && name && schema) {
|
if (!isSqllabView && dbId && name && schema) {
|
||||||
SupersetClient.get({
|
SupersetClient.get({
|
||||||
endpoint: `/superset/extra_table_metadata/${dbId}/${name}/${schema}/`,
|
endpoint: `/api/v1/database/${dbId}/table_extra/${name}/${schema}/`,
|
||||||
})
|
})
|
||||||
.then(({ json }) => {
|
.then(({ json }) => {
|
||||||
if (json && json.partitions) {
|
if (json && json.partitions) {
|
||||||
|
@ -112,6 +112,7 @@ MODEL_API_RW_METHOD_PERMISSION_MAP = {
|
|||||||
"schemas": "read",
|
"schemas": "read",
|
||||||
"select_star": "read",
|
"select_star": "read",
|
||||||
"table_metadata": "read",
|
"table_metadata": "read",
|
||||||
|
"table_extra_metadata": "read",
|
||||||
"test_connection": "read",
|
"test_connection": "read",
|
||||||
"validate_parameters": "read",
|
"validate_parameters": "read",
|
||||||
"favorite_status": "read",
|
"favorite_status": "read",
|
||||||
|
@ -63,6 +63,7 @@ from superset.databases.schemas import (
|
|||||||
get_export_ids_schema,
|
get_export_ids_schema,
|
||||||
SchemasResponseSchema,
|
SchemasResponseSchema,
|
||||||
SelectStarResponseSchema,
|
SelectStarResponseSchema,
|
||||||
|
TableExtraMetadataResponseSchema,
|
||||||
TableMetadataResponseSchema,
|
TableMetadataResponseSchema,
|
||||||
)
|
)
|
||||||
from superset.databases.utils import get_table_metadata
|
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.extensions import security_manager
|
||||||
from superset.models.core import Database
|
from superset.models.core import Database
|
||||||
from superset.superset_typing import FlaskResponse
|
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 (
|
from superset.views.base_api import (
|
||||||
BaseSupersetModelRestApi,
|
BaseSupersetModelRestApi,
|
||||||
requires_form_data,
|
requires_form_data,
|
||||||
@ -89,6 +90,7 @@ class DatabaseRestApi(BaseSupersetModelRestApi):
|
|||||||
RouteMethod.EXPORT,
|
RouteMethod.EXPORT,
|
||||||
RouteMethod.IMPORT,
|
RouteMethod.IMPORT,
|
||||||
"table_metadata",
|
"table_metadata",
|
||||||
|
"table_extra_metadata",
|
||||||
"select_star",
|
"select_star",
|
||||||
"schemas",
|
"schemas",
|
||||||
"test_connection",
|
"test_connection",
|
||||||
@ -197,6 +199,7 @@ class DatabaseRestApi(BaseSupersetModelRestApi):
|
|||||||
DatabaseRelatedObjectsResponse,
|
DatabaseRelatedObjectsResponse,
|
||||||
DatabaseTestConnectionSchema,
|
DatabaseTestConnectionSchema,
|
||||||
DatabaseValidateParametersSchema,
|
DatabaseValidateParametersSchema,
|
||||||
|
TableExtraMetadataResponseSchema,
|
||||||
TableMetadataResponseSchema,
|
TableMetadataResponseSchema,
|
||||||
SelectStarResponseSchema,
|
SelectStarResponseSchema,
|
||||||
SchemasResponseSchema,
|
SchemasResponseSchema,
|
||||||
@ -527,6 +530,69 @@ class DatabaseRestApi(BaseSupersetModelRestApi):
|
|||||||
self.incr_stats("success", self.table_metadata.__name__)
|
self.incr_stats("success", self.table_metadata.__name__)
|
||||||
return self.response(200, **table_info)
|
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>/", methods=["GET"])
|
||||||
@expose("/<int:pk>/select_star/<table_name>/<schema_name>/", methods=["GET"])
|
@expose("/<int:pk>/select_star/<table_name>/<schema_name>/", methods=["GET"])
|
||||||
@protect()
|
@protect()
|
||||||
|
@ -527,6 +527,12 @@ class TableMetadataResponseSchema(Schema):
|
|||||||
selectStar = fields.String(description="SQL select star")
|
selectStar = fields.String(description="SQL select star")
|
||||||
|
|
||||||
|
|
||||||
|
class TableExtraMetadataResponseSchema(Schema):
|
||||||
|
metadata = fields.Dict()
|
||||||
|
partitions = fields.Dict()
|
||||||
|
clustering = fields.Dict()
|
||||||
|
|
||||||
|
|
||||||
class SelectStarResponseSchema(Schema):
|
class SelectStarResponseSchema(Schema):
|
||||||
result = fields.String(description="SQL select star")
|
result = fields.String(description="SQL select star")
|
||||||
|
|
||||||
|
@ -641,7 +641,7 @@ class BaseEngineSpec: # pylint: disable=too-many-public-methods
|
|||||||
cls,
|
cls,
|
||||||
database: "Database",
|
database: "Database",
|
||||||
table_name: str,
|
table_name: str,
|
||||||
schema_name: str,
|
schema_name: Optional[str],
|
||||||
) -> Dict[str, Any]:
|
) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Returns engine-specific table metadata
|
Returns engine-specific table metadata
|
||||||
|
@ -268,7 +268,7 @@ class BigQueryEngineSpec(BaseEngineSpec):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def extra_table_metadata(
|
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]:
|
) -> Dict[str, Any]:
|
||||||
indexes = database.get_indexes(table_name, schema_name)
|
indexes = database.get_indexes(table_name, schema_name)
|
||||||
if not indexes:
|
if not indexes:
|
||||||
|
@ -97,7 +97,7 @@ class GSheetsEngineSpec(SqliteEngineSpec):
|
|||||||
cls,
|
cls,
|
||||||
database: "Database",
|
database: "Database",
|
||||||
table_name: str,
|
table_name: str,
|
||||||
schema_name: str,
|
schema_name: Optional[str],
|
||||||
) -> Dict[str, Any]:
|
) -> Dict[str, Any]:
|
||||||
engine = cls.get_engine(database, schema=schema_name)
|
engine = cls.get_engine(database, schema=schema_name)
|
||||||
with closing(engine.raw_connection()) as conn:
|
with closing(engine.raw_connection()) as conn:
|
||||||
|
@ -899,7 +899,7 @@ class PrestoEngineSpec(BaseEngineSpec): # pylint: disable=too-many-public-metho
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def extra_table_metadata(
|
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]:
|
) -> Dict[str, Any]:
|
||||||
metadata = {}
|
metadata = {}
|
||||||
|
|
||||||
@ -931,7 +931,7 @@ class PrestoEngineSpec(BaseEngineSpec): # pylint: disable=too-many-public-metho
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_create_view(
|
def get_create_view(
|
||||||
cls, database: "Database", schema: str, table: str
|
cls, database: "Database", schema: Optional[str], table: str
|
||||||
) -> Optional[str]:
|
) -> Optional[str]:
|
||||||
"""
|
"""
|
||||||
Return a CREATE VIEW statement, or `None` if not a view.
|
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
|
def extra_table_metadata( # pylint: disable=no-self-use
|
||||||
self, database_id: int, table_name: str, schema: str
|
self, database_id: int, table_name: str, schema: str
|
||||||
) -> FlaskResponse:
|
) -> 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)
|
parsed_schema = utils.parse_js_uri_path_item(schema, eval_undefined=True)
|
||||||
table_name = utils.parse_js_uri_path_item(table_name) # type: ignore
|
table_name = utils.parse_js_uri_path_item(table_name) # type: ignore
|
||||||
mydb = db.session.query(Database).filter_by(id=database_id).one()
|
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
|
Database API: Test get invalid table from table metadata
|
||||||
"""
|
"""
|
||||||
example_db = get_example_database()
|
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")
|
self.login(username="admin")
|
||||||
rv = self.client.get(uri)
|
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):
|
def test_get_table_metadata_no_db_permission(self):
|
||||||
"""
|
"""
|
||||||
@ -790,6 +810,46 @@ class TestDatabaseApi(SupersetTestCase):
|
|||||||
rv = self.client.get(uri)
|
rv = self.client.get(uri)
|
||||||
self.assertEqual(rv.status_code, 404)
|
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")
|
@pytest.mark.usefixtures("load_birth_names_dashboard_with_slices")
|
||||||
def test_get_select_star(self):
|
def test_get_select_star(self):
|
||||||
"""
|
"""
|
||||||
|
Loading…
Reference in New Issue
Block a user