mirror of
https://github.com/apache/superset.git
synced 2024-09-17 11:09:47 -04:00
feat: show rich error messages on past failed queries (#15158)
* feat: store SIP-40 error payload with queries * Set errors in query on load
This commit is contained in:
parent
e689b0d445
commit
d625f5f111
@ -118,6 +118,9 @@ export default function SouthPane({
|
|||||||
}
|
}
|
||||||
let results;
|
let results;
|
||||||
if (latestQuery) {
|
if (latestQuery) {
|
||||||
|
if (latestQuery?.extra?.errors) {
|
||||||
|
latestQuery.errors = latestQuery.extra.errors;
|
||||||
|
}
|
||||||
if (
|
if (
|
||||||
isFeatureEnabled(FeatureFlag.SQLLAB_BACKEND_PERSISTENCE) &&
|
isFeatureEnabled(FeatureFlag.SQLLAB_BACKEND_PERSISTENCE) &&
|
||||||
latestQuery.state === 'success' &&
|
latestQuery.state === 'success' &&
|
||||||
|
@ -14,12 +14,15 @@
|
|||||||
# KIND, either express or implied. See the License for the
|
# KIND, either express or implied. See the License for the
|
||||||
# specific language governing permissions and limitations
|
# specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
import re
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import List, Optional, TYPE_CHECKING
|
from typing import Any, Dict, List, Optional, Pattern, Tuple, TYPE_CHECKING
|
||||||
|
|
||||||
|
from flask_babel import gettext as __
|
||||||
from sqlalchemy.engine.reflection import Inspector
|
from sqlalchemy.engine.reflection import Inspector
|
||||||
|
|
||||||
from superset.db_engine_specs.base import BaseEngineSpec
|
from superset.db_engine_specs.base import BaseEngineSpec
|
||||||
|
from superset.errors import SupersetErrorType
|
||||||
from superset.utils import core as utils
|
from superset.utils import core as utils
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
@ -27,6 +30,9 @@ if TYPE_CHECKING:
|
|||||||
from superset.models.core import Database
|
from superset.models.core import Database
|
||||||
|
|
||||||
|
|
||||||
|
COLUMN_DOES_NOT_EXIST_REGEX = re.compile("no such column: (?P<column_name>.+)")
|
||||||
|
|
||||||
|
|
||||||
class SqliteEngineSpec(BaseEngineSpec):
|
class SqliteEngineSpec(BaseEngineSpec):
|
||||||
engine = "sqlite"
|
engine = "sqlite"
|
||||||
engine_name = "SQLite"
|
engine_name = "SQLite"
|
||||||
@ -53,6 +59,14 @@ class SqliteEngineSpec(BaseEngineSpec):
|
|||||||
"1969-12-28T00:00:00Z/P1W": "DATE({col}, 'weekday 0', '-7 days')",
|
"1969-12-28T00:00:00Z/P1W": "DATE({col}, 'weekday 0', '-7 days')",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
custom_errors: Dict[Pattern[str], Tuple[str, SupersetErrorType, Dict[str, Any]]] = {
|
||||||
|
COLUMN_DOES_NOT_EXIST_REGEX: (
|
||||||
|
__('We can\'t seem to resolve the column "%(column_name)s"'),
|
||||||
|
SupersetErrorType.COLUMN_DOES_NOT_EXIST_ERROR,
|
||||||
|
{},
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def epoch_to_dttm(cls) -> str:
|
def epoch_to_dttm(cls) -> str:
|
||||||
return "datetime({col}, 'unixepoch')"
|
return "datetime({col}, 'unixepoch')"
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
# KIND, either express or implied. See the License for the
|
# KIND, either express or implied. See the License for the
|
||||||
# specific language governing permissions and limitations
|
# specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
import dataclasses
|
||||||
import logging
|
import logging
|
||||||
import uuid
|
import uuid
|
||||||
from contextlib import closing
|
from contextlib import closing
|
||||||
@ -93,8 +94,17 @@ def handle_query_error(
|
|||||||
query.error_message = msg
|
query.error_message = msg
|
||||||
query.status = QueryStatus.FAILED
|
query.status = QueryStatus.FAILED
|
||||||
query.tmp_table_name = None
|
query.tmp_table_name = None
|
||||||
|
|
||||||
|
# extract DB-specific errors (invalid column, eg)
|
||||||
|
errors = [
|
||||||
|
dataclasses.asdict(error)
|
||||||
|
for error in query.database.db_engine_spec.extract_errors(msg)
|
||||||
|
]
|
||||||
|
if errors:
|
||||||
|
query.set_extra_json_key("errors", errors)
|
||||||
|
|
||||||
session.commit()
|
session.commit()
|
||||||
payload.update({"status": query.status, "error": msg})
|
payload.update({"status": query.status, "error": msg, "errors": errors})
|
||||||
if troubleshooting_link:
|
if troubleshooting_link:
|
||||||
payload["link"] = troubleshooting_link
|
payload["link"] = troubleshooting_link
|
||||||
return payload
|
return payload
|
||||||
|
@ -73,6 +73,7 @@ from superset.dashboards.dao import DashboardDAO
|
|||||||
from superset.databases.dao import DatabaseDAO
|
from superset.databases.dao import DatabaseDAO
|
||||||
from superset.databases.filters import DatabaseFilter
|
from superset.databases.filters import DatabaseFilter
|
||||||
from superset.datasets.commands.exceptions import DatasetNotFoundError
|
from superset.datasets.commands.exceptions import DatasetNotFoundError
|
||||||
|
from superset.errors import SupersetError
|
||||||
from superset.exceptions import (
|
from superset.exceptions import (
|
||||||
CacheLoadError,
|
CacheLoadError,
|
||||||
CertificateException,
|
CertificateException,
|
||||||
@ -2427,15 +2428,15 @@ class Superset(BaseSupersetView): # pylint: disable=too-many-public-methods
|
|||||||
raise SupersetGenericDBErrorException(utils.error_msg_from_exception(ex))
|
raise SupersetGenericDBErrorException(utils.error_msg_from_exception(ex))
|
||||||
|
|
||||||
if data.get("status") == QueryStatus.FAILED:
|
if data.get("status") == QueryStatus.FAILED:
|
||||||
msg = data["error"]
|
# new error payload with rich context
|
||||||
query = _session.query(Query).filter_by(id=query_id).one()
|
if data["errors"]:
|
||||||
database = query.database
|
raise SupersetErrorsException(
|
||||||
db_engine_spec = database.db_engine_spec
|
[SupersetError(**params) for params in data["errors"]]
|
||||||
errors = db_engine_spec.extract_errors(msg)
|
)
|
||||||
_session.close()
|
|
||||||
if errors:
|
# old string-only error message
|
||||||
raise SupersetErrorsException(errors)
|
raise SupersetGenericDBErrorException(data["error"])
|
||||||
raise SupersetGenericDBErrorException(msg)
|
|
||||||
return json_success(payload)
|
return json_success(payload)
|
||||||
|
|
||||||
@has_access_api
|
@has_access_api
|
||||||
|
Loading…
Reference in New Issue
Block a user