From 2055ecc1ba86fd79d96e2b60b90de1a1f54421f5 Mon Sep 17 00:00:00 2001 From: Erik Ritter Date: Thu, 6 Aug 2020 13:22:24 -0700 Subject: [PATCH] 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> --- docs/issue_code_reference.rst | 9 + superset-frontend/images/icons/warning.svg | 2 +- .../ErrorMessage/DatabaseErrorMessage.tsx | 91 ++++++++ .../components/ErrorMessage/ErrorAlert.tsx | 200 ++++++++++++++++++ .../ErrorMessageWithStackTrace.tsx | 46 ++-- .../ErrorMessage/TimeoutErrorMessage.tsx | 168 ++------------- .../src/setup/setupErrorMessages.ts | 5 + superset/db_engine_specs/athena.py | 1 + superset/db_engine_specs/base.py | 9 +- superset/db_engine_specs/bigquery.py | 1 + superset/db_engine_specs/clickhouse.py | 1 + superset/db_engine_specs/cockroachdb.py | 1 + superset/db_engine_specs/db2.py | 1 + superset/db_engine_specs/dremio.py | 1 + superset/db_engine_specs/drill.py | 1 + superset/db_engine_specs/druid.py | 1 + superset/db_engine_specs/elasticsearch.py | 1 + superset/db_engine_specs/exasol.py | 1 + superset/db_engine_specs/gsheets.py | 1 + superset/db_engine_specs/hana.py | 1 + superset/db_engine_specs/hive.py | 1 + superset/db_engine_specs/impala.py | 1 + superset/db_engine_specs/kylin.py | 1 + superset/db_engine_specs/mssql.py | 1 + superset/db_engine_specs/mysql.py | 1 + superset/db_engine_specs/oracle.py | 1 + superset/db_engine_specs/pinot.py | 1 + superset/db_engine_specs/postgres.py | 1 + superset/db_engine_specs/presto.py | 6 +- superset/db_engine_specs/redshift.py | 1 + superset/db_engine_specs/snowflake.py | 1 + superset/db_engine_specs/sqlite.py | 1 + superset/db_engine_specs/teradata.py | 1 + superset/db_engine_specs/vertica.py | 1 + superset/errors.py | 8 +- 35 files changed, 384 insertions(+), 185 deletions(-) create mode 100644 superset-frontend/src/components/ErrorMessage/DatabaseErrorMessage.tsx create mode 100644 superset-frontend/src/components/ErrorMessage/ErrorAlert.tsx diff --git a/docs/issue_code_reference.rst b/docs/issue_code_reference.rst index ef89d1e51..25518ed2f 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 df7fc6d11..118955914 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 000000000..6968163aa --- /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 000000000..8dd832e88 --- /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 04befca01..ab44376e9 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 029f1e052..c04d3d28e 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 269e7c573..29f3940a1 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 eaf76e858..91e1069ff 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 120ea4ba9..cfd651b13 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 f45145f3b..ea33531b4 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 f02ef51ab..658aef01f 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 09fa98d58..f2f00c1a0 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 4087d2186..ba03366de 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 33f027d3d..4a1142424 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 b1b0ceaf6..7406d654c 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 5a81ea9da..f941fd1ba 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 f1cc561b0..4f6067710 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 23449f0f8..a485be542 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 698728e09..308bc560a 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 2514194f4..c4b157a62 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 206e1c7d3..9cbcfb7e9 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 c0c4f0743..9d8dc9137 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 9f828d11d..4a64091d1 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 a8a6b9a86..abe1f6c2a 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 7d750d3aa..4d57c3807 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 813b1507a..24ebdb00d 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 853d756e3..d79a6947a 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 dceac26b1..0ccf51dba 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 fc74eb2cc..16e6a4c53 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 5c9f4bc46..ef7b0e29a 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 8b78b5248..e7282b884 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 72f1f3187..c14b8b937 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 0226baef6..88bffa5a0 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 e5f890122..5f3d99c6c 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 f2fda00bb..08a8cd80a 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."), + } + ], }