Bug description

When importing dashboard and the import fails, the error displayed is leaking HTML code of error page.

It seems there is other exception thrown when the import exception is processed.

Screenshots/recordings

Apache Superset Dashboard Import Error Shows HTML Code

Superset version

4.0.2

Python version

3.9

Node version

16

Browser

Chrome

Additional context

Extracted stacktrace from the displayed message:

Traceback (most recent call last): 
        File "/usr/local/lib/python3.10/site-packages/flask/app.py", line 1823, in full_dispatch_request rv = self.dispatch_request() 
        File "/usr/local/lib/python3.10/site-packages/flask/app.py", line 1799, in dispatch_request return self.ensure_sync(self.view_functions[rule.endpoint])(**view_args) 
        File "/usr/local/lib/python3.10/site-packages/flask_appbuilder/security/decorators.py", line 95, in wraps return f(self, *args, **kwargs) 
        File "/app/superset/views/base_api.py", line 127, in wraps raise ex File "/app/superset/views/base_api.py", line 121, in wraps duration, response = time_function(f, self, *args, **kwargs) 
        File "/app/superset/utils/core.py", line 1470, in time_function response = func(*args, **kwargs) 
        File "/app/superset/utils/log.py", line 255, in wrapper value = f(*args, **kwargs) 
        File "/app/superset/views/base_api.py", line 108, in wraps return f(self, *args, **kwargs) 
        File "/app/superset/dashboards/api.py", line 1135, in import_ command.run() 
        File "/app/superset/commands/dashboard/importers/dispatcher.py", line 64, in run raise exc 
        File "/app/superset/commands/dashboard/importers/dispatcher.py", line 57, in run command.run() 
        File "/app/superset/commands/importers/v1/__init__.py", line 71, in run self.validate() 
        File "/app/superset/commands/importers/v1/__init__.py", line 109, in validate raise CommandInvalidError( superset.commands.exceptions.CommandInvalidError: Error importing dashboard During handling of the above exception, another exception occurred:

        Traceback (most recent call last): 
        File "/usr/local/lib/python3.10/site-packages/flask/app.py", line 2529, in wsgi_app response = self.full_dispatch_request() 
        File "/usr/local/lib/python3.10/site-packages/flask/app.py", line 1825, in full_dispatch_request rv = self.handle_user_exception(e) 
        File "/usr/local/lib/python3.10/site-packages/flask_cors/extension.py", line 165, in wrapped_function return cors_after_request(app.make_response(f(*args, **kwargs))) 
        File "/usr/local/lib/python3.10/site-packages/flask/app.py", line 1676, in handle_user_exception return self.ensure_sync(handler)(e)
        File "/app/superset/views/base.py", line 505, in show_command_errors return json_errors_response( 
        File "/app/superset/views/base.py", line 165, in json_errors_response payload["errors"] = [dataclasses.asdict(error) for error in errors]
        File "/app/superset/views/base.py", line 165, in <listcomp> payload["errors"] = [dataclasses.asdict(error) for error in errors] 
        File "/usr/local/lib/python3.10/dataclasses.py", line 1238, in asdict return _asdict_inner(obj, dict_factory) 
        File "/usr/local/lib/python3.10/dataclasses.py", line 1245, in _asdict_inner value = _asdict_inner(getattr(obj, f.name), dict_factory) 
        File "/usr/local/lib/python3.10/dataclasses.py", line 1275, in _asdict_inner return type(obj)((_asdict_inner(k, dict_factory), 
        File "/usr/local/lib/python3.10/dataclasses.py", line 1276, in <genexpr> _asdict_inner(v, dict_factory)) 
        File "/usr/local/lib/python3.10/dataclasses.py", line 1275, in _asdict_inner return type(obj)((_asdict_inner(k, dict_factory), 
        File "/usr/local/lib/python3.10/dataclasses.py", line 1276, in <genexpr> _asdict_inner(v, dict_factory)) 
        File "/usr/local/lib/python3.10/dataclasses.py", line 1275, in _asdict_inner return type(obj)((_asdict_inner(k, dict_factory), 
        File "/usr/local/lib/python3.10/dataclasses.py", line 1276, in <genexpr> _asdict_inner(v, dict_factory)) 
        File "/usr/local/lib/python3.10/dataclasses.py", line 1275, in _asdict_inner return type(obj)((_asdict_inner(k, dict_factory), TypeError: first argument must be callable or None 

Checklist

  • [X] I have searched Superset docs and Slack and didn't find a solution to my problem.
  • [X] I have searched the GitHub issue tracker and didn't find a similar bug report.
  • [X] I have checked Superset's logs for errors and if I found a relevant Python stacktrace, I included it here as text in the "additional context" section.

Comment From: dosubot[bot]

Hello, @OldrichDlouhy! I'm here to help you with your bug report. If you need assistance with anything else, feel free to ask.

I found a similar issue related to a "500 - Internal Server Error" during dashboard import, which was caused by a CommandInvalidError. The solution involves ensuring proper error handling and mapping the error to a SupersetErrorException or SupersetErrorsException with a specific status code and error type.

To address the HTML code leakage and improve error handling:

  1. Validation: Ensure the metadata and configurations are correct. The validate method in ImportAssetsCommand checks these and raises CommandInvalidError if there are issues.

  2. Error Handling: Use the error handler for CommandException to log the error and return a JSON error response. If the error is a CommandInvalidError, include additional normalized messages in the response.

Here's a snippet for handling the error:

@app.errorhandler(CommandException)
def show_command_errors(ex: CommandException) -> FlaskResponse:
    logger.warning("CommandException", exc_info=True)
    if "text/html" in request.accept_mimetypes and not app.config["DEBUG"]:
        path = files("superset") / "static/assets/500.html"
        return send_file(path, max_age=0), 500

    extra = ex.normalized_messages() if isinstance(ex, CommandInvalidError) else {}
    return json_error_response(
        [
            SupersetError(
                message=ex.message,
                error_type=SupersetErrorType.GENERIC_COMMAND_ERROR,
                level=get_error_level_from_status(ex.status),
                extra=extra,
            ),
        ],
        status=ex.status,
    )

Ensure this handler is set up in your Flask app to catch and process CommandInvalidError exceptions, providing a more informative error response [1][2].

To continue talking to Dosu, mention @dosu.

Comment From: remcoboerma

Had a similar issue using superset on Yunohost. This helpmed troubleshoot the error for me.

index f90708acf5..2fd0579a0f 100644
--- a/superset/commands/importers/v1/__init__.py
+++ b/superset/commands/importers/v1/__init__.py
@@ -104,6 +104,8 @@ class ImportModelsCommand(BaseCommand):
         self._prevent_overwrite_existing_model(exceptions)

         if exceptions:
+            import sys
+            print('NEEDLE',exceptions,file=sys.stderr)
             raise CommandInvalidError(
                 f"Error importing {self.model_name}",
                 exceptions,

Printing the exceptions that are otherwise invislble allowed me to see what was causing the import error (invalid data in the exported yaml file). As stderr is routed towards the service output it's viewable in the logs in yunohost.

It's not a solution, but solved my most urgend problem. Hope it helps someone else.

Comment From: rusackas

@mistercrunch @yousoph wasn't someone on our end looking at these HTML error messages? Not sure where that project stands.

Comment From: mistercrunch

Doing a quick check, seems https://github.com/apache/superset/blob/master/superset/dashboards/api.py#L1368 doesn't have the @handle_api_exception decorator applied, which may help in serving a json error.

More generally, and tagging @dpgaspar for input, wondering if/when/where we should use the @api, @has_api_access and @handle_api_exception in api-related views. Seems we may want to make sure we have a single @api decorator that does it all, and make sure it's applied consitently for all API-related views (?)

@dpgaspar I'm happy to take this on if we define the best approach.

Comment From: mistercrunch

The comment above would take care of making sure the API request returns a json payload as expected, but unclear if the frontend would handle that well out of the box or whether it needs to be touched. Next steps for someone here would be to: - craft a bad zip file that should trigger an error - add the @handle_api_exception decorator to the specific/related endpoint(s) - see how the frontend handles it

bonus points for: - surfacing a more useful exception than "import has failed" by bubbling up the right user-relevant error message safely

Comment From: rusackas

I was tempted to close this as inactive... but don't want to overstep. Is anyone able to confirm if this is still an issue in 4.1.2/5.0.0?

Comment From: rusackas

We haven't heard back that this is still an issue (a lot of stuff got cleaned up!) so I'll close it as not-planned/inactive, but ping me if it's still an issue in 5.0/master and we'll reopen/revisit!