-
Notifications
You must be signed in to change notification settings - Fork 14.1k
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: enable ETag header for dashboard GET requests #10963
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,19 +16,27 @@ | |
# under the License. | ||
import logging | ||
from collections import defaultdict | ||
from datetime import date | ||
from datetime import date, datetime | ||
from typing import Any, Callable, DefaultDict, Dict, List, Optional, Set, Tuple, Union | ||
from urllib import parse | ||
|
||
import msgpack | ||
import pyarrow as pa | ||
import simplejson as json | ||
from flask import g, request | ||
from flask import abort, flash, g, redirect, request | ||
from flask_appbuilder.security.sqla import models as ab_models | ||
from flask_appbuilder.security.sqla.models import User | ||
from flask_babel import gettext as __ | ||
|
||
import superset.models.core as models | ||
from superset import app, dataframe, db, is_feature_enabled, result_set | ||
from superset import ( | ||
app, | ||
dataframe, | ||
db, | ||
is_feature_enabled, | ||
result_set, | ||
security_manager, | ||
) | ||
from superset.connectors.connector_registry import ConnectorRegistry | ||
from superset.errors import ErrorLevel, SupersetError, SupersetErrorType | ||
from superset.exceptions import SupersetException, SupersetSecurityException | ||
|
@@ -298,6 +306,36 @@ def get_time_range_endpoints( | |
CONTAINER_TYPES = ["COLUMN", "GRID", "TABS", "TAB", "ROW"] | ||
|
||
|
||
def get_dashboard(dashboard_id_or_slug: str,) -> Dashboard: | ||
session = db.session() | ||
qry = session.query(Dashboard) | ||
if dashboard_id_or_slug.isdigit(): | ||
qry = qry.filter_by(id=int(dashboard_id_or_slug)) | ||
else: | ||
qry = qry.filter_by(slug=dashboard_id_or_slug) | ||
dashboard = qry.one_or_none() | ||
|
||
if not dashboard: | ||
abort(404) | ||
|
||
return dashboard | ||
|
||
|
||
def get_dashboard_changedon_dt(_self: Any, dashboard_id_or_slug: str) -> datetime: | ||
""" | ||
Get latest changed datetime for a dashboard. The change could be dashboard | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. s/Get latest changed datetime for a dashboard. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yes. I rename it to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can we get more specific with _self type ? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't know what type to use here. do you have suggestion? (Sorry i am not an expert in Python) |
||
metadata change, or any of its slice data change. | ||
|
||
This function takes `self` since it must have the same signature as the | ||
the decorated method. | ||
""" | ||
dash = get_dashboard(dashboard_id_or_slug) | ||
dash_changed_on = dash.changed_on | ||
slices_changed_on = max([s.changed_on for s in dash.slices]) | ||
# drop microseconds in datetime to match with last_modified header | ||
return max(dash_changed_on, slices_changed_on).replace(microsecond=0) | ||
|
||
|
||
def get_dashboard_extra_filters( | ||
slice_id: int, dashboard_id: int | ||
) -> List[Dict[str, Any]]: | ||
|
@@ -490,6 +528,26 @@ def check_slice_perms(_self: Any, slice_id: int) -> None: | |
viz_obj.raise_for_access() | ||
|
||
|
||
def check_dashboard_perms(_self: Any, dashboard_id_or_slug: str) -> None: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. best practice is to have a unit test for every function, it would be great if you could add some There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, i agree. but this function is refactored out from dashboard function. it is tested in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. a good practice is to incrementally improve the state of the code, however it will be your call here |
||
""" | ||
Check if user can access a cached response from explore_json. | ||
|
||
This function takes `self` since it must have the same signature as the | ||
the decorated method. | ||
""" | ||
|
||
dash = get_dashboard(dashboard_id_or_slug) | ||
datasources = list(dash.datasources) | ||
if app.config["ENABLE_ACCESS_REQUEST"]: | ||
for datasource in datasources: | ||
if datasource and not security_manager.can_access_datasource(datasource): | ||
flash( | ||
__(security_manager.get_datasource_access_error_msg(datasource)), | ||
"danger", | ||
) | ||
redirect("superset/request_access/?" f"dashboard_id={dash.id}&") | ||
|
||
|
||
def _deserialize_results_payload( | ||
payload: Union[bytes, str], query: Query, use_msgpack: Optional[bool] = False | ||
) -> Dict[str, Any]: | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
add a comment here why content_changed_time is set to now()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We use this
content_changed_time
as cache's last_modified time.for dashboard
content_changed_time
is dashboard entity's latest updated time (like metadata, chart metadata changed time etc). this data is from a callback function.for explore_json, the cache is query results and there is no entity's latest modified time to use. so we use request time (now) as cache's last_modified time.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know generalizing too soon is not a good practice, but I wonder if we should pass a callable called
is_stale
here. It would simplify the decorator logic, and since it would be defined closer to the dashboard it might simplify the logic there as well.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
at first i thought is_stale is a good idea. but when start refactor it, i found last_modified time is needed by decorator function(to set header). So is_stale (only boolean value) is not enough.
So instead of using is_stale return true or false, I prefer to keep get_last_modified, and use last_modified time to invalid cache.