Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: initial work to make v1 API compatible with SIP-40 and SIP-41 #13960

Merged
merged 8 commits into from
Apr 7, 2021

Conversation

betodealmeida
Copy link
Member

@betodealmeida betodealmeida commented Apr 6, 2021

SUMMARY

This PR is a first step in making the v1 API compatible with SIP-40 and SIP-41.

Currently, the API returns exceptions using helper methods from Flask-AppBuilder, eg:

        try:
            TestConnectionDatabaseCommand(g.user, item).run()
            return self.response(200, message="OK")
        except DatabaseTestConnectionFailedError as ex:
            return self.response_422(message=str(ex))
        except SupersetErrorException as ex:
            return self.response(ex.status, message=ex.error.message)

These responses are simple JSON payloads, not compatible with the standard error format introduced in SIP-40. For example, when adding a database with a non-existent driver (foo://) this is returned:

{"message": "Could not load database driver: BaseEngineSpec"}

And this is the message displayed in the frontend (in a toast):

ERROR: Could not load database driver: BaseEngineSpec

To conform with SIP-41 I added a new decorator called handle_exception an error handler that captures exceptions and formats them according to SIP-40. This allows us to simplify the API logic, removing the need to capture exceptions when running commands (see the new test_connection method in this PR).

With the decorator, the backend now returns:

{
	"errors": [
		{
			"message": "Could not load database driver: BaseEngineSpec",
			"error_type": "GENERIC_COMMAND_ERROR",
			"level": "error",
			"extra": {
				"issue_codes": [
					{
						"code": 1010,
						"message": "Issue 1010 - Superset found an error while running a command."
					}
				]
			}
		}
	]
}

And the message is displayed correctly in the frontend as before, in a toast, without requiring any modifications:

ERROR: Could not load database driver: BaseEngineSpec

BEFORE/AFTER SCREENSHOTS OR ANIMATED GIF

See above.

TEST PLAN

Will add unit tests, I wanted to discuss this first.

ADDITIONAL INFORMATION

Copy link
Member

@etr2460 etr2460 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So in this case, you've got the DatabaseTestConnectionFailedError that test_connection can raise. Should this be something that extends a SupersetErrorException or should it be caught in the handle_exception wrapper and then return a response with a constructed SupersetError of type TEST_CONNECTION_PORT_CLOSED_ERROR or TEST_CONNECTION_HOST_DOWN_ERROR?

@codecov
Copy link

codecov bot commented Apr 6, 2021

Codecov Report

Merging #13960 (0ee5033) into master (f19f2c3) will increase coverage by 0.32%.
The diff coverage is 85.18%.

❗ Current head 0ee5033 differs from pull request most recent head a2d5833. Consider uploading reports for the commit a2d5833 to get more accurate results
Impacted file tree graph

@@            Coverage Diff             @@
##           master   #13960      +/-   ##
==========================================
+ Coverage   78.37%   78.70%   +0.32%     
==========================================
  Files         935      936       +1     
  Lines       47375    47407      +32     
  Branches     5973     5956      -17     
==========================================
+ Hits        37131    37310     +179     
+ Misses      10097     9954     -143     
+ Partials      147      143       -4     
Flag Coverage Δ
cypress 56.06% <100.00%> (+2.27%) ⬆️
hive 80.27% <84.00%> (+<0.01%) ⬆️
javascript 67.30% <100.00%> (+0.18%) ⬆️
mysql 80.56% <84.00%> (+<0.01%) ⬆️
postgres 80.60% <84.00%> (+<0.01%) ⬆️
presto 80.29% <84.00%> (+<0.01%) ⬆️
python 81.15% <84.00%> (+<0.01%) ⬆️
sqlite 80.16% <84.00%> (+<0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Impacted Files Coverage Δ
...rset-frontend/src/components/ErrorMessage/types.ts 100.00% <ø> (ø)
...src/dashboard/components/PropertiesModal/index.jsx 85.61% <ø> (ø)
superset/exceptions.py 96.66% <60.00%> (+0.05%) ⬆️
superset/views/base.py 76.11% <78.57%> (+0.28%) ⬆️
...rBar/CascadeFilters/CascadeFilterControl/index.tsx 85.71% <100.00%> (ø)
.../FilterBar/CascadeFilters/CascadePopover/index.tsx 83.09% <100.00%> (ø)
.../controls/MetricControl/AdhocMetricEditPopover.jsx 79.54% <100.00%> (ø)
superset/connectors/sqla/models.py 90.76% <100.00%> (-0.02%) ⬇️
superset/databases/api.py 91.80% <100.00%> (-0.23%) ⬇️
superset/databases/commands/exceptions.py 93.33% <100.00%> (+0.15%) ⬆️
... and 28 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update f19f2c3...a2d5833. Read the comment docs.

@betodealmeida
Copy link
Member Author

So in this case, you've got the DatabaseTestConnectionFailedError that test_connection can raise. Should this be something that extends a SupersetErrorException or should it be caught in the handle_exception wrapper and then return a response with a constructed SupersetError of type TEST_CONNECTION_PORT_CLOSED_ERROR or TEST_CONNECTION_HOST_DOWN_ERROR?

Right, I actually have another PR where I started doing that:

class DatabaseTestConnectionFailedError(SupersetErrorsException):
    message = _("Connection failed, please check your connection settings")

This way it will expose the proper errors (and additional errors that I'm adding, eg, TEST_CONNECTION_INVALID_USERNAME_ERROR).

Copy link
Member

@dpgaspar dpgaspar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sometime ago @john-bodley proposed to use this Flask capability: https://flask.palletsprojects.com/en/1.1.x/errorhandling/#registering. I like the idea, and it seems doable, that way we would not need to implement our own decorator for error handling

logger.warning(ex)
return json_errors_response(
errors=[
SupersetError(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we always going to throw HTTP 500 on these? would it make sense that the exception itself could identify the HTTP status code?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think that command exceptions should have HTTP status codes, they should care only about business logic and be decoupled from the API.

Ideally the API layer should do the mapping between command exceptions to SupersetError(s)Exception. The reason why I added CommandInvalidError and CommandException is so we can gradually implement that mapping, while in the meantime any non-covered command exceptions return a 500.

superset/databases/api.py Show resolved Hide resolved
@betodealmeida
Copy link
Member Author

Sometime ago @john-bodley proposed to use this Flask capability: https://flask.palletsprojects.com/en/1.1.x/errorhandling/#registering. I like the idea, and it seems doable, that way we would not need to implement our own decorator for error handling

I agree, I love that pattern. Let me give it a try, it might be easier since we register the error handler and then we can gradually change the APIs to raise SupersetErrorException and SupersetErrorsException (or ComandException initially, but I'd rather have all APIs only returning one of the first two).

docs/src/pages/docs/Miscellaneous/issue_codes.mdx Outdated Show resolved Hide resolved
superset/errors.py Outdated Show resolved Hide resolved
Copy link
Member

@etr2460 etr2460 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this all makes sense to me, stamping from the frontend perspective

"message": "Could not load database driver: BaseEngineSpec",
"errors": [
{
"message": "Could not load database driver: BaseEngineSpec",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i guess ideally in the future these messages will become actual errors with details on how to resolve them in the documentation

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, we're going to work on all the messaging in the test connection flow and add database flow.

@betodealmeida betodealmeida merged commit a82d72f into apache:master Apr 7, 2021
@etr2460 etr2460 mentioned this pull request Apr 9, 2021
8 tasks
allanco91 pushed a commit to allanco91/superset that referenced this pull request May 21, 2021
…pache#13960)

* WIP

* Use errorhandler

* Add response schema

* Fix status on HTTPException

* s/found/encountered/g

* Fix test

* Fix lint

* Fix lint and test
QAlexBall pushed a commit to QAlexBall/superset that referenced this pull request Dec 29, 2021
…pache#13960)

* WIP

* Use errorhandler

* Add response schema

* Fix status on HTTPException

* s/found/encountered/g

* Fix test

* Fix lint

* Fix lint and test
@mistercrunch mistercrunch added 🏷️ bot A label used by `supersetbot` to keep track of which PR where auto-tagged with release labels 🚢 1.2.0 labels Mar 12, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
🏷️ bot A label used by `supersetbot` to keep track of which PR where auto-tagged with release labels size/L 🚢 1.2.0
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants