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: add hook for dataset health check #11970

Merged

Conversation

graceguo-supercat
Copy link

@graceguo-supercat graceguo-supercat commented Dec 9, 2020

SUMMARY

Inside airbnb, we spent a lot of time on Superset dashboard and query performance issues. We create some guidelines for airbnb users, to help them write better queries to create virtual dataset. So we want to add a dataset health check logic when user explore a chart, and show some warning message if health check returns any message.

To decide whether a dataset is good or bad, this logic is probably very specific to each corporation. Some rules applied in airbnb may totally not relevant for Dropbox. So in the open source codebase, we only add an entry point,
DATASET_HEALTH_CHECK = _your function to check dataset quality_
which allow each company to implement and hook their own rules.

For example, after implements a rule that checks if the chart is using a virtual dataset:

def my_check(datasource: SqlaTable) -> str:
    message = None # pass the check no warning
    if datasource.type == "table" and datasource.sql:
        message = "You should replace this virtual datasource with physical table."
    return message

DATASET_HEALTH_CHECK = my_check
DATASET_HEALTH_CHECK.version = 0.1

I will see a warning message like this:

Screen Shot 2020-12-08 at 8 31 33 PM

Note
The health_check result will be saved with datasource record extra column. When user update datasource, or DATASET_HEALTH_CHECK.version is changed, health_check function will be triggered again and update the record in the database. Please see how we discussed different ideas for this feature.

TEST PLAN

CI

ADDITIONAL INFORMATION

  • Has associated issue:
  • Changes UI
  • Requires DB Migration.
  • Confirm DB Migration upgrade and downgrade tested.
  • Introduces new feature or API
  • Removes existing feature or API

cc @ktmud @etr2460

