From 44654e5abcc70d1fd934529dc82d7fe1d1bb20ef Mon Sep 17 00:00:00 2001 From: Bogdan Date: Tue, 13 Sep 2022 08:52:08 -0700 Subject: [PATCH] perf: Memoize the common_bootstrap_payload and include user param (#21018) (#21439) Co-authored-by: Bogdan Kyryliuk --- superset/embedded/view.py | 4 ++-- superset/views/base.py | 27 +++++++++++++++++---------- superset/views/core.py | 10 +++++----- superset/views/dashboard/views.py | 2 +- tests/integration_tests/core_tests.py | 4 +++- 5 files changed, 28 insertions(+), 19 deletions(-) diff --git a/superset/embedded/view.py b/superset/embedded/view.py index b64bcf6fc28f..c3c6c39ba25a 100644 --- a/superset/embedded/view.py +++ b/superset/embedded/view.py @@ -17,7 +17,7 @@ import json from typing import Callable -from flask import abort, request +from flask import abort, g, request from flask_appbuilder import expose from flask_login import AnonymousUserMixin, LoginManager from flask_wtf.csrf import same_origin @@ -77,7 +77,7 @@ def embedded( ) bootstrap_data = { - "common": common_bootstrap_payload(), + "common": common_bootstrap_payload(g.user), "embedded": { "dashboard_id": embedded.dashboard_id, }, diff --git a/superset/views/base.py b/superset/views/base.py index 330ca0b04ee7..b790ca709c26 100644 --- a/superset/views/base.py +++ b/superset/views/base.py @@ -39,6 +39,7 @@ from flask_appbuilder.actions import action from flask_appbuilder.forms import DynamicForm from flask_appbuilder.models.sqla.filters import BaseFilter +from flask_appbuilder.security.sqla.models import User from flask_appbuilder.widgets import ListWidget from flask_babel import get_locale, gettext as __, lazy_gettext as _ from flask_jwt_extended.exceptions import NoAuthorizationError @@ -71,6 +72,7 @@ SupersetException, SupersetSecurityException, ) +from superset.extensions import cache_manager from superset.models.helpers import ImportExportMixin from superset.reports.models import ReportRecipientType from superset.superset_typing import FlaskResponse @@ -284,7 +286,7 @@ def json_response(obj: Any, status: int = 200) -> FlaskResponse: def render_app_template(self) -> FlaskResponse: payload = { "user": bootstrap_user_data(g.user, include_perms=True), - "common": common_bootstrap_payload(), + "common": common_bootstrap_payload(g.user), } return self.render_template( "superset/spa.html", @@ -295,7 +297,7 @@ def render_app_template(self) -> FlaskResponse: ) -def menu_data() -> Dict[str, Any]: +def menu_data(user: User) -> Dict[str, Any]: menu = appbuilder.menu.get_data() languages = {} @@ -340,22 +342,27 @@ def menu_data() -> Dict[str, Any]: "build_number": build_number, "languages": languages, "show_language_picker": len(languages.keys()) > 1, - "user_is_anonymous": g.user.is_anonymous, + "user_is_anonymous": user.is_anonymous, "user_info_url": None if appbuilder.app.config["MENU_HIDE_USER_INFO"] else appbuilder.get_url_for_userinfo, "user_logout_url": appbuilder.get_url_for_logout, "user_login_url": appbuilder.get_url_for_login, "user_profile_url": None - if g.user.is_anonymous or appbuilder.app.config["MENU_HIDE_USER_INFO"] - else f"/superset/profile/{g.user.username}", + if user.is_anonymous or appbuilder.app.config["MENU_HIDE_USER_INFO"] + else f"/superset/profile/{user.username}", "locale": session.get("locale", "en"), }, } -def common_bootstrap_payload() -> Dict[str, Any]: - """Common data always sent to the client""" +@cache_manager.cache.memoize(timeout=60) +def common_bootstrap_payload(user: User) -> Dict[str, Any]: + """Common data always sent to the client + + The function is memoized as the return value only changes based + on configuration and feature flag values. + """ messages = get_flashed_messages(with_categories=True) locale = str(get_locale()) @@ -388,7 +395,7 @@ def common_bootstrap_payload() -> Dict[str, Any]: "extra_sequential_color_schemes": conf["EXTRA_SEQUENTIAL_COLOR_SCHEMES"], "extra_categorical_color_schemes": conf["EXTRA_CATEGORICAL_COLOR_SCHEMES"], "theme_overrides": conf["THEME_OVERRIDES"], - "menu_data": menu_data(), + "menu_data": menu_data(user), } bootstrap_data.update(conf["COMMON_BOOTSTRAP_OVERRIDES_FUNC"](bootstrap_data)) return bootstrap_data @@ -499,7 +506,7 @@ def show_unexpected_exception(ex: Exception) -> FlaskResponse: def get_common_bootstrap_data() -> Dict[str, Any]: def serialize_bootstrap_data() -> str: return json.dumps( - {"common": common_bootstrap_payload()}, + {"common": common_bootstrap_payload(g.user)}, default=utils.pessimistic_json_iso_dttm_ser, ) @@ -517,7 +524,7 @@ class SupersetModelView(ModelView): def render_app_template(self) -> FlaskResponse: payload = { "user": bootstrap_user_data(g.user, include_perms=True), - "common": common_bootstrap_payload(), + "common": common_bootstrap_payload(g.user), } return self.render_template( "superset/spa.html", diff --git a/superset/views/core.py b/superset/views/core.py index aba559c2ed9f..859b42ad97d7 100755 --- a/superset/views/core.py +++ b/superset/views/core.py @@ -928,7 +928,7 @@ def explore( "force": force, "user": bootstrap_user_data(g.user, include_perms=True), "forced_height": request.args.get("height"), - "common": common_bootstrap_payload(), + "common": common_bootstrap_payload(g.user), } if slc: title = slc.slice_name @@ -1932,7 +1932,7 @@ def dashboard( bootstrap_data = { "user": bootstrap_user_data(g.user, include_perms=True), - "common": common_bootstrap_payload(), + "common": common_bootstrap_payload(g.user), } return self.render_template( @@ -2673,7 +2673,7 @@ def welcome(self) -> FlaskResponse: payload = { "user": bootstrap_user_data(g.user, include_perms=True), - "common": common_bootstrap_payload(), + "common": common_bootstrap_payload(g.user), } return self.render_template( @@ -2702,7 +2702,7 @@ def profile(self, username: str) -> FlaskResponse: payload = { "user": bootstrap_user_data(user, include_perms=True), - "common": common_bootstrap_payload(), + "common": common_bootstrap_payload(g.user), } return self.render_template( @@ -2766,7 +2766,7 @@ def sqllab(self) -> FlaskResponse: """SQL Editor""" payload = { "defaultDbId": config["SQLLAB_DEFAULT_DBID"], - "common": common_bootstrap_payload(), + "common": common_bootstrap_payload(g.user), **self._get_sqllab_tabs(get_user_id()), } diff --git a/superset/views/dashboard/views.py b/superset/views/dashboard/views.py index 8d562fefbc13..32f0189d7046 100644 --- a/superset/views/dashboard/views.py +++ b/superset/views/dashboard/views.py @@ -158,7 +158,7 @@ def embedded( ) bootstrap_data = { - "common": common_bootstrap_payload(), + "common": common_bootstrap_payload(g.user), "embedded": {"dashboard_id": dashboard_id_or_slug}, } diff --git a/tests/integration_tests/core_tests.py b/tests/integration_tests/core_tests.py index 5f3122462419..75314c153330 100644 --- a/tests/integration_tests/core_tests.py +++ b/tests/integration_tests/core_tests.py @@ -62,7 +62,7 @@ from superset.db_engine_specs.base import BaseEngineSpec from superset.db_engine_specs.mssql import MssqlEngineSpec from superset.exceptions import SupersetException -from superset.extensions import async_query_manager +from superset.extensions import async_query_manager, cache_manager from superset.models import core as models from superset.models.annotations import Annotation, AnnotationLayer from superset.models.dashboard import Dashboard @@ -1434,6 +1434,8 @@ def test_feature_flag_serialization(self): """ Functions in feature flags don't break bootstrap data serialization. """ + # feature flags are cached + cache_manager.cache.clear() self.login() encoded = json.dumps(