From ccee28f11774ae6c66f1c68b3e52145310069719 Mon Sep 17 00:00:00 2001 From: Craig Date: Mon, 14 Mar 2022 13:21:37 -0700 Subject: [PATCH 1/8] Adding manifest prefix config --- superset/common/query_object.py | 2 +- superset/config.py | 6 +- superset/connectors/base/models.py | 2 +- superset/connectors/druid/models.py | 2 +- superset/connectors/druid/views.py | 2 +- superset/connectors/sqla/models.py | 8 ++- superset/connectors/sqla/views.py | 2 +- superset/databases/api.py | 2 +- superset/extensions.py | 41 +++++++++----- superset/initialization/__init__.py | 2 +- superset/result_set.py | 2 +- superset/{typing.py => superset_typing.py} | 0 superset/utils/core.py | 2 +- superset/views/alerts.py | 2 +- superset/views/annotations.py | 2 +- superset/views/api.py | 2 +- superset/views/base.py | 2 +- superset/views/base_api.py | 2 +- superset/views/chart/views.py | 2 +- superset/views/core.py | 2 +- superset/views/css_templates.py | 2 +- superset/views/dashboard/views.py | 2 +- superset/views/database/views.py | 2 +- superset/views/datasource/views.py | 2 +- superset/views/health.py | 2 +- superset/views/key_value.py | 2 +- superset/views/redirects.py | 2 +- superset/views/schedules.py | 2 +- superset/views/sql_lab.py | 2 +- superset/views/tags.py | 2 +- superset/views/utils.py | 2 +- superset/viz.py | 8 ++- tests/unit_tests/extension_tests.py | 55 +++++++++++++++++++ .../fixtures/static/assets/manifest.json | 20 +++++++ 34 files changed, 147 insertions(+), 45 deletions(-) rename superset/{typing.py => superset_typing.py} (100%) create mode 100644 tests/unit_tests/extension_tests.py create mode 100644 tests/unit_tests/fixtures/static/assets/manifest.json diff --git a/superset/common/query_object.py b/superset/common/query_object.py index 2a40155d1ca4f..fd988a36fac05 100644 --- a/superset/common/query_object.py +++ b/superset/common/query_object.py @@ -31,7 +31,7 @@ QueryObjectValidationError, ) from superset.sql_parse import validate_filter_clause -from superset.typing import Column, Metric, OrderBy +from superset.superset_typing import Column, Metric, OrderBy from superset.utils import pandas_postprocessing from superset.utils.core import ( DTTM_ALIAS, diff --git a/superset/config.py b/superset/config.py index 6579719ea81cc..a7c03945fd10c 100644 --- a/superset/config.py +++ b/superset/config.py @@ -45,7 +45,7 @@ from superset.constants import CHANGE_ME_SECRET_KEY from superset.jinja_context import BaseTemplateProcessor from superset.stats_logger import DummyStatsLogger -from superset.typing import CacheConfig +from superset.superset_typing import CacheConfig from superset.utils.core import is_test, parse_boolean_string from superset.utils.encrypt import SQLAlchemyUtilsAdapter from superset.utils.log import DBEventLogger @@ -1249,6 +1249,10 @@ def SQL_QUERY_MUTATOR( # pylint: disable=invalid-name,unused-argument # SQLALCHEMY_DATABASE_URI by default if set to `None` SQLALCHEMY_EXAMPLES_URI = None +# Optional prefix to be added to all static asset paths when rendering the UI. +# This is useful for hosting assets in an external CDN, for example +STATIC_ASSETS_PREFIX = "" + # Some sqlalchemy connection strings can open Superset to security risks. # Typically these should not be allowed. PREVENT_UNSAFE_DB_CONNECTIONS = True diff --git a/superset/connectors/base/models.py b/superset/connectors/base/models.py index 967235f328c2e..5cf2a8719bf95 100644 --- a/superset/connectors/base/models.py +++ b/superset/connectors/base/models.py @@ -29,7 +29,7 @@ from superset.datasets.commands.exceptions import DatasetNotFoundError from superset.models.helpers import AuditMixinNullable, ImportExportMixin, QueryResult from superset.models.slice import Slice -from superset.typing import FilterValue, FilterValues, QueryObjectDict +from superset.superset_typing import FilterValue, FilterValues, QueryObjectDict from superset.utils import core as utils from superset.utils.core import GenericDataType diff --git a/superset/connectors/druid/models.py b/superset/connectors/druid/models.py index 32edb695279c0..3a17ec5319374 100644 --- a/superset/connectors/druid/models.py +++ b/superset/connectors/druid/models.py @@ -58,7 +58,7 @@ from superset.extensions import encrypted_field_factory from superset.models.core import Database from superset.models.helpers import AuditMixinNullable, ImportExportMixin, QueryResult -from superset.typing import ( +from superset.superset_typing import ( AdhocMetric, AdhocMetricColumn, FilterValues, diff --git a/superset/connectors/druid/views.py b/superset/connectors/druid/views.py index 03a3a42ec08cc..cd7e5d279ba25 100644 --- a/superset/connectors/druid/views.py +++ b/superset/connectors/druid/views.py @@ -34,7 +34,7 @@ from superset.connectors.connector_registry import ConnectorRegistry from superset.connectors.druid import models from superset.constants import RouteMethod -from superset.typing import FlaskResponse +from superset.superset_typing import FlaskResponse from superset.utils import core as utils from superset.views.base import ( BaseSupersetView, diff --git a/superset/connectors/sqla/models.py b/superset/connectors/sqla/models.py index 9cc2f8a78136b..2f466bc681dc2 100644 --- a/superset/connectors/sqla/models.py +++ b/superset/connectors/sqla/models.py @@ -96,8 +96,14 @@ QueryResult, ) from superset.sql_parse import ParsedQuery +from superset.superset_typing import ( + AdhocColumn, + AdhocMetric, + Metric, + OrderBy, + QueryObjectDict, +) from superset.tables.models import Table as NewTable -from superset.typing import AdhocColumn, AdhocMetric, Metric, OrderBy, QueryObjectDict from superset.utils import core as utils from superset.utils.core import ( GenericDataType, diff --git a/superset/connectors/sqla/views.py b/superset/connectors/sqla/views.py index fef8a2d8a4356..a16ffa49f62ba 100644 --- a/superset/connectors/sqla/views.py +++ b/superset/connectors/sqla/views.py @@ -36,7 +36,7 @@ from superset.connectors.base.views import DatasourceModelView from superset.connectors.sqla import models from superset.constants import MODEL_VIEW_RW_METHOD_PERMISSION_MAP, RouteMethod -from superset.typing import FlaskResponse +from superset.superset_typing import FlaskResponse from superset.utils import core as utils from superset.views.base import ( check_ownership, diff --git a/superset/databases/api.py b/superset/databases/api.py index 1b8b408c1ca91..eea5c9979fa4d 100644 --- a/superset/databases/api.py +++ b/superset/databases/api.py @@ -70,7 +70,7 @@ from superset.errors import ErrorLevel, SupersetError, SupersetErrorType from superset.extensions import security_manager from superset.models.core import Database -from superset.typing import FlaskResponse +from superset.superset_typing import FlaskResponse from superset.utils.core import error_msg_from_exception from superset.views.base_api import ( BaseSupersetModelRestApi, diff --git a/superset/extensions.py b/superset/extensions.py index 33dc1706a6b78..da03bb856ed7e 100644 --- a/superset/extensions.py +++ b/superset/extensions.py @@ -63,22 +63,33 @@ def init_app(self, app: Flask) -> None: self.app = app # Preload the cache self.parse_manifest_json() + self.register_processor(app) - @app.context_processor - def get_manifest() -> Dict[str, Callable[[str], List[str]]]: - loaded_chunks = set() - - def get_files(bundle: str, asset_type: str = "js") -> List[str]: - files = self.get_manifest_files(bundle, asset_type) - filtered_files = [f for f in files if f not in loaded_chunks] - for f in filtered_files: - loaded_chunks.add(f) - return filtered_files - - return dict( - js_manifest=lambda bundle: get_files(bundle, "js"), - css_manifest=lambda bundle: get_files(bundle, "css"), - ) + def register_processor(self, app: Flask) -> None: + app.template_context_processors[None].append(self.get_manifest) + + def get_manifest(self) -> Dict[str, Callable[[str], List[str]]]: + loaded_chunks = set() + + def get_files(bundle: str, asset_type: str = "js") -> List[str]: + files = self.get_manifest_files(bundle, asset_type) + filtered_files = [ + self.apply_resource_prefix(f) for f in files if f not in loaded_chunks + ] + for f in filtered_files: + loaded_chunks.add(f) + return filtered_files + + return dict( + js_manifest=lambda bundle: get_files(bundle, "js"), + css_manifest=lambda bundle: get_files(bundle, "css"), + ) + + def apply_resource_prefix(self, file: str) -> str: + if self.app: + return f"{self.app.config['STATIC_ASSETS_PREFIX']}{file}" + + return file def parse_manifest_json(self) -> None: try: diff --git a/superset/initialization/__init__.py b/superset/initialization/__init__.py index 59e204d6d65e8..94d905b2d0bf6 100644 --- a/superset/initialization/__init__.py +++ b/superset/initialization/__init__.py @@ -49,7 +49,7 @@ talisman, ) from superset.security import SupersetSecurityManager -from superset.typing import FlaskResponse +from superset.superset_typing import FlaskResponse from superset.utils.core import pessimistic_connection_handling from superset.utils.log import DBEventLogger, get_event_logger_from_cfg_value diff --git a/superset/result_set.py b/superset/result_set.py index b95b5e680d7db..19035b6d23788 100644 --- a/superset/result_set.py +++ b/superset/result_set.py @@ -26,7 +26,7 @@ import pyarrow as pa from superset.db_engine_specs import BaseEngineSpec -from superset.typing import DbapiDescription, DbapiResult +from superset.superset_typing import DbapiDescription, DbapiResult from superset.utils import core as utils logger = logging.getLogger(__name__) diff --git a/superset/typing.py b/superset/superset_typing.py similarity index 100% rename from superset/typing.py rename to superset/superset_typing.py diff --git a/superset/utils/core.py b/superset/utils/core.py index 2fdbc278adb70..36d59333d2ed3 100644 --- a/superset/utils/core.py +++ b/superset/utils/core.py @@ -98,7 +98,7 @@ SupersetException, SupersetTimeoutException, ) -from superset.typing import ( +from superset.superset_typing import ( AdhocColumn, AdhocMetric, AdhocMetricColumn, diff --git a/superset/views/alerts.py b/superset/views/alerts.py index e96f701c3d1b7..416966fbe7c35 100644 --- a/superset/views/alerts.py +++ b/superset/views/alerts.py @@ -30,8 +30,8 @@ from superset import is_feature_enabled from superset.constants import RouteMethod from superset.models.alerts import Alert, AlertLog, SQLObservation +from superset.superset_typing import FlaskResponse from superset.tasks.alerts.validator import check_validator -from superset.typing import FlaskResponse from superset.utils import core as utils from superset.utils.core import get_email_address_str, markdown diff --git a/superset/views/annotations.py b/superset/views/annotations.py index 4fa83c0ca4be4..dc1df5642af35 100644 --- a/superset/views/annotations.py +++ b/superset/views/annotations.py @@ -26,7 +26,7 @@ from superset import is_feature_enabled from superset.constants import MODEL_VIEW_RW_METHOD_PERMISSION_MAP, RouteMethod from superset.models.annotations import Annotation, AnnotationLayer -from superset.typing import FlaskResponse +from superset.superset_typing import FlaskResponse from superset.views.base import SupersetModelView diff --git a/superset/views/api.py b/superset/views/api.py index d4d94ce72346c..bde25236460da 100644 --- a/superset/views/api.py +++ b/superset/views/api.py @@ -31,7 +31,7 @@ ) from superset.legacy import update_time_range from superset.models.slice import Slice -from superset.typing import FlaskResponse +from superset.superset_typing import FlaskResponse from superset.utils import core as utils from superset.utils.date_parser import get_since_until from superset.views.base import api, BaseSupersetView, handle_api_exception diff --git a/superset/views/base.py b/superset/views/base.py index 1249bc43cc4fb..3024c4490d167 100644 --- a/superset/views/base.py +++ b/superset/views/base.py @@ -73,8 +73,8 @@ ) from superset.models.helpers import ImportExportMixin from superset.models.reports import ReportRecipientType +from superset.superset_typing import FlaskResponse from superset.translations.utils import get_language_pack -from superset.typing import FlaskResponse from superset.utils import core as utils from .utils import bootstrap_user_data diff --git a/superset/views/base_api.py b/superset/views/base_api.py index 87e99e7c74a7b..260e5731788bc 100644 --- a/superset/views/base_api.py +++ b/superset/views/base_api.py @@ -37,7 +37,7 @@ from superset.schemas import error_payload_content from superset.sql_lab import Query as SqllabQuery from superset.stats_logger import BaseStatsLogger -from superset.typing import FlaskResponse +from superset.superset_typing import FlaskResponse from superset.utils.core import time_function logger = logging.getLogger(__name__) diff --git a/superset/views/chart/views.py b/superset/views/chart/views.py index 37ef9a043e881..9ecc69f7b9e8e 100644 --- a/superset/views/chart/views.py +++ b/superset/views/chart/views.py @@ -24,7 +24,7 @@ from superset import is_feature_enabled from superset.constants import MODEL_VIEW_RW_METHOD_PERMISSION_MAP, RouteMethod from superset.models.slice import Slice -from superset.typing import FlaskResponse +from superset.superset_typing import FlaskResponse from superset.utils import core as utils from superset.views.base import ( check_ownership, diff --git a/superset/views/core.py b/superset/views/core.py index f69ee77bef2f0..2263320a403c5 100755 --- a/superset/views/core.py +++ b/superset/views/core.py @@ -118,8 +118,8 @@ from superset.sqllab.sqllab_execution_context import SqlJsonExecutionContext from superset.sqllab.utils import apply_display_max_row_configuration_if_require from superset.sqllab.validators import CanAccessQueryValidatorImpl +from superset.superset_typing import FlaskResponse from superset.tasks.async_queries import load_explore_json_into_cache -from superset.typing import FlaskResponse from superset.utils import core as utils, csv from superset.utils.async_query_manager import AsyncQueryTokenException from superset.utils.cache import etag_cache diff --git a/superset/views/css_templates.py b/superset/views/css_templates.py index d26acea5cfac7..2cfbd43ae962a 100644 --- a/superset/views/css_templates.py +++ b/superset/views/css_templates.py @@ -22,7 +22,7 @@ from superset import is_feature_enabled from superset.constants import MODEL_VIEW_RW_METHOD_PERMISSION_MAP, RouteMethod from superset.models import core as models -from superset.typing import FlaskResponse +from superset.superset_typing import FlaskResponse from superset.views.base import DeleteMixin, SupersetModelView diff --git a/superset/views/dashboard/views.py b/superset/views/dashboard/views.py index 99782def38a68..49ba61d08e0d2 100644 --- a/superset/views/dashboard/views.py +++ b/superset/views/dashboard/views.py @@ -29,7 +29,7 @@ from superset import db, event_logger, is_feature_enabled, security_manager from superset.constants import MODEL_VIEW_RW_METHOD_PERMISSION_MAP, RouteMethod from superset.models.dashboard import Dashboard as DashboardModel -from superset.typing import FlaskResponse +from superset.superset_typing import FlaskResponse from superset.utils import core as utils from superset.views.base import ( BaseSupersetView, diff --git a/superset/views/database/views.py b/superset/views/database/views.py index 115d168ed636a..aea4e04383570 100644 --- a/superset/views/database/views.py +++ b/superset/views/database/views.py @@ -37,7 +37,7 @@ from superset.exceptions import CertificateException from superset.extensions import event_logger from superset.sql_parse import Table -from superset.typing import FlaskResponse +from superset.superset_typing import FlaskResponse from superset.utils import core as utils from superset.views.base import DeleteMixin, SupersetModelView, YamlExportMixin diff --git a/superset/views/datasource/views.py b/superset/views/datasource/views.py index e2cb204082dd6..7e1ffa0468e90 100644 --- a/superset/views/datasource/views.py +++ b/superset/views/datasource/views.py @@ -38,7 +38,7 @@ from superset.exceptions import SupersetException, SupersetSecurityException from superset.extensions import security_manager from superset.models.core import Database -from superset.typing import FlaskResponse +from superset.superset_typing import FlaskResponse from superset.views.base import ( api, BaseSupersetView, diff --git a/superset/views/health.py b/superset/views/health.py index 876e7a5e130be..cf85b8927899d 100644 --- a/superset/views/health.py +++ b/superset/views/health.py @@ -15,7 +15,7 @@ # specific language governing permissions and limitations # under the License. from superset import app, talisman -from superset.typing import FlaskResponse +from superset.superset_typing import FlaskResponse @talisman(force_https=False) diff --git a/superset/views/key_value.py b/superset/views/key_value.py index 8f8aa99787a21..da39f094b812f 100644 --- a/superset/views/key_value.py +++ b/superset/views/key_value.py @@ -23,7 +23,7 @@ from superset import db, event_logger, is_feature_enabled from superset.models import core as models -from superset.typing import FlaskResponse +from superset.superset_typing import FlaskResponse from superset.utils import core as utils from superset.views.base import BaseSupersetView, json_error_response diff --git a/superset/views/redirects.py b/superset/views/redirects.py index da9613f3a0bb8..6b1510d8f5778 100644 --- a/superset/views/redirects.py +++ b/superset/views/redirects.py @@ -24,7 +24,7 @@ from superset import db, event_logger from superset.models import core as models -from superset.typing import FlaskResponse +from superset.superset_typing import FlaskResponse from superset.views.base import BaseSupersetView logger = logging.getLogger(__name__) diff --git a/superset/views/schedules.py b/superset/views/schedules.py index d1c59f413c839..39d4af9b8b259 100644 --- a/superset/views/schedules.py +++ b/superset/views/schedules.py @@ -42,8 +42,8 @@ SliceEmailSchedule, ) from superset.models.slice import Slice +from superset.superset_typing import FlaskResponse from superset.tasks.schedules import schedule_email_report -from superset.typing import FlaskResponse from superset.utils.core import get_email_address_list, json_iso_dttm_ser from superset.views.core import json_success diff --git a/superset/views/sql_lab.py b/superset/views/sql_lab.py index 6a5ce26d38ad9..5ec525b9cac73 100644 --- a/superset/views/sql_lab.py +++ b/superset/views/sql_lab.py @@ -24,7 +24,7 @@ from superset import db, is_feature_enabled from superset.constants import MODEL_VIEW_RW_METHOD_PERMISSION_MAP, RouteMethod from superset.models.sql_lab import Query, SavedQuery, TableSchema, TabState -from superset.typing import FlaskResponse +from superset.superset_typing import FlaskResponse from superset.utils import core as utils from .base import BaseSupersetView, DeleteMixin, json_success, SupersetModelView diff --git a/superset/views/tags.py b/superset/views/tags.py index c6fac2ff77145..8ab2798f5d84c 100644 --- a/superset/views/tags.py +++ b/superset/views/tags.py @@ -33,7 +33,7 @@ from superset.models.slice import Slice from superset.models.sql_lab import SavedQuery from superset.models.tags import ObjectTypes, Tag, TaggedObject, TagTypes -from superset.typing import FlaskResponse +from superset.superset_typing import FlaskResponse from .base import BaseSupersetView, json_success diff --git a/superset/views/utils.py b/superset/views/utils.py index 17ec6ea1088c9..62639174f647e 100644 --- a/superset/views/utils.py +++ b/superset/views/utils.py @@ -46,7 +46,7 @@ from superset.models.dashboard import Dashboard from superset.models.slice import Slice from superset.models.sql_lab import Query -from superset.typing import FormData +from superset.superset_typing import FormData from superset.utils.decorators import stats_timing from superset.viz import BaseViz diff --git a/superset/viz.py b/superset/viz.py index 9a1086442be62..7c0f8e134875b 100644 --- a/superset/viz.py +++ b/superset/viz.py @@ -70,7 +70,13 @@ from superset.extensions import cache_manager, security_manager from superset.models.helpers import QueryResult from superset.sql_parse import validate_filter_clause -from superset.typing import Column, Metric, QueryObjectDict, VizData, VizPayload +from superset.superset_typing import ( + Column, + Metric, + QueryObjectDict, + VizData, + VizPayload, +) from superset.utils import core as utils, csv from superset.utils.cache import set_and_log_cache from superset.utils.core import ( diff --git a/tests/unit_tests/extension_tests.py b/tests/unit_tests/extension_tests.py new file mode 100644 index 0000000000000..541fe52c2f994 --- /dev/null +++ b/tests/unit_tests/extension_tests.py @@ -0,0 +1,55 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +from os.path import dirname +from unittest.mock import Mock + +from extensions import UIManifestProcessor + +APP_DIR = f"{dirname(__file__)}/fixtures" + + +def test_get_manifest_with_prefix(): + app = Mock( + config={"STATIC_ASSETS_PREFIX": "https://cool.url/here"}, + template_context_processors={None: []}, + ) + manifest_processor = UIManifestProcessor(APP_DIR) + manifest_processor.init_app(app) + manifest = manifest_processor.get_manifest() + assert manifest["js_manifest"]("main") == [ + "https://cool.url/here/static/dist/main-js.js" + ] + assert manifest["css_manifest"]("main") == [ + "https://cool.url/here/static/dist/main-css.css" + ] + assert manifest["js_manifest"]("styles") == [ + "https://cool.url/here/static/dist/styles-js.js" + ] + assert manifest["css_manifest"]("styles") == [] + + +def test_get_manifest_no_prefix(): + app = Mock( + config={"STATIC_ASSETS_PREFIX": ""}, template_context_processors={None: []} + ) + manifest_processor = UIManifestProcessor(APP_DIR) + manifest_processor.init_app(app) + manifest = manifest_processor.get_manifest() + assert manifest["js_manifest"]("main") == ["/static/dist/main-js.js"] + assert manifest["css_manifest"]("main") == ["/static/dist/main-css.css"] + assert manifest["js_manifest"]("styles") == ["/static/dist/styles-js.js"] + assert manifest["css_manifest"]("styles") == [] diff --git a/tests/unit_tests/fixtures/static/assets/manifest.json b/tests/unit_tests/fixtures/static/assets/manifest.json new file mode 100644 index 0000000000000..7482a04eac74e --- /dev/null +++ b/tests/unit_tests/fixtures/static/assets/manifest.json @@ -0,0 +1,20 @@ +{ + "entrypoints": { + "styles": { + "js": [ + "/static/dist/styles-js.js" + ] + }, + "main": { + "css": [ + "/static/dist/main-css.css" + ], + "js": [ + "/static/dist/main-js.js" + ] + } + }, + "main.css": "/static/dist/main.b51d3f6225194da423d6.entry.css", + "main.js": "/static/dist/main.b51d3f6225194da423d6.entry.js", + "styles.js": "/static/dist/styles.35840b4bbf794f902b7c.entry.js" +} From ba9fdf305082a5520b5d66dbad01f6008d6ad86e Mon Sep 17 00:00:00 2001 From: Craig Date: Mon, 14 Mar 2022 13:39:40 -0700 Subject: [PATCH 2/8] Fixing broken tests --- tests/integration_tests/charts/data/api_tests.py | 2 +- tests/unit_tests/dataframe_test.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/integration_tests/charts/data/api_tests.py b/tests/integration_tests/charts/data/api_tests.py index 4f63ad51b65d7..bc8ec74feb0d6 100644 --- a/tests/integration_tests/charts/data/api_tests.py +++ b/tests/integration_tests/charts/data/api_tests.py @@ -47,7 +47,7 @@ from superset.extensions import async_query_manager, db from superset.models.annotations import AnnotationLayer from superset.models.slice import Slice -from superset.typing import AdhocColumn +from superset.superset_typing import AdhocColumn from superset.utils.core import ( AnnotationType, get_example_default_schema, diff --git a/tests/unit_tests/dataframe_test.py b/tests/unit_tests/dataframe_test.py index 3e986a5e43a7f..79625cffe63de 100644 --- a/tests/unit_tests/dataframe_test.py +++ b/tests/unit_tests/dataframe_test.py @@ -16,7 +16,7 @@ # under the License. # pylint: disable=unused-argument, import-outside-toplevel from superset.dataframe import df_to_records -from superset.typing import DbapiDescription +from superset.superset_typing import DbapiDescription def test_df_to_records(app_context: None) -> None: From 66bae2dd6e20f9b81aeb9ac1f281d9ace81f9713 Mon Sep 17 00:00:00 2001 From: Craig Date: Mon, 14 Mar 2022 14:40:05 -0700 Subject: [PATCH 3/8] Fixing import --- tests/unit_tests/extension_tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit_tests/extension_tests.py b/tests/unit_tests/extension_tests.py index 541fe52c2f994..74eaa4a3e51e7 100644 --- a/tests/unit_tests/extension_tests.py +++ b/tests/unit_tests/extension_tests.py @@ -17,7 +17,7 @@ from os.path import dirname from unittest.mock import Mock -from extensions import UIManifestProcessor +from superset.extensions import UIManifestProcessor APP_DIR = f"{dirname(__file__)}/fixtures" From 5706f2a5f43e70a634b546a1e991f26aa7bfb0e7 Mon Sep 17 00:00:00 2001 From: Craig Date: Mon, 14 Mar 2022 15:05:38 -0700 Subject: [PATCH 4/8] Adding prefix for remaining assets --- superset/extensions.py | 1 + superset/templates/superset/base.html | 2 +- superset/templates/superset/basic.html | 8 ++++---- superset/templates/superset/theme.html | 2 +- tests/unit_tests/extension_tests.py | 2 ++ 5 files changed, 9 insertions(+), 6 deletions(-) diff --git a/superset/extensions.py b/superset/extensions.py index da03bb856ed7e..b264755b13089 100644 --- a/superset/extensions.py +++ b/superset/extensions.py @@ -83,6 +83,7 @@ def get_files(bundle: str, asset_type: str = "js") -> List[str]: return dict( js_manifest=lambda bundle: get_files(bundle, "js"), css_manifest=lambda bundle: get_files(bundle, "css"), + assets_prefix=self.app.config["STATIC_ASSETS_PREFIX"] if self.app else "", ) def apply_resource_prefix(self, file: str) -> str: diff --git a/superset/templates/superset/base.html b/superset/templates/superset/base.html index a861c659e7034..e3c3d35dfe503 100644 --- a/superset/templates/superset/base.html +++ b/superset/templates/superset/base.html @@ -21,7 +21,7 @@ {% block head_css %} {{ super() }} - + {{ css_bundle("theme") }} {% endblock %} diff --git a/superset/templates/superset/basic.html b/superset/templates/superset/basic.html index 902fc8c328de4..fff57fdb9fa18 100644 --- a/superset/templates/superset/basic.html +++ b/superset/templates/superset/basic.html @@ -40,11 +40,11 @@ rel="{{favicon.rel if favicon.rel else "icon"}}" type="{{favicon.type if favicon.type else "image/png"}}" {% if favicon.sizes %}sizes={{favicon.sizes}}{% endif %} - href="{{favicon.href}}" + href="{{ assets_prefix }}{{favicon.href}}" > {% endfor %} - - + + {{ css_bundle("theme") }} @@ -73,7 +73,7 @@ {% block body %}
- +
{% endblock %} diff --git a/superset/templates/superset/theme.html b/superset/templates/superset/theme.html index feac56f895980..856796a4c4b21 100644 --- a/superset/templates/superset/theme.html +++ b/superset/templates/superset/theme.html @@ -1342,5 +1342,5 @@ {{ super() }} - + {% endblock %} diff --git a/tests/unit_tests/extension_tests.py b/tests/unit_tests/extension_tests.py index 74eaa4a3e51e7..1fe609937c620 100644 --- a/tests/unit_tests/extension_tests.py +++ b/tests/unit_tests/extension_tests.py @@ -40,6 +40,7 @@ def test_get_manifest_with_prefix(): "https://cool.url/here/static/dist/styles-js.js" ] assert manifest["css_manifest"]("styles") == [] + assert manifest["assets_prefix"] == "https://cool.url/here" def test_get_manifest_no_prefix(): @@ -53,3 +54,4 @@ def test_get_manifest_no_prefix(): assert manifest["css_manifest"]("main") == ["/static/dist/main-css.css"] assert manifest["js_manifest"]("styles") == ["/static/dist/styles-js.js"] assert manifest["css_manifest"]("styles") == [] + assert manifest["assets_prefix"] == "" From e16369074b64d08eedaa261195c2932ed7c351f7 Mon Sep 17 00:00:00 2001 From: Craig Date: Tue, 15 Mar 2022 17:00:57 -0700 Subject: [PATCH 5/8] Changing static prefix strategy --- superset-frontend/webpack.config.js | 4 ++-- superset/extensions.py | 10 +--------- tests/unit_tests/extension_tests.py | 12 +++--------- 3 files changed, 6 insertions(+), 20 deletions(-) diff --git a/superset-frontend/webpack.config.js b/superset-frontend/webpack.config.js index a4c1b86b482eb..6b3c4e30a8b13 100644 --- a/superset-frontend/webpack.config.js +++ b/superset-frontend/webpack.config.js @@ -95,10 +95,10 @@ const plugins = [ entryFiles[entry] = { css: chunks .filter(x => x.endsWith('.css')) - .map(x => path.join(output.publicPath, x)), + .map(x => `${output.publicPath}${x}`), js: chunks .filter(x => x.endsWith('.js')) - .map(x => path.join(output.publicPath, x)), + .map(x => `${output.publicPath}${x}`), }; }); diff --git a/superset/extensions.py b/superset/extensions.py index b264755b13089..742182b078d1b 100644 --- a/superset/extensions.py +++ b/superset/extensions.py @@ -73,9 +73,7 @@ def get_manifest(self) -> Dict[str, Callable[[str], List[str]]]: def get_files(bundle: str, asset_type: str = "js") -> List[str]: files = self.get_manifest_files(bundle, asset_type) - filtered_files = [ - self.apply_resource_prefix(f) for f in files if f not in loaded_chunks - ] + filtered_files = [f for f in files if f not in loaded_chunks] for f in filtered_files: loaded_chunks.add(f) return filtered_files @@ -86,12 +84,6 @@ def get_files(bundle: str, asset_type: str = "js") -> List[str]: assets_prefix=self.app.config["STATIC_ASSETS_PREFIX"] if self.app else "", ) - def apply_resource_prefix(self, file: str) -> str: - if self.app: - return f"{self.app.config['STATIC_ASSETS_PREFIX']}{file}" - - return file - def parse_manifest_json(self) -> None: try: with open(self.manifest_file, "r") as f: diff --git a/tests/unit_tests/extension_tests.py b/tests/unit_tests/extension_tests.py index 1fe609937c620..724b03f01a2ab 100644 --- a/tests/unit_tests/extension_tests.py +++ b/tests/unit_tests/extension_tests.py @@ -30,15 +30,9 @@ def test_get_manifest_with_prefix(): manifest_processor = UIManifestProcessor(APP_DIR) manifest_processor.init_app(app) manifest = manifest_processor.get_manifest() - assert manifest["js_manifest"]("main") == [ - "https://cool.url/here/static/dist/main-js.js" - ] - assert manifest["css_manifest"]("main") == [ - "https://cool.url/here/static/dist/main-css.css" - ] - assert manifest["js_manifest"]("styles") == [ - "https://cool.url/here/static/dist/styles-js.js" - ] + assert manifest["js_manifest"]("main") == ["/static/dist/main-js.js"] + assert manifest["css_manifest"]("main") == ["/static/dist/main-css.css"] + assert manifest["js_manifest"]("styles") == ["/static/dist/styles-js.js"] assert manifest["css_manifest"]("styles") == [] assert manifest["assets_prefix"] == "https://cool.url/here" From 50eaa491eb7f2befc089b40ab04311b9b9dcde39 Mon Sep 17 00:00:00 2001 From: Craig Date: Wed, 16 Mar 2022 09:13:12 -0700 Subject: [PATCH 6/8] Fixing DST test --- .../TimezoneSelector/TimezoneSelector.test.tsx | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/superset-frontend/src/components/TimezoneSelector/TimezoneSelector.test.tsx b/superset-frontend/src/components/TimezoneSelector/TimezoneSelector.test.tsx index 035cff842c9e2..47ed9dc0884c2 100644 --- a/superset-frontend/src/components/TimezoneSelector/TimezoneSelector.test.tsx +++ b/superset-frontend/src/components/TimezoneSelector/TimezoneSelector.test.tsx @@ -69,9 +69,9 @@ it('can select a timezone values and returns canonical value', async () => { }); expect(searchInput).toBeInTheDocument(); userEvent.click(searchInput); - const isDaylight = moment(moment.now()).isDST(); + const isDST = moment(moment.now()).isDST(); - const selectedTimezone = isDaylight + const selectedTimezone = isDST ? 'GMT -04:00 (Eastern Daylight Time)' : 'GMT -05:00 (Eastern Standard Time)'; @@ -82,12 +82,17 @@ it('can select a timezone values and returns canonical value', async () => { // others are ranked by offset expect(options[1]).toHaveTextContent('GMT -11:00 (Pacific/Pago_Pago)'); expect(options[2]).toHaveTextContent('GMT -10:00 (Hawaii Standard Time)'); - expect(options[3]).toHaveTextContent('GMT -10:00 (America/Adak)'); + + if (isDST) { + expect(options[3]).toHaveTextContent('GMT -09:30 (Pacific/Marquesas)'); + } else { + expect(options[3]).toHaveTextContent('GMT -10:00 (America/Adak)'); + } // search for mountain time await userEvent.type(searchInput, 'mou', { delay: 10 }); - const findTitle = isDaylight + const findTitle = isDST ? 'GMT -06:00 (Mountain Daylight Time)' : 'GMT -07:00 (Mountain Standard Time)'; const selectOption = await screen.findByTitle(findTitle); From 4b69adf506f9b55cf4ea7a061b97e907242a9571 Mon Sep 17 00:00:00 2001 From: Craig Date: Wed, 16 Mar 2022 09:28:39 -0700 Subject: [PATCH 7/8] Fixing up formatting --- .../TimezoneSelector/TimezoneSelector.test.tsx | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/superset-frontend/src/components/TimezoneSelector/TimezoneSelector.test.tsx b/superset-frontend/src/components/TimezoneSelector/TimezoneSelector.test.tsx index 47ed9dc0884c2..5d3463718a898 100644 --- a/superset-frontend/src/components/TimezoneSelector/TimezoneSelector.test.tsx +++ b/superset-frontend/src/components/TimezoneSelector/TimezoneSelector.test.tsx @@ -70,7 +70,9 @@ it('can select a timezone values and returns canonical value', async () => { expect(searchInput).toBeInTheDocument(); userEvent.click(searchInput); const isDST = moment(moment.now()).isDST(); - + const expectedOptionThree = isDST + ? 'GMT -09:30 (Pacific/Marquesas)' + : 'GMT -10:00 (America/Adak)'; const selectedTimezone = isDST ? 'GMT -04:00 (Eastern Daylight Time)' : 'GMT -05:00 (Eastern Standard Time)'; @@ -82,12 +84,7 @@ it('can select a timezone values and returns canonical value', async () => { // others are ranked by offset expect(options[1]).toHaveTextContent('GMT -11:00 (Pacific/Pago_Pago)'); expect(options[2]).toHaveTextContent('GMT -10:00 (Hawaii Standard Time)'); - - if (isDST) { - expect(options[3]).toHaveTextContent('GMT -09:30 (Pacific/Marquesas)'); - } else { - expect(options[3]).toHaveTextContent('GMT -10:00 (America/Adak)'); - } + expect(options[3]).toHaveTextContent(expectedOptionThree); // search for mountain time await userEvent.type(searchInput, 'mou', { delay: 10 }); From 296511b1e3aca41ba5308d2a1ed93ec823fcb93e Mon Sep 17 00:00:00 2001 From: Craig Date: Wed, 16 Mar 2022 11:19:13 -0700 Subject: [PATCH 8/8] Fixing up async_query_manager.py types --- superset/utils/async_query_manager.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/superset/utils/async_query_manager.py b/superset/utils/async_query_manager.py index a026fd6f3f3d7..b1c3cfe0e7322 100644 --- a/superset/utils/async_query_manager.py +++ b/superset/utils/async_query_manager.py @@ -20,8 +20,8 @@ from typing import Any, Dict, List, Optional, Tuple import jwt -import redis from flask import Flask, g, request, Request, Response, session +from redis import Redis logger = logging.getLogger(__name__) @@ -71,7 +71,7 @@ class AsyncQueryManager: def __init__(self) -> None: super().__init__() - self._redis: redis.Redis # type: ignore + self._redis: Optional[Any] = None self._stream_prefix: str = "" self._stream_limit: Optional[int] self._stream_limit_firehose: Optional[int] @@ -98,7 +98,7 @@ def init_app(self, app: Flask) -> None: "Please provide a JWT secret at least 32 bytes long" ) - self._redis = redis.Redis( + self._redis = Redis( **config["GLOBAL_ASYNC_QUERIES_REDIS_CONFIG"], decode_responses=True ) self._stream_prefix = config["GLOBAL_ASYNC_QUERIES_REDIS_STREAM_PREFIX"] @@ -176,7 +176,11 @@ def read_events( ) -> List[Optional[Dict[str, Any]]]: stream_name = f"{self._stream_prefix}{channel}" start_id = increment_id(last_id) if last_id else "-" - results = self._redis.xrange(stream_name, start_id, "+", self.MAX_EVENT_COUNT) + results = ( + self._redis.xrange(stream_name, start_id, "+", self.MAX_EVENT_COUNT) + if self._redis + else None + ) return [] if not results else list(map(parse_event, results)) def update_job( @@ -197,5 +201,8 @@ def update_job( logger.debug("********** logging event data to stream %s", scoped_stream_name) logger.debug(event_data) - self._redis.xadd(scoped_stream_name, event_data, "*", self._stream_limit) - self._redis.xadd(full_stream_name, event_data, "*", self._stream_limit_firehose) + if self._redis: + self._redis.xadd(scoped_stream_name, event_data, "*", self._stream_limit) + self._redis.xadd( + full_stream_name, event_data, "*", self._stream_limit_firehose + )