@@ -139,6 +139,10 @@ class ControlPanelsContainer extends React.Component {
controls[s].validationErrors.length > 0,
),
);
if (exploreState.dataset_health_check_message) {
Copy link
Author

@graceguo-supercat graceguo-supercat Dec 9, 2020

Choose a reason for hiding this comment

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

i can populate warning message from here, or from superset-ui-chart-controls:
https://github.com/apache-superset/superset-ui/blob/1f8a700b953efbed81d2c9970b514d7fe031ad2e/packages/superset-ui-chart-controls/src/shared-controls/index.tsx#L166

but it will need to create another PR in another code base.@ktmud do you have preference?

Copy link
Member

Choose a reason for hiding this comment

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

This component ControlPanelsContainer is generic and should stay generic. It should not contain logics related to any specific control field.

@ktmud
Copy link
Member

ktmud commented Dec 9, 2020

What if users change datasource or update datasource to point to a physical datasource in the Edit Datasource modal?

@codecov-io
Copy link

codecov-io commented Dec 9, 2020

Codecov Report

Merging #11970 (9800606) into master (cc44a2c) will decrease coverage by 6.01%.
The diff coverage is 81.81%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master   #11970      +/-   ##
==========================================
- Coverage   66.26%   60.24%   -6.02%     
==========================================
  Files         941      912      -29     
  Lines       45715    45009     -706     
  Branches     4395     4061     -334     
==========================================
- Hits        30293    27116    -3177     
- Misses      15287    17893    +2606     
+ Partials      135        0     -135     
Flag Coverage Δ
cypress 53.10% <66.66%> (+9.29%) ⬆️
javascript ?
python 64.07% <83.33%> (-0.19%) ⬇️

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

Impacted Files Coverage Δ
superset/config.py 90.47% <0.00%> (+0.39%) ⬆️
.../explore/components/controls/DatasourceControl.jsx 72.46% <66.66%> (+16.40%) ⬆️
superset/connectors/sqla/models.py 91.24% <100.00%> (+0.46%) ⬆️
superset/datasets/dao.py 87.28% <100.00%> (+0.21%) ⬆️
superset/views/core.py 72.76% <100.00%> (-1.95%) ⬇️
superset/views/datasource.py 95.00% <100.00%> (+0.17%) ⬆️
...uperset-frontend/src/dashboard/util/dnd-reorder.js 0.00% <0.00%> (-100.00%) ⬇️
...rset-frontend/src/dashboard/util/getEmptyLayout.js 0.00% <0.00%> (-100.00%) ⬇️
...dashboard/components/resizable/ResizableHandle.jsx 0.00% <0.00%> (-100.00%) ⬇️
.../src/dashboard/util/getFilterScopeFromNodesTree.js 0.00% <0.00%> (-93.48%) ⬇️
... and 458 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 cc44a2c...9800606. Read the comment docs.

@@ -197,6 +197,7 @@ export const controls = {
label: t('Dataset'),
default: null,
description: null,
healthCheckMessage: null,
Copy link
Member

Choose a reason for hiding this comment

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

I believe most if not all control configs in this file are not in use any more. We should clean them up.

Copy link
Author

Choose a reason for hiding this comment

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

yes. this line has no use :(

@graceguo-supercat
Copy link
Author

graceguo-supercat commented Dec 9, 2020

What if users change datasource or update datasource to point to a physical datasource in the Edit Datasource modal?

I don't offer any rule in the PR. the goal for this PR is to offer a hook in the codebase. Each company define their own rules.

@ktmud
Copy link
Member

ktmud commented Dec 9, 2020

I mean does the error message update when users switch datasources? It seems the check only happens at the first page load.

@graceguo-supercat
Copy link
Author

I mean does the error message update when users switch datasources? It seems the check only happens at the first page load.

No. save dataset data is on a different api. This health check will only happen when opening explore view.

@@ -1467,6 +1469,26 @@ class and any keys added via `ExtraCache`.
extra_cache_keys += sqla_query.extra_cache_keys
return extra_cache_keys

@event_logger.log_this
Copy link
Author

@graceguo-supercat graceguo-supercat Dec 15, 2020

Choose a reason for hiding this comment

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

can i add event log this way? want to monitor the duration for health check logic, in case it may affect explore view perf.

Copy link
Member

Choose a reason for hiding this comment

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

What you care most is the actual check function, the part where we check version and skip the health check (happens for most page views) is known to be pretty trivial. So maybe use this code instead:

with event_logger.log_context(action="datasource_health_check") as log:
    message = check(self)
    ...

@@ -698,6 +703,7 @@ def data(self) -> Dict[str, Any]:
data_["fetch_values_predicate"] = self.fetch_values_predicate
data_["template_params"] = self.template_params
data_["is_sqllab_view"] = self.is_sqllab_view
data_["health_check_message"] = self.health_check_message
Copy link
Author

@graceguo-supercat graceguo-supercat Dec 15, 2020

Choose a reason for hiding this comment

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

instead of output whole "extra", i saw certified data only offer flattened information. I think it's easier for frontend usage.

Copy link
Member

@ktmud ktmud left a comment

Choose a reason for hiding this comment

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

Thanks for the iterations! One blocking comment (you should set health_check even when check returns None), otherwise LGTM.

I'm also curious to hear how others think about this feature, though.

@junlincc @mistercrunch do you think this align well with overall Superset roadmap?

@@ -650,6 +650,10 @@ def explore( # pylint: disable=too-many-locals,too-many-return-statements
f"datasource_id={datasource_id}&"
)

# if feature enabled, run some health check rules for sqla datasource
if hasattr(datasource, "health_check") and conf["DATASET_HEALTH_CHECK"]:
Copy link
Member

@ktmud ktmud Dec 15, 2020

Choose a reason for hiding this comment

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

I think you can skip the conf check here. It will be immediately checked by datasource.health_check() anyway.

extra["health_check"] = {
"version": check.version,
"message": message,
}
Copy link
Member

Choose a reason for hiding this comment

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

You might want to set the health_check field even if check message returns None or empty. Because otherwise this will run for every "good" datasources that didn't have any health check warnings.

Copy link
Member

@ktmud ktmud Dec 15, 2020

Choose a reason for hiding this comment

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

I would also recommend making the message more structured for scalability (i.e. let DATASET_HEALTH_CHECK return a dict instead of a string). Because in the future, you may need to localize the warning message, then it would be better to store an ERROR_CODE + some metadata instead of just a message string. This also opens the door for more actionable warning message where users may click on the warning icon and open a popup for more details.

Maybe we can even define it as a new SupersetError type and use @etr2460 's error message registry to render it.

But of course, this could also be reserved for future refactor/extension. (We can always update the health check version when that happens!)

Copy link
Author

@graceguo-supercat graceguo-supercat Dec 15, 2020

Choose a reason for hiding this comment

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

It's not as strong as SupesetError. Error and Warning should be treated differently.

Let's keep this PR as simple as it is, and we can reiterate when it is really needed. The impact of this feature is still unknown to me, because it depends a lot on the rule implementation. Let's run the core function with some real examples before putting too much efforts on extras.

Copy link
Member

Choose a reason for hiding this comment

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

Yes, agreed. We don't need to go that route if it's too much work. Just laying out my thoughts here in case we want to double down on this feature. Even if the message depends on specific rule implementation, we can enforce a basic structure so that it's possible the override the message renders.

Error and Warning should be treated differently.

This I must humbly disagree. Both are actually a subclass of Alert in most UI libraries, and we see in a lot of places the concept of "error levels" (error, warning, info). Most of the time, you'd see warnings and errors being rendered by the same component, just different colors or icons. And in case we want to render them differently, the shared infrastructure of custom renderer registry comes handy.

Copy link
Author

@graceguo-supercat graceguo-supercat Dec 16, 2020

Choose a reason for hiding this comment

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

thanks. i will keep this work in mind. If we see this feature is useful, I will polish it with localization and other re-usable UI component.

it('should render health check message', () => {
const wrapper = setup();
const icons = wrapper.find(Icon);
expect(icons.first().prop('name')).toBe('alert-solid');
Copy link
Member

Choose a reason for hiding this comment

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

should we also make sure the warning message is shown here?

return (
<Styles className="DatasourceControl">
<ControlHeader {...this.props} />
<div>
<div style={{ display: 'flex' }}>
Copy link
Member

Choose a reason for hiding this comment

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

instead of an inline style, should we use a styled.div to apply this style?

Copy link
Author

Choose a reason for hiding this comment

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

fixed.

Comment on lines +996 to +998
# It's possible to add a dataset health check logic which is specific to your system.
# It will get executed each time when user open a chart's explore view.
DATASET_HEALTH_CHECK = None
Copy link
Member

Choose a reason for hiding this comment

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

can you add the signature about the health check to the comment here?

Or maybe even better, include a default function that always passes. It's actually a bit unclear to me how this should be used (should I return True if the dataset passes? None?)

Copy link
Author

@graceguo-supercat graceguo-supercat Dec 16, 2020

Choose a reason for hiding this comment

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

See Jesse's comment above, if the dataset passes, it will still add a health_check section into db record (so that not running check again).

@@ -650,6 +650,10 @@ def explore( # pylint: disable=too-many-locals,too-many-return-statements
f"datasource_id={datasource_id}&"
)

# if feature enabled, run some health check rules for sqla datasource
if hasattr(datasource, "health_check"):
datasource.health_check()
Copy link
Member

Choose a reason for hiding this comment

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

@graceguo-supercat why in this instance do we not require to force a check, i.e., datasource.health_check(force=True)?

Copy link
Author

@graceguo-supercat graceguo-supercat Apr 6, 2021

Choose a reason for hiding this comment

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

We should run check again when user updates dataset, or when health check rule's version is changed. Otherwise the healthy status will not be changed.
This function is triggered at the time explore view is opened from browser, so it doesn't need to force run the check.

@john-bodley john-bodley mentioned this pull request Apr 7, 2021
8 tasks
@mistercrunch mistercrunch added 🏷️ bot A label used by `supersetbot` to keep track of which PR where auto-tagged with release labels 🚢 1.0.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/M 🚢 1.0.0
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants