diff --git a/docs/issue_code_reference.rst b/docs/issue_code_reference.rst index ef89d1e51d3..25518ed2feb 100644 --- a/docs/issue_code_reference.rst +++ b/docs/issue_code_reference.rst @@ -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. diff --git a/superset-frontend/images/icons/warning.svg b/superset-frontend/images/icons/warning.svg index df7fc6d117d..118955914f5 100644 --- a/superset-frontend/images/icons/warning.svg +++ b/superset-frontend/images/icons/warning.svg @@ -17,6 +17,6 @@ under the License. --> - + diff --git a/superset-frontend/src/components/ErrorMessage/DatabaseErrorMessage.tsx b/superset-frontend/src/components/ErrorMessage/DatabaseErrorMessage.tsx new file mode 100644 index 00000000000..6968163aa7b --- /dev/null +++ b/superset-frontend/src/components/ErrorMessage/DatabaseErrorMessage.tsx @@ -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) { + const { extra, level, message } = error; + + const isVisualization = ['dashboard', 'explore'].includes(source); + + const body = ( + <> +

+ {t('This may be triggered by:')} +
+ {extra.issue_codes + .map(issueCode => ) + .reduce((prev, curr) => [prev,
, curr])} +

+ {isVisualization && extra.owners && ( + <> +
+

+ {tn( + 'Please reach out to the Chart Owner for assistance.', + 'Please reach out to the Chart Owners for assistance.', + extra.owners.length, + )} +

+

+ {tn( + 'Chart Owner: %s', + 'Chart Owners: %s', + extra.owners.length, + extra.owners.join(', '), + )} +

+ + )} + + ); + + const copyText = `${message} +${t('This may be triggered by:')} +${extra.issue_codes.map(issueCode => issueCode.message).join('\n')}`; + + return ( + + ); +} + +export default DatabaseErrorMessage; diff --git a/superset-frontend/src/components/ErrorMessage/ErrorAlert.tsx b/superset-frontend/src/components/ErrorMessage/ErrorAlert.tsx new file mode 100644 index 00000000000..8dd832e881e --- /dev/null +++ b/superset-frontend/src/components/ErrorMessage/ErrorAlert.tsx @@ -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 ( + +
+ + + {title} + + {!isExpandable && ( + setIsModalOpen(true)}> + {t('See More')} + + )} +
+ {isExpandable ? ( +
+

{subtitle}

+ {body && ( + <> + {!isBodyExpanded && ( + setIsBodyExpanded(true)} + > + {t('See More')} + + )} + {isBodyExpanded && ( + <> +
+ {body} + setIsBodyExpanded(false)} + > + {t('See Less')} + + + )} + + )} +
+ ) : ( + setIsModalOpen(false)} + > + + + +
{title}
+
+ setIsModalOpen(false)} + > + + +
+ +

{subtitle}

