diff --git a/docs/src/pages/docs/Miscellaneous/issue_codes.mdx b/docs/src/pages/docs/Miscellaneous/issue_codes.mdx index 1a83ef5ef..37536ff54 100644 --- a/docs/src/pages/docs/Miscellaneous/issue_codes.mdx +++ b/docs/src/pages/docs/Miscellaneous/issue_codes.mdx @@ -129,7 +129,7 @@ running a command. Please reach out to your administrator. Superset encountered an unexpected error. ``` -Someething unexpected happened in the Superset backend. Please reach out +Something unexpected happened in the Superset backend. Please reach out to your administrator. ## Issue 1012 @@ -147,5 +147,13 @@ that the username is typed correctly and exists in the database. The password provided when connecting to a database is not valid. ``` -The user provided a password that is incorrect. Please check that the -password is typed correctly. +The user provided a password that is incorrect. Please check that the password is typed correctly. + +## Issue 1014 + +``` +Either the username or the password used are incorrect. +``` + +Either the username provided does not exist or the password was written incorrectly. Please +check that the username and password were typed correctly. diff --git a/superset-frontend/src/components/ErrorMessage/types.ts b/superset-frontend/src/components/ErrorMessage/types.ts index 7c98fcc59..00d21226a 100644 --- a/superset-frontend/src/components/ErrorMessage/types.ts +++ b/superset-frontend/src/components/ErrorMessage/types.ts @@ -36,6 +36,7 @@ export const ErrorTypeEnum = { 'TEST_CONNECTION_INVALID_HOSTNAME_ERROR', TEST_CONNECTION_PORT_CLOSED_ERROR: 'TEST_CONNECTION_PORT_CLOSED_ERROR', TEST_CONNECTION_HOST_DOWN_ERROR: 'TEST_CONNECTION_HOST_DOWN_ERROR', + TEST_CONNECTION_ACCESS_DENIED_ERROR: 'TEST_CONNECTION_ACCESS_DENIED_ERROR', // Viz errors VIZ_GET_DF_ERROR: 'VIZ_GET_DF_ERROR', diff --git a/superset/db_engine_specs/mysql.py b/superset/db_engine_specs/mysql.py index 3cb35e308..28a0a6a7f 100644 --- a/superset/db_engine_specs/mysql.py +++ b/superset/db_engine_specs/mysql.py @@ -19,6 +19,7 @@ from datetime import datetime from typing import Any, Callable, Dict, Match, Optional, Pattern, Tuple, Union from urllib import parse +from flask_babel import gettext as __ from sqlalchemy.dialects.mysql import ( BIT, DECIMAL, @@ -35,9 +36,21 @@ from sqlalchemy.engine.url import URL from sqlalchemy.types import TypeEngine from superset.db_engine_specs.base import BaseEngineSpec +from superset.errors import SupersetErrorType from superset.utils import core as utils from superset.utils.core import ColumnSpec, GenericDataType +# Regular expressions to catch custom errors +TEST_CONNECTION_ACCESS_DENIED_REGEX = re.compile( + "Access denied for user '(?P.*?)'@'(?P.*?)'. " +) +TEST_CONNECTION_INVALID_HOSTNAME_REGEX = re.compile( + "Unknown MySQL server host '(?P.*?)'." +) +TEST_CONNECTION_HOST_DOWN_REGEX = re.compile( + "Can't connect to MySQL server on '(?P.*?)'." +) + class MySQLEngineSpec(BaseEngineSpec): engine = "mysql" @@ -93,6 +106,21 @@ class MySQLEngineSpec(BaseEngineSpec): type_code_map: Dict[int, str] = {} # loaded from get_datatype only if needed + custom_errors = { + TEST_CONNECTION_ACCESS_DENIED_REGEX: ( + __('Either the username "%(username)s" or the password is incorrect.'), + SupersetErrorType.TEST_CONNECTION_ACCESS_DENIED_ERROR, + ), + TEST_CONNECTION_INVALID_HOSTNAME_REGEX: ( + __('Unknown MySQL server host "%(hostname)s".'), + SupersetErrorType.TEST_CONNECTION_INVALID_HOSTNAME_ERROR, + ), + TEST_CONNECTION_HOST_DOWN_REGEX: ( + __('The host "%(hostname)s" might be down and can\'t be reached.'), + SupersetErrorType.TEST_CONNECTION_HOST_DOWN_ERROR, + ), + } + @classmethod def convert_dttm(cls, target_type: str, dttm: datetime) -> Optional[str]: tt = target_type.upper() diff --git a/superset/errors.py b/superset/errors.py index 16d2216d9..c91e374e0 100644 --- a/superset/errors.py +++ b/superset/errors.py @@ -44,6 +44,7 @@ class SupersetErrorType(str, Enum): TEST_CONNECTION_INVALID_HOSTNAME_ERROR = "TEST_CONNECTION_INVALID_HOSTNAME_ERROR" TEST_CONNECTION_PORT_CLOSED_ERROR = "TEST_CONNECTION_PORT_CLOSED_ERROR" TEST_CONNECTION_HOST_DOWN_ERROR = "TEST_CONNECTION_HOST_DOWN_ERROR" + TEST_CONNECTION_ACCESS_DENIED_ERROR = "TEST_CONNECTION_ACCESS_DENIED_ERROR" # Viz errors VIZ_GET_DF_ERROR = "VIZ_GET_DF_ERROR" @@ -173,6 +174,12 @@ ERROR_TYPES_TO_ISSUE_CODES_MAPPING = { ), }, ], + SupersetErrorType.TEST_CONNECTION_ACCESS_DENIED_ERROR: [ + { + "code": 1014, + "message": _("Issue 1014 - Either the username or the password is wrong."), + } + ], } diff --git a/tests/db_engine_specs/mysql_tests.py b/tests/db_engine_specs/mysql_tests.py index 035b06f68..3c71dc8ac 100644 --- a/tests/db_engine_specs/mysql_tests.py +++ b/tests/db_engine_specs/mysql_tests.py @@ -20,6 +20,7 @@ from sqlalchemy.dialects import mysql from sqlalchemy.dialects.mysql import DATE, NVARCHAR, TEXT, VARCHAR from superset.db_engine_specs.mysql import MySQLEngineSpec +from superset.errors import ErrorLevel, SupersetError, SupersetErrorType from superset.utils.core import GenericDataType from tests.db_engine_specs.base_tests import TestDbEngineSpec @@ -104,3 +105,83 @@ class TestMySQLEngineSpecsDbEngineSpec(TestDbEngineSpec): exception = OperationalError(123, message) extracted_message = MySQLEngineSpec._extract_error_message(exception) assert extracted_message == message + + def test_extract_errors(self): + """ + Test that custom error messages are extracted correctly. + """ + msg = "mysql: Access denied for user 'test'@'testuser.com'. " + result = MySQLEngineSpec.extract_errors(Exception(msg)) + assert result == [ + SupersetError( + error_type=SupersetErrorType.TEST_CONNECTION_ACCESS_DENIED_ERROR, + message='Either the username "test" or the password is incorrect.', + level=ErrorLevel.ERROR, + extra={ + "engine_name": "MySQL", + "issue_codes": [ + { + "code": 1014, + "message": "Issue 1014 - Either the username or the password is wrong.", + } + ], + }, + ) + ] + + msg = "mysql: Unknown MySQL server host 'badhostname.com'. " + result = MySQLEngineSpec.extract_errors(Exception(msg)) + assert result == [ + SupersetError( + error_type=SupersetErrorType.TEST_CONNECTION_INVALID_HOSTNAME_ERROR, + message='Unknown MySQL server host "badhostname.com".', + level=ErrorLevel.ERROR, + extra={ + "engine_name": "MySQL", + "issue_codes": [ + { + "code": 1007, + "message": "Issue 1007 - The hostname provided can't be resolved.", + } + ], + }, + ) + ] + + msg = "mysql: Can't connect to MySQL server on 'badconnection.com'." + result = MySQLEngineSpec.extract_errors(Exception(msg)) + assert result == [ + SupersetError( + error_type=SupersetErrorType.TEST_CONNECTION_HOST_DOWN_ERROR, + message='The host "badconnection.com" might be down and can\'t be reached.', + level=ErrorLevel.ERROR, + extra={ + "engine_name": "MySQL", + "issue_codes": [ + { + "code": 1007, + "message": "Issue 1007 - The hostname provided can't be resolved.", + } + ], + }, + ) + ] + + msg = "mysql: Can't connect to MySQL server on '93.184.216.34'." + result = MySQLEngineSpec.extract_errors(Exception(msg)) + assert result == [ + SupersetError( + error_type=SupersetErrorType.TEST_CONNECTION_HOST_DOWN_ERROR, + message='The host "93.184.216.34" might be down and can\'t be reached.', + level=ErrorLevel.ERROR, + extra={ + "engine_name": "MySQL", + "issue_codes": [ + { + "code": 10007, + "message": "Issue 1007 - The hostname provided can't be resolved.", + } + ], + }, + ) + ]