mirror of
https://github.com/apache/superset.git
synced 2026-05-07 00:44:26 +00:00
Small fixes
This commit is contained in:
@@ -105,7 +105,13 @@ class CeleryConfig:
|
||||
|
||||
CELERY_CONFIG = CeleryConfig
|
||||
|
||||
FEATURE_FLAGS = {"ALERT_REPORTS": True, "DATASET_FOLDERS": True}
|
||||
FEATURE_FLAGS = {
|
||||
"ALERT_REPORTS": True,
|
||||
"DATASET_FOLDERS": True,
|
||||
"ENABLE_EXTENSIONS": True,
|
||||
"SEMANTIC_LAYERS": True,
|
||||
}
|
||||
EXTENSIONS_PATH = "/app/docker/extensions"
|
||||
ALERT_REPORTS_NOTIFICATION_DRY_RUN = True
|
||||
WEBDRIVER_BASEURL = f"http://superset_app{os.environ.get('SUPERSET_APP_ROOT', '/')}/" # When using docker compose baseurl should be http://superset_nginx{ENV{BASEPATH}}/ # noqa: E501
|
||||
# The base URL for the email report hyperlinks.
|
||||
|
||||
10439
superset-frontend/package-lock.json
generated
10439
superset-frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -65,6 +65,9 @@ class GetCombinedDatasourceListCommand(BaseCommand):
|
||||
source_type, name_filter, sql_filter, type_filter = self._parse_filters(filters)
|
||||
source_type = self._resolve_source_type(source_type, sql_filter, type_filter)
|
||||
|
||||
if source_type == "empty":
|
||||
return {"count": 0, "result": []}
|
||||
|
||||
ds_q = DatasourceDAO.build_dataset_query(name_filter, sql_filter)
|
||||
sv_q = DatasourceDAO.build_semantic_view_query(name_filter)
|
||||
|
||||
@@ -108,8 +111,18 @@ class GetCombinedDatasourceListCommand(BaseCommand):
|
||||
sql_filter: bool | None,
|
||||
type_filter: str | None,
|
||||
) -> str:
|
||||
"""Narrow source_type based on access flags, sql filter, and type filter."""
|
||||
"""Narrow source_type based on access flags, sql filter, and type filter.
|
||||
|
||||
Returns one of: "database", "semantic_layer", "all", or "empty".
|
||||
"empty" signals that the caller should short-circuit and return no results
|
||||
(used when the user explicitly requests semantic views but lacks access).
|
||||
"""
|
||||
if not self._can_read_semantic_views:
|
||||
# If the user explicitly asked for semantic views but cannot read them,
|
||||
# return "empty" so the caller yields zero results rather than silently
|
||||
# falling back to the full dataset list.
|
||||
if source_type == "semantic_layer" or type_filter == "semantic_view":
|
||||
return "empty"
|
||||
return "database"
|
||||
if not self._can_read_datasets:
|
||||
return "semantic_layer"
|
||||
|
||||
@@ -22,18 +22,20 @@ from __future__ import annotations
|
||||
from typing import Any
|
||||
|
||||
from sqlalchemy.exc import StatementError
|
||||
|
||||
from superset_core.semantic_layers.daos import (
|
||||
AbstractSemanticLayerDAO,
|
||||
AbstractSemanticViewDAO,
|
||||
)
|
||||
|
||||
from superset.daos.base import BaseDAO
|
||||
from superset.extensions import db
|
||||
from superset.semantic_layers.models import SemanticLayer, SemanticView
|
||||
from superset.utils import json
|
||||
|
||||
|
||||
class SemanticLayerDAO(AbstractSemanticLayerDAO):
|
||||
class SemanticLayerDAO(BaseDAO[SemanticLayer], AbstractSemanticLayerDAO):
|
||||
# SemanticLayer uses uuid as the primary key
|
||||
id_column_name = "uuid"
|
||||
"""
|
||||
Data Access Object for SemanticLayer model.
|
||||
"""
|
||||
@@ -112,7 +114,7 @@ class SemanticLayerDAO(AbstractSemanticLayerDAO):
|
||||
)
|
||||
|
||||
|
||||
class SemanticViewDAO(AbstractSemanticViewDAO):
|
||||
class SemanticViewDAO(BaseDAO[SemanticView], AbstractSemanticViewDAO):
|
||||
"""Data Access Object for SemanticView model."""
|
||||
|
||||
model_cls = SemanticView
|
||||
|
||||
@@ -56,7 +56,7 @@ from superset.commands.semantic_layer.update import (
|
||||
UpdateSemanticViewCommand,
|
||||
)
|
||||
from superset.constants import MODEL_API_RW_METHOD_PERMISSION_MAP
|
||||
from superset.daos.semantic_layer import SemanticLayerDAO, SemanticViewDAO
|
||||
from superset.daos.semantic_layer import SemanticLayerDAO
|
||||
from superset.datasets.schemas import get_delete_ids_schema
|
||||
from superset.models.core import Database
|
||||
from superset.semantic_layers.models import SemanticLayer, SemanticView
|
||||
@@ -175,6 +175,11 @@ class SemanticViewRestApi(BaseSupersetModelRestApi):
|
||||
class_permission_name = "SemanticView"
|
||||
method_permission_name = MODEL_API_RW_METHOD_PERMISSION_MAP
|
||||
include_route_methods = {"put", "post", "delete", "bulk_delete"}
|
||||
# SemanticViewRestApi exposes only write endpoints, but can_read must be
|
||||
# declared explicitly so that FAB registers the permission. It is used by
|
||||
# DatasourceRestApi.combined_list to gate access to semantic views in the
|
||||
# combined datasource list.
|
||||
base_permissions = ["can_read", "can_write"]
|
||||
|
||||
edit_model_schema = SemanticViewPutSchema()
|
||||
|
||||
@@ -635,7 +640,7 @@ class SemanticLayerRestApi(BaseSupersetApi):
|
||||
)
|
||||
|
||||
# Check which views already exist with the same runtime config
|
||||
existing = SemanticViewDAO.find_by_semantic_layer(str(layer.uuid))
|
||||
existing = SemanticLayerDAO.get_semantic_views(str(layer.uuid))
|
||||
existing_keys: set[tuple[str, str]] = set()
|
||||
for v in existing:
|
||||
config = v.configuration
|
||||
|
||||
@@ -291,11 +291,7 @@ def map_query_object(query_object: ValidatedQueryObject) -> list[SemanticQuery]:
|
||||
|
||||
metrics = [all_metrics[metric] for metric in (query_object.metrics or [])]
|
||||
|
||||
grain = (
|
||||
_convert_time_grain(query_object.extras["time_grain_sqla"])
|
||||
if "time_grain_sqla" in query_object.extras
|
||||
else None
|
||||
)
|
||||
grain = _convert_time_grain(query_object.extras.get("time_grain_sqla"))
|
||||
dimensions = [
|
||||
dimension
|
||||
for dimension in semantic_view.dimensions
|
||||
@@ -740,13 +736,17 @@ def _get_group_limit_filters(
|
||||
return filters if filters else None
|
||||
|
||||
|
||||
def _convert_time_grain(time_grain: str) -> Grain | None:
|
||||
def _convert_time_grain(time_grain: str | None) -> Grain | None:
|
||||
"""
|
||||
Convert a time grain string (ISO 8601 duration) to a Grain instance.
|
||||
|
||||
Returns None when ``time_grain`` is None or empty (no grain selected).
|
||||
"""
|
||||
if not time_grain:
|
||||
return None
|
||||
try:
|
||||
return Grains.get(time_grain)
|
||||
except (ValueError, isodate.ISO8601Error):
|
||||
except (TypeError, ValueError, isodate.ISO8601Error):
|
||||
return None
|
||||
|
||||
|
||||
|
||||
@@ -1796,8 +1796,7 @@ def test_get_views(
|
||||
mock_dao = mocker.patch("superset.semantic_layers.api.SemanticLayerDAO")
|
||||
mock_dao.find_by_uuid.return_value = mock_layer
|
||||
|
||||
mock_sv_dao = mocker.patch("superset.semantic_layers.api.SemanticViewDAO")
|
||||
mock_sv_dao.find_by_semantic_layer.return_value = []
|
||||
mock_dao.get_semantic_views.return_value = []
|
||||
|
||||
response = client.post(
|
||||
f"/api/v1/semantic_layer/{test_uuid}/views",
|
||||
@@ -1834,8 +1833,7 @@ def test_get_views_with_existing(
|
||||
existing_view.name = "Existing View"
|
||||
existing_view.configuration = '{"database": "mydb"}'
|
||||
|
||||
mock_sv_dao = mocker.patch("superset.semantic_layers.api.SemanticViewDAO")
|
||||
mock_sv_dao.find_by_semantic_layer.return_value = [existing_view]
|
||||
mock_dao.get_semantic_views.return_value = [existing_view]
|
||||
|
||||
response = client.post(
|
||||
f"/api/v1/semantic_layer/{test_uuid}/views",
|
||||
@@ -1915,8 +1913,7 @@ def test_get_views_existing_dict_config(
|
||||
existing_view.name = "View X"
|
||||
existing_view.configuration = {"key": "val"} # dict, not string
|
||||
|
||||
mock_sv_dao = mocker.patch("superset.semantic_layers.api.SemanticViewDAO")
|
||||
mock_sv_dao.find_by_semantic_layer.return_value = [existing_view]
|
||||
mock_dao.get_semantic_views.return_value = [existing_view]
|
||||
|
||||
response = client.post(
|
||||
f"/api/v1/semantic_layer/{test_uuid}/views",
|
||||
|
||||
Reference in New Issue
Block a user