+
+ {body} +
+ + {copyText && ( + {t('Copy Message')}} + /> + )} + + +
+ )} +
+ ); +} diff --git a/superset-frontend/src/components/ErrorMessage/ErrorMessageWithStackTrace.tsx b/superset-frontend/src/components/ErrorMessage/ErrorMessageWithStackTrace.tsx index 04befca0168..ab44376e931 100644 --- a/superset-frontend/src/components/ErrorMessage/ErrorMessageWithStackTrace.tsx +++ b/superset-frontend/src/components/ErrorMessage/ErrorMessageWithStackTrace.tsx @@ -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 ( -
- setShowStackTrace(!showStackTrace)} - > - {message || t('An error occurred.')} - {link && ( - - (Request Access) - - )} - - {stackTrace && ( - -
{stackTrace}
-
- )} -
+ + {link && ( + + (Request Access) + + )} +
+ {stackTrace &&
{stackTrace}
} + + ) : undefined + } + /> ); } diff --git a/superset-frontend/src/components/ErrorMessage/TimeoutErrorMessage.tsx b/superset-frontend/src/components/ErrorMessage/TimeoutErrorMessage.tsx index 029f1e0522c..c04d3d28ee1 100644 --- a/superset-frontend/src/components/ErrorMessage/TimeoutErrorMessage.tsx +++ b/superset-frontend/src/components/ErrorMessage/TimeoutErrorMessage.tsx @@ -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) { - 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( 'We’re having trouble loading this visualization. Queries are set to timeout after %s second.', 'We’re 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 = ( <>

{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 ( - -

- - - {t('Timeout Error')} - - {!isExpandable && ( - setIsModalOpen(true)}> - {t('See More')} - - )} -
- {isExpandable ? ( -
-

{title}

- {!isMessageExpanded && ( - setIsMessageExpanded(true)} - > - {t('See More')} - - )} - {isMessageExpanded && ( - <> -
- {message} - setIsMessageExpanded(false)} - > - {t('See Less')} - - - )} -
- ) : ( - setIsModalOpen(false)}> - - - -
{t('Timeout Error')}
-
- setIsModalOpen(false)} - > - - -
- -

{title}

-
- {message} -
- - {t('Copy Message')}} - /> - - -
- )} -
+ ); } diff --git a/superset-frontend/src/setup/setupErrorMessages.ts b/superset-frontend/src/setup/setupErrorMessages.ts index 269e7c57352..29f3940a15e 100644 --- a/superset-frontend/src/setup/setupErrorMessages.ts +++ b/superset-frontend/src/setup/setupErrorMessages.ts @@ -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(); } diff --git a/superset/db_engine_specs/athena.py b/superset/db_engine_specs/athena.py index eaf76e858d2..91e1069ff68 100644 --- a/superset/db_engine_specs/athena.py +++ b/superset/db_engine_specs/athena.py @@ -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}", diff --git a/superset/db_engine_specs/base.py b/superset/db_engine_specs/base.py index 120ea4ba9a2..cfd651b1363 100644 --- a/superset/db_engine_specs/base.py +++ b/superset/db_engine_specs/base.py @@ -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}, ) ) ] diff --git a/superset/db_engine_specs/bigquery.py b/superset/db_engine_specs/bigquery.py index f45145f3bd1..ea33531b429 100644 --- a/superset/db_engine_specs/bigquery.py +++ b/superset/db_engine_specs/bigquery.py @@ -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 """ diff --git a/superset/db_engine_specs/clickhouse.py b/superset/db_engine_specs/clickhouse.py index f02ef51abad..658aef01f21 100644 --- a/superset/db_engine_specs/clickhouse.py +++ b/superset/db_engine_specs/clickhouse.py @@ -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 diff --git a/superset/db_engine_specs/cockroachdb.py b/superset/db_engine_specs/cockroachdb.py index 09fa98d587a..f2f00c1a047 100644 --- a/superset/db_engine_specs/cockroachdb.py +++ b/superset/db_engine_specs/cockroachdb.py @@ -19,3 +19,4 @@ from superset.db_engine_specs.postgres import PostgresEngineSpec class CockroachDbEngineSpec(PostgresEngineSpec): engine = "cockroachdb" + engine_name = "CockroachDB" diff --git a/superset/db_engine_specs/db2.py b/superset/db_engine_specs/db2.py index 4087d218647..ba03366de41 100644 --- a/superset/db_engine_specs/db2.py +++ b/superset/db_engine_specs/db2.py @@ -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 diff --git a/superset/db_engine_specs/dremio.py b/superset/db_engine_specs/dremio.py index 33f027d3d1e..4a11424247c 100644 --- a/superset/db_engine_specs/dremio.py +++ b/superset/db_engine_specs/dremio.py @@ -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}", diff --git a/superset/db_engine_specs/drill.py b/superset/db_engine_specs/drill.py index b1b0ceaf684..7406d654cbe 100644 --- a/superset/db_engine_specs/drill.py +++ b/superset/db_engine_specs/drill.py @@ -28,6 +28,7 @@ class DrillEngineSpec(BaseEngineSpec): """Engine spec for Apache Drill""" engine = "drill" + engine_name = "Apache Drill" _time_grain_expressions = { None: "{col}", diff --git a/superset/db_engine_specs/druid.py b/superset/db_engine_specs/druid.py index 5a81ea9dac6..f941fd1bad7 100644 --- a/superset/db_engine_specs/druid.py +++ b/superset/db_engine_specs/druid.py @@ -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 diff --git a/superset/db_engine_specs/elasticsearch.py b/superset/db_engine_specs/elasticsearch.py index f1cc561b0af..4f6067710f6 100644 --- a/superset/db_engine_specs/elasticsearch.py +++ b/superset/db_engine_specs/elasticsearch.py @@ -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 diff --git a/superset/db_engine_specs/exasol.py b/superset/db_engine_specs/exasol.py index 23449f0f88d..a485be54236 100644 --- a/superset/db_engine_specs/exasol.py +++ b/superset/db_engine_specs/exasol.py @@ -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 diff --git a/superset/db_engine_specs/gsheets.py b/superset/db_engine_specs/gsheets.py index 698728e098f..308bc560a4f 100644 --- a/superset/db_engine_specs/gsheets.py +++ b/superset/db_engine_specs/gsheets.py @@ -21,5 +21,6 @@ class GSheetsEngineSpec(SqliteEngineSpec): """Engine for Google spreadsheets""" engine = "gsheets" + engine_name = "Google Sheets" allows_joins = False allows_subqueries = False diff --git a/superset/db_engine_specs/hana.py b/superset/db_engine_specs/hana.py index 2514194f4f4..c4b157a6249 100644 --- a/superset/db_engine_specs/hana.py +++ b/superset/db_engine_specs/hana.py @@ -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 diff --git a/superset/db_engine_specs/hive.py b/superset/db_engine_specs/hive.py index 206e1c7d357..9cbcfb7e93c 100644 --- a/superset/db_engine_specs/hive.py +++ b/superset/db_engine_specs/hive.py @@ -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 = { diff --git a/superset/db_engine_specs/impala.py b/superset/db_engine_specs/impala.py index c0c4f074303..9d8dc91374f 100644 --- a/superset/db_engine_specs/impala.py +++ b/superset/db_engine_specs/impala.py @@ -27,6 +27,7 @@ class ImpalaEngineSpec(BaseEngineSpec): """Engine spec for Cloudera's Impala""" engine = "impala" + engine_name = "Apache Impala" _time_grain_expressions = { None: "{col}", diff --git a/superset/db_engine_specs/kylin.py b/superset/db_engine_specs/kylin.py index 9f828d11d65..4a64091d155 100644 --- a/superset/db_engine_specs/kylin.py +++ b/superset/db_engine_specs/kylin.py @@ -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}", diff --git a/superset/db_engine_specs/mssql.py b/superset/db_engine_specs/mssql.py index a8a6b9a8688..abe1f6c2a2b 100644 --- a/superset/db_engine_specs/mssql.py +++ b/superset/db_engine_specs/mssql.py @@ -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 diff --git a/superset/db_engine_specs/mysql.py b/superset/db_engine_specs/mysql.py index 7d750d3aae4..4d57c380761 100644 --- a/superset/db_engine_specs/mysql.py +++ b/superset/db_engine_specs/mysql.py @@ -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 = { diff --git a/superset/db_engine_specs/oracle.py b/superset/db_engine_specs/oracle.py index 813b1507a64..24ebdb00d4f 100644 --- a/superset/db_engine_specs/oracle.py +++ b/superset/db_engine_specs/oracle.py @@ -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 diff --git a/superset/db_engine_specs/pinot.py b/superset/db_engine_specs/pinot.py index 853d756e340..d79a6947ade 100644 --- a/superset/db_engine_specs/pinot.py +++ b/superset/db_engine_specs/pinot.py @@ -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 diff --git a/superset/db_engine_specs/postgres.py b/superset/db_engine_specs/postgres.py index dceac26b1cf..0ccf51dba07 100644 --- a/superset/db_engine_specs/postgres.py +++ b/superset/db_engine_specs/postgres.py @@ -38,6 +38,7 @@ class PostgresBaseEngineSpec(BaseEngineSpec): """ Abstract class for Postgres 'like' databases """ engine = "" + engine_name = "PostgreSQL" _time_grain_expressions = { None: "{col}", diff --git a/superset/db_engine_specs/presto.py b/superset/db_engine_specs/presto.py index fc74eb2cccc..16e6a4c53b2 100644 --- a/superset/db_engine_specs/presto.py +++ b/superset/db_engine_specs/presto.py @@ -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 diff --git a/superset/db_engine_specs/redshift.py b/superset/db_engine_specs/redshift.py index 5c9f4bc4676..ef7b0e29a3e 100644 --- a/superset/db_engine_specs/redshift.py +++ b/superset/db_engine_specs/redshift.py @@ -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 diff --git a/superset/db_engine_specs/snowflake.py b/superset/db_engine_specs/snowflake.py index 8b78b52487b..e7282b8847f 100644 --- a/superset/db_engine_specs/snowflake.py +++ b/superset/db_engine_specs/snowflake.py @@ -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 diff --git a/superset/db_engine_specs/sqlite.py b/superset/db_engine_specs/sqlite.py index 72f1f318712..c14b8b937a5 100644 --- a/superset/db_engine_specs/sqlite.py +++ b/superset/db_engine_specs/sqlite.py @@ -29,6 +29,7 @@ if TYPE_CHECKING: class SqliteEngineSpec(BaseEngineSpec): engine = "sqlite" + engine_name = "SQLite" _time_grain_expressions = { None: "{col}", diff --git a/superset/db_engine_specs/teradata.py b/superset/db_engine_specs/teradata.py index 0226baef62e..88bffa5a075 100644 --- a/superset/db_engine_specs/teradata.py +++ b/superset/db_engine_specs/teradata.py @@ -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 diff --git a/superset/db_engine_specs/vertica.py b/superset/db_engine_specs/vertica.py index e5f89012209..5f3d99c6c54 100644 --- a/superset/db_engine_specs/vertica.py +++ b/superset/db_engine_specs/vertica.py @@ -19,3 +19,4 @@ from superset.db_engine_specs.postgres import PostgresBaseEngineSpec class VerticaEngineSpec(PostgresBaseEngineSpec): engine = "vertica" + engine_name = "Vertica" diff --git a/superset/errors.py b/superset/errors.py index f2fda00bb86..08a8cd80a34 100644 --- a/superset/errors.py +++ b/superset/errors.py @@ -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."), + } + ], }