feat: refactor error components and add database issue code (#10473)

* feat: refactor error components and add database issue code

* Apply suggestions from code review

Co-authored-by: John Bodley <4567245+john-bodley@users.noreply.github.com>

Co-authored-by: John Bodley <4567245+john-bodley@users.noreply.github.com>
This commit is contained in:
Erik Ritter 2020-08-06 13:22:24 -07:00 committed by GitHub
parent 62b873e3da
commit 2055ecc1ba
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
35 changed files with 384 additions and 185 deletions

View File

@ -37,3 +37,12 @@ Issue 1001
The database is under an unusual load.
Your query may have timed out because of unusually high load on the database engine. You can make your query simpler, or wait until the database is under less load and try again.
Issue 1002
""""""""""
.. code-block:: text
The database returned an unexpected error.
Your query failed because of an error that occurred on the database. This may be due to a syntax error, a bug in your query, or some other internal failure within the database. This is usually not an issue within Superset, but instead a problem with the underlying database that serves your query.

View File

@ -17,6 +17,6 @@
under the License.
-->
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M12 2C6.47715 2 2 6.47715 2 12C2 17.5228 6.47715 22 12 22C17.5228 22 22 17.5228 22 12C22 9.34784 20.9464 6.8043 19.0711 4.92893C17.1957 3.05357 14.6522 2 12 2Z" fill="#666666"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M12 2C6.47715 2 2 6.47715 2 12C2 17.5228 6.47715 22 12 22C17.5228 22 22 17.5228 22 12C22 9.34784 20.9464 6.8043 19.0711 4.92893C17.1957 3.05357 14.6522 2 12 2Z" fill="currentColor"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M11.0003 8C11.0003 7.44772 11.448 7 12.0003 7C12.5526 7 13.0003 7.44772 13.0003 8V12C13.0003 12.5523 12.5526 13 12.0003 13C11.448 13 11.0003 12.5523 11.0003 12V8ZM11.2903 15.29C11.3854 15.199 11.4975 15.1276 11.6203 15.08C11.8637 14.98 12.1368 14.98 12.3803 15.08C12.503 15.1276 12.6152 15.199 12.7103 15.29C12.893 15.4816 12.9966 15.7352 13.0003 16C12.9986 16.3326 12.8317 16.6426 12.555 16.8271C12.2783 17.0116 11.9279 17.0464 11.6203 16.92C11.4991 16.8694 11.3875 16.7983 11.2903 16.71C11.1031 16.5213 10.9987 16.2658 11.0003 16C10.9969 15.8688 11.0243 15.7387 11.0803 15.62C11.1309 15.4988 11.2019 15.3872 11.2903 15.29Z" fill="white"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -0,0 +1,91 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import React from 'react';
import { t, tn } from '@superset-ui/translation';
import { ErrorMessageComponentProps } from './types';
import IssueCode from './IssueCode';
import ErrorAlert from './ErrorAlert';
interface DatabaseErrorExtra {
owners?: string[];
issue_codes: {
code: number;
message: string;
}[];
engine_name: string | null;
}
function DatabaseErrorMessage({
error,
source = 'dashboard',
}: ErrorMessageComponentProps<DatabaseErrorExtra>) {
const { extra, level, message } = error;
const isVisualization = ['dashboard', 'explore'].includes(source);
const body = (
<>
<p>
{t('This may be triggered by:')}
<br />
{extra.issue_codes
.map<React.ReactNode>(issueCode => <IssueCode {...issueCode} />)
.reduce((prev, curr) => [prev, <br />, curr])}
</p>
{isVisualization && extra.owners && (
<>
<br />
<p>
{tn(
'Please reach out to the Chart Owner for assistance.',
'Please reach out to the Chart Owners for assistance.',
extra.owners.length,
)}
</p>
<p>
{tn(
'Chart Owner: %s',
'Chart Owners: %s',
extra.owners.length,
extra.owners.join(', '),
)}
</p>
</>
)}
</>
);
const copyText = `${message}
${t('This may be triggered by:')}
${extra.issue_codes.map(issueCode => issueCode.message).join('\n')}`;
return (
<ErrorAlert
title={t('%s Error', extra.engine_name || t('DB Engine'))}
subtitle={message}
level={level}
source={source}
copyText={copyText}
body={body}
/>
);
}
export default DatabaseErrorMessage;

View File

@ -0,0 +1,200 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import React, { useState, ReactNode } from 'react';
import { Modal } from 'react-bootstrap';
import { styled, supersetTheme } from '@superset-ui/style';
import { t } from '@superset-ui/translation';
import { noOp } from 'src/utils/common';
import Button from 'src/views/CRUD/dataset/Button';
import Icon from '../Icon';
import { ErrorLevel, ErrorSource } from './types';
import CopyToClipboard from '../CopyToClipboard';
const ErrorAlertDiv = styled.div<{ level: ErrorLevel }>`
align-items: center;
background-color: ${({ level, theme }) => theme.colors[level].light2};
border-radius: ${({ theme }) => theme.borderRadius}px;
border: 1px solid ${({ level, theme }) => theme.colors[level].base};
color: ${({ level, theme }) => theme.colors[level].dark2};
padding: ${({ theme }) => 2 * theme.gridUnit}px;
width: 100%;
.top-row {
display: flex;
justify-content: space-between;
}
.error-body {
padding-top: ${({ theme }) => theme.gridUnit}px;
padding-left: ${({ theme }) => 8 * theme.gridUnit}px;
}
.icon {
margin-right: ${({ theme }) => 2 * theme.gridUnit}px;
}
.link {
color: ${({ level, theme }) => theme.colors[level].dark2};
text-decoration: underline;
}
`;
const ErrorModal = styled(Modal)<{ level: ErrorLevel }>`
color: ${({ level, theme }) => theme.colors[level].dark2};
.icon {
margin-right: ${({ theme }) => 2 * theme.gridUnit}px;
}
.header {
align-items: center;
background-color: ${({ level, theme }) => theme.colors[level].light2};
display: flex;
justify-content: space-between;
font-size: ${({ theme }) => theme.typography.sizes.l}px;
// Remove clearfix hack as Superset is only used on modern browsers
::before,
::after {
content: unset;
}
}
`;
const LeftSideContent = styled.div`
align-items: center;
display: flex;
`;
interface ErrorAlertProps {
body: ReactNode;
copyText?: string;
level: ErrorLevel;
source?: ErrorSource;
subtitle: ReactNode;
title: ReactNode;
}
export default function ErrorAlert({
body,
copyText,
level,
source = 'dashboard',
subtitle,
title,
}: ErrorAlertProps) {
const [isModalOpen, setIsModalOpen] = useState(false);
const [isBodyExpanded, setIsBodyExpanded] = useState(false);
const isExpandable = ['explore', 'sqllab'].includes(source);
return (
<ErrorAlertDiv level={level}>
<div className="top-row">
<LeftSideContent>
<Icon
className="icon"
name={level === 'error' ? 'error' : 'warning'}
color={supersetTheme.colors[level].base}
/>
<strong>{title}</strong>
</LeftSideContent>
{!isExpandable && (
<a href="#" className="link" onClick={() => setIsModalOpen(true)}>
{t('See More')}
</a>
)}
</div>
{isExpandable ? (
<div className="error-body">
<p>{subtitle}</p>
{body && (
<>
{!isBodyExpanded && (
<a
href="#"
className="link"
onClick={() => setIsBodyExpanded(true)}
>
{t('See More')}
</a>
)}
{isBodyExpanded && (
<>
<br />
{body}
<a
href="#"
className="link"
onClick={() => setIsBodyExpanded(false)}
>
{t('See Less')}
</a>
</>
)}
</>
)}
</div>
) : (
<ErrorModal
level={level}
show={isModalOpen}
onHide={() => setIsModalOpen(false)}
>
<Modal.Header className="header">
<LeftSideContent>
<Icon
className="icon"
name={level === 'error' ? 'error' : 'warning'}
color={supersetTheme.colors[level].base}
/>
<div className="title">{title}</div>
</LeftSideContent>
<span
role="button"
tabIndex={0}
onClick={() => setIsModalOpen(false)}
>
<Icon name="close" />
</span>
</Modal.Header>
<Modal.Body>
<p>{subtitle}</p>
<br />
{body}
</Modal.Body>
<Modal.Footer>
{copyText && (
<CopyToClipboard
text={copyText}
shouldShowText={false}
wrapped={false}
copyNode={<Button onClick={noOp}>{t('Copy Message')}</Button>}
/>
)}
<Button bsStyle="primary" onClick={() => setIsModalOpen(false)}>
{t('Close')}
</Button>
</Modal.Footer>
</ErrorModal>
)}
</ErrorAlertDiv>
);
}

View File

@ -16,13 +16,12 @@
* specific language governing permissions and limitations
* under the License.
*/
import React, { useState } from 'react';
// @ts-ignore
import { Alert, Collapse } from 'react-bootstrap';
import React from 'react';
import { t } from '@superset-ui/translation';
import getErrorMessageComponentRegistry from './getErrorMessageComponentRegistry';
import { SupersetError, ErrorSource } from './types';
import ErrorAlert from './ErrorAlert';
type Props = {
error?: SupersetError;
@ -39,8 +38,6 @@ export default function ErrorMessageWithStackTrace({
stackTrace,
source,
}: Props) {
const [showStackTrace, setShowStackTrace] = useState(false);
// Check if a custom error message component was registered for this message
if (error) {
const ErrorMessageComponent = getErrorMessageComponentRegistry().get(
@ -51,25 +48,26 @@ export default function ErrorMessageWithStackTrace({
}
}
// Fallback to the default error message renderer
return (
<div className={`stack-trace-container${stackTrace ? ' has-trace' : ''}`}>
<Alert
bsStyle="warning"
onClick={() => setShowStackTrace(!showStackTrace)}
>
{message || t('An error occurred.')}
{link && (
<a href={link} target="_blank" rel="noopener noreferrer">
(Request Access)
</a>
)}
</Alert>
{stackTrace && (
<Collapse in={showStackTrace}>
<pre>{stackTrace}</pre>
</Collapse>
)}
</div>
<ErrorAlert
level="warning"
title={t('Unexpected Error')}
subtitle={message}
copyText={message}
source={source}
body={
link || stackTrace ? (
<>
{link && (
<a href={link} target="_blank" rel="noopener noreferrer">
(Request Access)
</a>
)}
<br />
{stackTrace && <pre>{stackTrace}</pre>}
</>
) : undefined
}
/>
);
}

View File

@ -16,73 +16,12 @@
* specific language governing permissions and limitations
* under the License.
*/
import React, { useState } from 'react';
import { Modal } from 'react-bootstrap';
import { styled, supersetTheme } from '@superset-ui/style';
import React from 'react';
import { t, tn } from '@superset-ui/translation';
import { noOp } from 'src/utils/common';
import Button from 'src/views/CRUD/dataset/Button';
import Icon from '../Icon';
import { ErrorMessageComponentProps } from './types';
import CopyToClipboard from '../CopyToClipboard';
import IssueCode from './IssueCode';
const ErrorAlert = styled.div`
align-items: center;
background-color: ${({ theme }) => theme.colors.error.light2};
border-radius: ${({ theme }) => theme.borderRadius}px;
border: 1px solid ${({ theme }) => theme.colors.error.base};
color: ${({ theme }) => theme.colors.error.dark2};
padding: ${({ theme }) => 2 * theme.gridUnit}px;
width: 100%;
.top-row {
display: flex;
justify-content: space-between;
}
.error-body {
padding-top: ${({ theme }) => theme.gridUnit}px;
padding-left: ${({ theme }) => 8 * theme.gridUnit}px;
}
.icon {
margin-right: ${({ theme }) => 2 * theme.gridUnit}px;
}
.link {
color: ${({ theme }) => theme.colors.error.dark2};
text-decoration: underline;
}
`;
const ErrorModal = styled(Modal)`
color: ${({ theme }) => theme.colors.error.dark2};
.icon {
margin-right: ${({ theme }) => 2 * theme.gridUnit}px;
}
.header {
align-items: center;
background-color: ${({ theme }) => theme.colors.error.light2};
display: flex;
justify-content: space-between;
font-size: ${({ theme }) => theme.typography.sizes.l}px;
// Remove clearfix hack as Superset is only used on modern browsers
::before,
::after {
content: unset;
}
}
`;
const LeftSideContent = styled.div`
align-items: center;
display: flex;
`;
import ErrorAlert from './ErrorAlert';
interface TimeoutErrorExtra {
issue_codes: {
@ -97,21 +36,14 @@ function TimeoutErrorMessage({
error,
source,
}: ErrorMessageComponentProps<TimeoutErrorExtra>) {
const [isModalOpen, setIsModalOpen] = useState(false);
const [isMessageExpanded, setIsMessageExpanded] = useState(false);
const { extra } = error;
const { extra, level } = error;
const isVisualization = (['dashboard', 'explore'] as (
| string
| undefined
)[]).includes(source);
const isExpandable = (['explore', 'sqllab'] as (
| string
| undefined
)[]).includes(source);
const title = isVisualization
const subtitle = isVisualization
? tn(
'Were having trouble loading this visualization. Queries are set to timeout after %s second.',
'Were having trouble loading this visualization. Queries are set to timeout after %s seconds.',
@ -125,7 +57,7 @@ function TimeoutErrorMessage({
extra.timeout,
);
const message = (
const body = (
<>
<p>
{t('This may be triggered by:')}
@ -157,91 +89,19 @@ function TimeoutErrorMessage({
</>
);
const copyText = `${title}
const copyText = `${subtitle}
${t('This may be triggered by:')}
${extra.issue_codes.map(issueCode => issueCode.message).join('\n')}`;
return (
<ErrorAlert>
<div className="top-row">
<LeftSideContent>
<Icon
className="icon"
name="error"
color={supersetTheme.colors.error.base}
/>
<strong>{t('Timeout Error')}</strong>
</LeftSideContent>
{!isExpandable && (
<a href="#" className="link" onClick={() => setIsModalOpen(true)}>
{t('See More')}
</a>
)}
</div>
{isExpandable ? (
<div className="error-body">
<p>{title}</p>
{!isMessageExpanded && (
<a
href="#"
className="link"
onClick={() => setIsMessageExpanded(true)}
>
{t('See More')}
</a>
)}
{isMessageExpanded && (
<>
<br />
{message}
<a
href="#"
className="link"
onClick={() => setIsMessageExpanded(false)}
>
{t('See Less')}
</a>
</>
)}
</div>
) : (
<ErrorModal show={isModalOpen} onHide={() => setIsModalOpen(false)}>
<Modal.Header className="header">
<LeftSideContent>
<Icon
className="icon"
name="error"
color={supersetTheme.colors.error.base}
/>
<div className="title">{t('Timeout Error')}</div>
</LeftSideContent>
<span
role="button"
tabIndex={0}
onClick={() => setIsModalOpen(false)}
>
<Icon name="close" />
</span>
</Modal.Header>
<Modal.Body>
<p>{title}</p>
<br />
{message}
</Modal.Body>
<Modal.Footer>
<CopyToClipboard
text={copyText}
shouldShowText={false}
wrapped={false}
copyNode={<Button onClick={noOp}>{t('Copy Message')}</Button>}
/>
<Button bsStyle="primary" onClick={() => setIsModalOpen(false)}>
{t('Close')}
</Button>
</Modal.Footer>
</ErrorModal>
)}
</ErrorAlert>
<ErrorAlert
title={t('Timeout Error')}
subtitle={subtitle}
level={level}
source={source}
copyText={copyText}
body={body}
/>
);
}

View File

@ -19,6 +19,7 @@
import getErrorMessageComponentRegistry from 'src/components/ErrorMessage/getErrorMessageComponentRegistry';
import { ErrorTypeEnum } from 'src/components/ErrorMessage/types';
import TimeoutErrorMessage from 'src/components/ErrorMessage/TimeoutErrorMessage';
import DatabaseErrorMessage from 'src/components/ErrorMessage/DatabaseErrorMessage';
import setupErrorMessagesExtra from './setupErrorMessagesExtra';
@ -33,5 +34,9 @@ export default function setupErrorMessages() {
ErrorTypeEnum.BACKEND_TIMEOUT_ERROR,
TimeoutErrorMessage,
);
errorMessageComponentRegistry.registerValue(
ErrorTypeEnum.GENERIC_DB_ENGINE_ERROR,
DatabaseErrorMessage,
);
setupErrorMessagesExtra();
}

View File

@ -23,6 +23,7 @@ from superset.utils import core as utils
class AthenaEngineSpec(BaseEngineSpec):
engine = "awsathena"
engine_name = "Amazon Athena"
_time_grain_expressions = {
None: "{col}",

View File

@ -137,6 +137,9 @@ class BaseEngineSpec: # pylint: disable=too-many-public-methods
"""Abstract class for database engine specific configurations"""
engine = "base" # str as defined in sqlalchemy.engine.engine
engine_name: Optional[
str
] = None # used for user messages, overridden in child classes
_date_trunc_functions: Dict[str, str] = {}
_time_grain_expressions: Dict[Optional[str], str] = {}
time_groupby_inline = False
@ -569,7 +572,7 @@ class BaseEngineSpec: # pylint: disable=too-many-public-methods
return f"{cls.engine} error: {cls._extract_error_message(ex)}"
@classmethod
def _extract_error_message(cls, ex: Exception) -> Optional[str]:
def _extract_error_message(cls, ex: Exception) -> str:
"""Extract error message for queries"""
return utils.error_msg_from_exception(ex)
@ -579,9 +582,9 @@ class BaseEngineSpec: # pylint: disable=too-many-public-methods
dataclasses.asdict(
SupersetError(
error_type=SupersetErrorType.GENERIC_DB_ENGINE_ERROR,
message=cls.extract_error_message(ex),
message=cls._extract_error_message(ex),
level=ErrorLevel.ERROR,
extra={"engine": cls.engine},
extra={"engine_name": cls.engine_name},
)
)
]

View File

@ -37,6 +37,7 @@ class BigQueryEngineSpec(BaseEngineSpec):
As contributed by @mxmzdlv on issue #945"""
engine = "bigquery"
engine_name = "Google BigQuery"
max_column_name_length = 128
"""

View File

@ -25,6 +25,7 @@ class ClickHouseEngineSpec(BaseEngineSpec): # pylint: disable=abstract-method
"""Dialect for ClickHouse analytical DB."""
engine = "clickhouse"
engine_name = "ClickHouse"
time_secondary_columns = True
time_groupby_inline = True

View File

@ -19,3 +19,4 @@ from superset.db_engine_specs.postgres import PostgresEngineSpec
class CockroachDbEngineSpec(PostgresEngineSpec):
engine = "cockroachdb"
engine_name = "CockroachDB"

View File

@ -19,6 +19,7 @@ from superset.db_engine_specs.base import BaseEngineSpec, LimitMethod
class Db2EngineSpec(BaseEngineSpec):
engine = "ibm_db_sa"
engine_name = "IBM Db2"
limit_method = LimitMethod.WRAP_SQL
force_column_alias_quotes = True
max_column_name_length = 30

View File

@ -20,6 +20,7 @@ from superset.db_engine_specs.base import BaseEngineSpec
class DremioBaseEngineSpec(BaseEngineSpec):
engine = "dremio"
engine_name = "Dremio"
_time_grain_expressions = {
None: "{col}",

View File

@ -28,6 +28,7 @@ class DrillEngineSpec(BaseEngineSpec):
"""Engine spec for Apache Drill"""
engine = "drill"
engine_name = "Apache Drill"
_time_grain_expressions = {
None: "{col}",

View File

@ -35,6 +35,7 @@ class DruidEngineSpec(BaseEngineSpec): # pylint: disable=abstract-method
"""Engine spec for Druid.io"""
engine = "druid"
engine_name = "Apache Druid"
allows_joins = False
allows_subqueries = True

View File

@ -23,6 +23,7 @@ from superset.utils import core as utils
class ElasticSearchEngineSpec(BaseEngineSpec): # pylint: disable=abstract-method
engine = "elasticsearch"
engine_name = "ElasticSearch"
time_groupby_inline = True
time_secondary_columns = True
allows_joins = False

View File

@ -23,6 +23,7 @@ class ExasolEngineSpec(BaseEngineSpec): # pylint: disable=abstract-method
"""Engine spec for Exasol"""
engine = "exa"
engine_name = "Exasol"
max_column_name_length = 128
# Exasol's DATE_TRUNC function is PostgresSQL compatible

View File

@ -21,5 +21,6 @@ class GSheetsEngineSpec(SqliteEngineSpec):
"""Engine for Google spreadsheets"""
engine = "gsheets"
engine_name = "Google Sheets"
allows_joins = False
allows_subqueries = False

View File

@ -24,6 +24,7 @@ from superset.utils import core as utils
class HanaEngineSpec(PostgresBaseEngineSpec):
engine = "hana"
engine_name = "SAP HANA"
limit_method = LimitMethod.WRAP_SQL
force_column_alias_quotes = True
max_column_name_length = 30

View File

@ -55,6 +55,7 @@ class HiveEngineSpec(PrestoEngineSpec):
"""Reuses PrestoEngineSpec functionality."""
engine = "hive"
engine_name = "Apache Hive"
max_column_name_length = 767
# pylint: disable=line-too-long
_time_grain_expressions = {

View File

@ -27,6 +27,7 @@ class ImpalaEngineSpec(BaseEngineSpec):
"""Engine spec for Cloudera's Impala"""
engine = "impala"
engine_name = "Apache Impala"
_time_grain_expressions = {
None: "{col}",

View File

@ -25,6 +25,7 @@ class KylinEngineSpec(BaseEngineSpec): # pylint: disable=abstract-method
"""Dialect for Apache Kylin"""
engine = "kylin"
engine_name = "Apache Kylin"
_time_grain_expressions = {
None: "{col}",

View File

@ -32,6 +32,7 @@ logger = logging.getLogger(__name__)
class MssqlEngineSpec(BaseEngineSpec):
engine = "mssql"
engine_name = "Microsoft SQL"
limit_method = LimitMethod.WRAP_SQL
max_column_name_length = 128

View File

@ -26,6 +26,7 @@ from superset.utils import core as utils
class MySQLEngineSpec(BaseEngineSpec):
engine = "mysql"
engine_name = "MySQL"
max_column_name_length = 64
_time_grain_expressions = {

View File

@ -23,6 +23,7 @@ from superset.utils import core as utils
class OracleEngineSpec(BaseEngineSpec):
engine = "oracle"
engine_name = "Oracle"
limit_method = LimitMethod.WRAP_SQL
force_column_alias_quotes = True
max_column_name_length = 30

View File

@ -24,6 +24,7 @@ from superset.db_engine_specs.base import BaseEngineSpec, TimestampExpression
class PinotEngineSpec(BaseEngineSpec): # pylint: disable=abstract-method
engine = "pinot"
engine_name = "Apache Pinot"
allows_subqueries = False
allows_joins = False
allows_column_aliases = False

View File

@ -38,6 +38,7 @@ class PostgresBaseEngineSpec(BaseEngineSpec):
""" Abstract class for Postgres 'like' databases """
engine = ""
engine_name = "PostgreSQL"
_time_grain_expressions = {
None: "{col}",

View File

@ -27,6 +27,7 @@ from urllib import parse
import pandas as pd
import simplejson as json
from flask_babel import lazy_gettext as _
from sqlalchemy import Column, literal_column
from sqlalchemy.engine.base import Engine
from sqlalchemy.engine.reflection import Inspector
@ -104,6 +105,7 @@ def get_children(column: Dict[str, str]) -> List[Dict[str, str]]:
class PrestoEngineSpec(BaseEngineSpec):
engine = "presto"
engine_name = "Presto"
_time_grain_expressions = {
None: "{col}",
@ -780,7 +782,7 @@ class PrestoEngineSpec(BaseEngineSpec):
polled = cursor.poll()
@classmethod
def _extract_error_message(cls, ex: Exception) -> Optional[str]:
def _extract_error_message(cls, ex: Exception) -> str:
if (
hasattr(ex, "orig")
and type(ex.orig).__name__ == "DatabaseError" # type: ignore
@ -794,7 +796,7 @@ class PrestoEngineSpec(BaseEngineSpec):
)
if type(ex).__name__ == "DatabaseError" and hasattr(ex, "args") and ex.args:
error_dict = ex.args[0]
return error_dict.get("message")
return error_dict.get("message", _("Unknown Presto Error"))
return utils.error_msg_from_exception(ex)
@classmethod

View File

@ -19,6 +19,7 @@ from superset.db_engine_specs.postgres import PostgresBaseEngineSpec
class RedshiftEngineSpec(PostgresBaseEngineSpec):
engine = "redshift"
engine_name = "Amazon Redshift"
max_column_name_length = 127
@staticmethod

View File

@ -30,6 +30,7 @@ if TYPE_CHECKING:
class SnowflakeEngineSpec(PostgresBaseEngineSpec):
engine = "snowflake"
engine_name = "Snowflake"
force_column_alias_quotes = True
max_column_name_length = 256

View File

@ -29,6 +29,7 @@ if TYPE_CHECKING:
class SqliteEngineSpec(BaseEngineSpec):
engine = "sqlite"
engine_name = "SQLite"
_time_grain_expressions = {
None: "{col}",

View File

@ -21,6 +21,7 @@ class TeradataEngineSpec(BaseEngineSpec):
"""Dialect for Teradata DB."""
engine = "teradata"
engine_name = "Teradata"
limit_method = LimitMethod.WRAP_SQL
max_column_name_length = 30 # since 14.10 this is 128

View File

@ -19,3 +19,4 @@ from superset.db_engine_specs.postgres import PostgresBaseEngineSpec
class VerticaEngineSpec(PostgresBaseEngineSpec):
engine = "vertica"
engine_name = "Vertica"

View File

@ -61,7 +61,13 @@ ERROR_TYPES_TO_ISSUE_CODES_MAPPING = {
"code": 1001,
"message": _("Issue 1001 - The database is under an unusual load."),
},
]
],
SupersetErrorType.GENERIC_DB_ENGINE_ERROR: [
{
"code": 1002,
"message": _("Issue 1002 - The database returned an unexpected error."),
}
],
}