perf: Memoize the common_bootstrap_payload and include user param (#21018) (#21439)

Co-authored-by: Bogdan Kyryliuk <bogdankyryliuk@dropbox.com>
This commit is contained in:
Bogdan 2022-09-13 08:52:08 -07:00 committed by GitHub
parent eac6fdcd29
commit 44654e5abc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 28 additions and 19 deletions

View File

@ -17,7 +17,7 @@
import json import json
from typing import Callable from typing import Callable
from flask import abort, request from flask import abort, g, request
from flask_appbuilder import expose from flask_appbuilder import expose
from flask_login import AnonymousUserMixin, LoginManager from flask_login import AnonymousUserMixin, LoginManager
from flask_wtf.csrf import same_origin from flask_wtf.csrf import same_origin
@ -77,7 +77,7 @@ class EmbeddedView(BaseSupersetView):
) )
bootstrap_data = { bootstrap_data = {
"common": common_bootstrap_payload(), "common": common_bootstrap_payload(g.user),
"embedded": { "embedded": {
"dashboard_id": embedded.dashboard_id, "dashboard_id": embedded.dashboard_id,
}, },

View File

@ -39,6 +39,7 @@ from flask_appbuilder import BaseView, Model, ModelView
from flask_appbuilder.actions import action from flask_appbuilder.actions import action
from flask_appbuilder.forms import DynamicForm from flask_appbuilder.forms import DynamicForm
from flask_appbuilder.models.sqla.filters import BaseFilter from flask_appbuilder.models.sqla.filters import BaseFilter
from flask_appbuilder.security.sqla.models import User
from flask_appbuilder.widgets import ListWidget from flask_appbuilder.widgets import ListWidget
from flask_babel import get_locale, gettext as __, lazy_gettext as _ from flask_babel import get_locale, gettext as __, lazy_gettext as _
from flask_jwt_extended.exceptions import NoAuthorizationError from flask_jwt_extended.exceptions import NoAuthorizationError
@ -71,6 +72,7 @@ from superset.exceptions import (
SupersetException, SupersetException,
SupersetSecurityException, SupersetSecurityException,
) )
from superset.extensions import cache_manager
from superset.models.helpers import ImportExportMixin from superset.models.helpers import ImportExportMixin
from superset.reports.models import ReportRecipientType from superset.reports.models import ReportRecipientType
from superset.superset_typing import FlaskResponse from superset.superset_typing import FlaskResponse
@ -284,7 +286,7 @@ class BaseSupersetView(BaseView):
def render_app_template(self) -> FlaskResponse: def render_app_template(self) -> FlaskResponse:
payload = { payload = {
"user": bootstrap_user_data(g.user, include_perms=True), "user": bootstrap_user_data(g.user, include_perms=True),
"common": common_bootstrap_payload(), "common": common_bootstrap_payload(g.user),
} }
return self.render_template( return self.render_template(
"superset/spa.html", "superset/spa.html",
@ -295,7 +297,7 @@ class BaseSupersetView(BaseView):
) )
def menu_data() -> Dict[str, Any]: def menu_data(user: User) -> Dict[str, Any]:
menu = appbuilder.menu.get_data() menu = appbuilder.menu.get_data()
languages = {} languages = {}
@ -340,22 +342,27 @@ def menu_data() -> Dict[str, Any]:
"build_number": build_number, "build_number": build_number,
"languages": languages, "languages": languages,
"show_language_picker": len(languages.keys()) > 1, "show_language_picker": len(languages.keys()) > 1,
"user_is_anonymous": g.user.is_anonymous, "user_is_anonymous": user.is_anonymous,
"user_info_url": None "user_info_url": None
if appbuilder.app.config["MENU_HIDE_USER_INFO"] if appbuilder.app.config["MENU_HIDE_USER_INFO"]
else appbuilder.get_url_for_userinfo, else appbuilder.get_url_for_userinfo,
"user_logout_url": appbuilder.get_url_for_logout, "user_logout_url": appbuilder.get_url_for_logout,
"user_login_url": appbuilder.get_url_for_login, "user_login_url": appbuilder.get_url_for_login,
"user_profile_url": None "user_profile_url": None
if g.user.is_anonymous or appbuilder.app.config["MENU_HIDE_USER_INFO"] if user.is_anonymous or appbuilder.app.config["MENU_HIDE_USER_INFO"]
else f"/superset/profile/{g.user.username}", else f"/superset/profile/{user.username}",
"locale": session.get("locale", "en"), "locale": session.get("locale", "en"),
}, },
} }
def common_bootstrap_payload() -> Dict[str, Any]: @cache_manager.cache.memoize(timeout=60)
"""Common data always sent to the client""" 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) messages = get_flashed_messages(with_categories=True)
locale = str(get_locale()) 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_sequential_color_schemes": conf["EXTRA_SEQUENTIAL_COLOR_SCHEMES"],
"extra_categorical_color_schemes": conf["EXTRA_CATEGORICAL_COLOR_SCHEMES"], "extra_categorical_color_schemes": conf["EXTRA_CATEGORICAL_COLOR_SCHEMES"],
"theme_overrides": conf["THEME_OVERRIDES"], "theme_overrides": conf["THEME_OVERRIDES"],
"menu_data": menu_data(), "menu_data": menu_data(user),
} }
bootstrap_data.update(conf["COMMON_BOOTSTRAP_OVERRIDES_FUNC"](bootstrap_data)) bootstrap_data.update(conf["COMMON_BOOTSTRAP_OVERRIDES_FUNC"](bootstrap_data))
return 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 get_common_bootstrap_data() -> Dict[str, Any]:
def serialize_bootstrap_data() -> str: def serialize_bootstrap_data() -> str:
return json.dumps( return json.dumps(
{"common": common_bootstrap_payload()}, {"common": common_bootstrap_payload(g.user)},
default=utils.pessimistic_json_iso_dttm_ser, default=utils.pessimistic_json_iso_dttm_ser,
) )
@ -517,7 +524,7 @@ class SupersetModelView(ModelView):
def render_app_template(self) -> FlaskResponse: def render_app_template(self) -> FlaskResponse:
payload = { payload = {
"user": bootstrap_user_data(g.user, include_perms=True), "user": bootstrap_user_data(g.user, include_perms=True),
"common": common_bootstrap_payload(), "common": common_bootstrap_payload(g.user),
} }
return self.render_template( return self.render_template(
"superset/spa.html", "superset/spa.html",

View File

@ -928,7 +928,7 @@ class Superset(BaseSupersetView): # pylint: disable=too-many-public-methods
"force": force, "force": force,
"user": bootstrap_user_data(g.user, include_perms=True), "user": bootstrap_user_data(g.user, include_perms=True),
"forced_height": request.args.get("height"), "forced_height": request.args.get("height"),
"common": common_bootstrap_payload(), "common": common_bootstrap_payload(g.user),
} }
if slc: if slc:
title = slc.slice_name title = slc.slice_name
@ -1932,7 +1932,7 @@ class Superset(BaseSupersetView): # pylint: disable=too-many-public-methods
bootstrap_data = { bootstrap_data = {
"user": bootstrap_user_data(g.user, include_perms=True), "user": bootstrap_user_data(g.user, include_perms=True),
"common": common_bootstrap_payload(), "common": common_bootstrap_payload(g.user),
} }
return self.render_template( return self.render_template(
@ -2673,7 +2673,7 @@ class Superset(BaseSupersetView): # pylint: disable=too-many-public-methods
payload = { payload = {
"user": bootstrap_user_data(g.user, include_perms=True), "user": bootstrap_user_data(g.user, include_perms=True),
"common": common_bootstrap_payload(), "common": common_bootstrap_payload(g.user),
} }
return self.render_template( return self.render_template(
@ -2702,7 +2702,7 @@ class Superset(BaseSupersetView): # pylint: disable=too-many-public-methods
payload = { payload = {
"user": bootstrap_user_data(user, include_perms=True), "user": bootstrap_user_data(user, include_perms=True),
"common": common_bootstrap_payload(), "common": common_bootstrap_payload(g.user),
} }
return self.render_template( return self.render_template(
@ -2766,7 +2766,7 @@ class Superset(BaseSupersetView): # pylint: disable=too-many-public-methods
"""SQL Editor""" """SQL Editor"""
payload = { payload = {
"defaultDbId": config["SQLLAB_DEFAULT_DBID"], "defaultDbId": config["SQLLAB_DEFAULT_DBID"],
"common": common_bootstrap_payload(), "common": common_bootstrap_payload(g.user),
**self._get_sqllab_tabs(get_user_id()), **self._get_sqllab_tabs(get_user_id()),
} }

View File

@ -158,7 +158,7 @@ class Dashboard(BaseSupersetView):
) )
bootstrap_data = { bootstrap_data = {
"common": common_bootstrap_payload(), "common": common_bootstrap_payload(g.user),
"embedded": {"dashboard_id": dashboard_id_or_slug}, "embedded": {"dashboard_id": dashboard_id_or_slug},
} }

View File

@ -62,7 +62,7 @@ from superset.connectors.sqla.models import SqlaTable
from superset.db_engine_specs.base import BaseEngineSpec from superset.db_engine_specs.base import BaseEngineSpec
from superset.db_engine_specs.mssql import MssqlEngineSpec from superset.db_engine_specs.mssql import MssqlEngineSpec
from superset.exceptions import SupersetException 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 import core as models
from superset.models.annotations import Annotation, AnnotationLayer from superset.models.annotations import Annotation, AnnotationLayer
from superset.models.dashboard import Dashboard from superset.models.dashboard import Dashboard
@ -1434,6 +1434,8 @@ class TestCore(SupersetTestCase):
""" """
Functions in feature flags don't break bootstrap data serialization. Functions in feature flags don't break bootstrap data serialization.
""" """
# feature flags are cached
cache_manager.cache.clear()
self.login() self.login()
encoded = json.dumps( encoded = json.dumps(