mirror of https://github.com/apache/superset.git
feat: handle chart/data API errors (#14040)
This commit is contained in:
parent
ec3f8d07f4
commit
3d357c661c
|
@ -65,7 +65,7 @@ from superset.charts.schemas import (
|
||||||
from superset.commands.exceptions import CommandInvalidError
|
from superset.commands.exceptions import CommandInvalidError
|
||||||
from superset.commands.importers.v1.utils import get_contents_from_bundle
|
from superset.commands.importers.v1.utils import get_contents_from_bundle
|
||||||
from superset.constants import MODEL_API_RW_METHOD_PERMISSION_MAP, RouteMethod
|
from superset.constants import MODEL_API_RW_METHOD_PERMISSION_MAP, RouteMethod
|
||||||
from superset.exceptions import QueryObjectValidationError, SupersetSecurityException
|
from superset.exceptions import QueryObjectValidationError
|
||||||
from superset.extensions import event_logger
|
from superset.extensions import event_logger
|
||||||
from superset.models.slice import Slice
|
from superset.models.slice import Slice
|
||||||
from superset.tasks.thumbnails import cache_chart_thumbnail
|
from superset.tasks.thumbnails import cache_chart_thumbnail
|
||||||
|
@ -500,7 +500,6 @@ class ChartRestApi(BaseSupersetModelRestApi):
|
||||||
|
|
||||||
@expose("/data", methods=["POST"])
|
@expose("/data", methods=["POST"])
|
||||||
@protect()
|
@protect()
|
||||||
@safe
|
|
||||||
@statsd_metrics
|
@statsd_metrics
|
||||||
@event_logger.log_this_with_context(
|
@event_logger.log_this_with_context(
|
||||||
action=lambda self, *args, **kwargs: f"{self.__class__.__name__}.data",
|
action=lambda self, *args, **kwargs: f"{self.__class__.__name__}.data",
|
||||||
|
@ -569,8 +568,6 @@ class ChartRestApi(BaseSupersetModelRestApi):
|
||||||
"Request is incorrect: %(error)s", error=error.normalized_messages()
|
"Request is incorrect: %(error)s", error=error.normalized_messages()
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
except SupersetSecurityException:
|
|
||||||
return self.response_401()
|
|
||||||
|
|
||||||
# TODO: support CSV, SQL query and other non-JSON types
|
# TODO: support CSV, SQL query and other non-JSON types
|
||||||
if (
|
if (
|
||||||
|
@ -591,7 +588,6 @@ class ChartRestApi(BaseSupersetModelRestApi):
|
||||||
|
|
||||||
@expose("/data/<cache_key>", methods=["GET"])
|
@expose("/data/<cache_key>", methods=["GET"])
|
||||||
@protect()
|
@protect()
|
||||||
@safe
|
|
||||||
@statsd_metrics
|
@statsd_metrics
|
||||||
@event_logger.log_this_with_context(
|
@event_logger.log_this_with_context(
|
||||||
action=lambda self, *args, **kwargs: f"{self.__class__.__name__}"
|
action=lambda self, *args, **kwargs: f"{self.__class__.__name__}"
|
||||||
|
@ -641,9 +637,6 @@ class ChartRestApi(BaseSupersetModelRestApi):
|
||||||
return self.response_400(
|
return self.response_400(
|
||||||
message=_("Request is incorrect: %(error)s", error=error.messages)
|
message=_("Request is incorrect: %(error)s", error=error.messages)
|
||||||
)
|
)
|
||||||
except SupersetSecurityException as exc:
|
|
||||||
logger.info(exc)
|
|
||||||
return self.response_401()
|
|
||||||
|
|
||||||
return self.get_data_response(command, True)
|
return self.get_data_response(command, True)
|
||||||
|
|
||||||
|
|
|
@ -41,6 +41,14 @@ class SupersetException(Exception):
|
||||||
class SupersetErrorException(SupersetException):
|
class SupersetErrorException(SupersetException):
|
||||||
"""Exceptions with a single SupersetErrorType associated with them"""
|
"""Exceptions with a single SupersetErrorType associated with them"""
|
||||||
|
|
||||||
|
def __init__(self, error: SupersetError) -> None:
|
||||||
|
super().__init__(error.message)
|
||||||
|
self.error = error
|
||||||
|
|
||||||
|
|
||||||
|
class SupersetErrorFromParamsException(SupersetErrorException):
|
||||||
|
"""Exceptions that pass in parameters to construct a SupersetError"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
error_type: SupersetErrorType,
|
error_type: SupersetErrorType,
|
||||||
|
@ -48,9 +56,10 @@ class SupersetErrorException(SupersetException):
|
||||||
level: ErrorLevel,
|
level: ErrorLevel,
|
||||||
extra: Optional[Dict[str, Any]] = None,
|
extra: Optional[Dict[str, Any]] = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
super().__init__(message)
|
super().__init__(
|
||||||
self.error = SupersetError(
|
SupersetError(
|
||||||
error_type=error_type, message=message, level=level, extra=extra or {}
|
error_type=error_type, message=message, level=level, extra=extra or {}
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -62,11 +71,11 @@ class SupersetErrorsException(SupersetException):
|
||||||
self.errors = errors
|
self.errors = errors
|
||||||
|
|
||||||
|
|
||||||
class SupersetTimeoutException(SupersetErrorException):
|
class SupersetTimeoutException(SupersetErrorFromParamsException):
|
||||||
status = 408
|
status = 408
|
||||||
|
|
||||||
|
|
||||||
class SupersetGenericDBErrorException(SupersetErrorException):
|
class SupersetGenericDBErrorException(SupersetErrorFromParamsException):
|
||||||
status = 400
|
status = 400
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
|
@ -80,7 +89,7 @@ class SupersetGenericDBErrorException(SupersetErrorException):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class SupersetTemplateParamsErrorException(SupersetErrorException):
|
class SupersetTemplateParamsErrorException(SupersetErrorFromParamsException):
|
||||||
status = 400
|
status = 400
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
|
@ -94,14 +103,13 @@ class SupersetTemplateParamsErrorException(SupersetErrorException):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class SupersetSecurityException(SupersetException):
|
class SupersetSecurityException(SupersetErrorException):
|
||||||
status = 401
|
status = 401
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self, error: SupersetError, payload: Optional[Dict[str, Any]] = None
|
self, error: SupersetError, payload: Optional[Dict[str, Any]] = None
|
||||||
) -> None:
|
) -> None:
|
||||||
super().__init__(error.message)
|
super().__init__(error)
|
||||||
self.error = error
|
|
||||||
self.payload = payload
|
self.payload = payload
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -38,6 +38,7 @@ from tests.fixtures.world_bank_dashboard import load_world_bank_dashboard_with_s
|
||||||
from tests.test_app import app
|
from tests.test_app import app
|
||||||
from superset.charts.commands.data import ChartDataCommand
|
from superset.charts.commands.data import ChartDataCommand
|
||||||
from superset.connectors.sqla.models import SqlaTable, TableColumn
|
from superset.connectors.sqla.models import SqlaTable, TableColumn
|
||||||
|
from superset.errors import SupersetErrorType
|
||||||
from superset.extensions import async_query_manager, cache_manager, db
|
from superset.extensions import async_query_manager, cache_manager, db
|
||||||
from superset.models.annotations import AnnotationLayer
|
from superset.models.annotations import AnnotationLayer
|
||||||
from superset.models.core import Database, FavStar, FavStarClassName
|
from superset.models.core import Database, FavStar, FavStarClassName
|
||||||
|
@ -47,6 +48,7 @@ from superset.models.slice import Slice
|
||||||
from superset.utils import core as utils
|
from superset.utils import core as utils
|
||||||
from superset.utils.core import AnnotationType, get_example_database, get_main_database
|
from superset.utils.core import AnnotationType, get_example_database, get_main_database
|
||||||
|
|
||||||
|
|
||||||
from tests.base_api_tests import ApiOwnersTestCaseMixin
|
from tests.base_api_tests import ApiOwnersTestCaseMixin
|
||||||
from tests.base_tests import SupersetTestCase, post_assert_metric, test_client
|
from tests.base_tests import SupersetTestCase, post_assert_metric, test_client
|
||||||
|
|
||||||
|
@ -1345,6 +1347,11 @@ class TestChartApi(SupersetTestCase, ApiOwnersTestCaseMixin, InsertChartMixin):
|
||||||
payload = get_query_context("birth_names")
|
payload = get_query_context("birth_names")
|
||||||
rv = self.post_assert_metric(CHART_DATA_URI, payload, "data")
|
rv = self.post_assert_metric(CHART_DATA_URI, payload, "data")
|
||||||
self.assertEqual(rv.status_code, 401)
|
self.assertEqual(rv.status_code, 401)
|
||||||
|
response_payload = json.loads(rv.data.decode("utf-8"))
|
||||||
|
assert (
|
||||||
|
response_payload["errors"][0]["error_type"]
|
||||||
|
== SupersetErrorType.DATASOURCE_SECURITY_ACCESS_ERROR
|
||||||
|
)
|
||||||
|
|
||||||
@pytest.mark.usefixtures("load_birth_names_dashboard_with_slices")
|
@pytest.mark.usefixtures("load_birth_names_dashboard_with_slices")
|
||||||
def test_chart_data_jinja_filter_request(self):
|
def test_chart_data_jinja_filter_request(self):
|
||||||
|
|
Loading…
Reference in New Issue