mirror of
https://github.com/apache/superset.git
synced 2026-05-12 19:35:17 +00:00
Small fixes
This commit is contained in:
@@ -105,7 +105,13 @@ class CeleryConfig:
|
|||||||
|
|
||||||
CELERY_CONFIG = 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
|
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
|
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.
|
# 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, name_filter, sql_filter, type_filter = self._parse_filters(filters)
|
||||||
source_type = self._resolve_source_type(source_type, sql_filter, type_filter)
|
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)
|
ds_q = DatasourceDAO.build_dataset_query(name_filter, sql_filter)
|
||||||
sv_q = DatasourceDAO.build_semantic_view_query(name_filter)
|
sv_q = DatasourceDAO.build_semantic_view_query(name_filter)
|
||||||
|
|
||||||
@@ -108,8 +111,18 @@ class GetCombinedDatasourceListCommand(BaseCommand):
|
|||||||
sql_filter: bool | None,
|
sql_filter: bool | None,
|
||||||
type_filter: str | None,
|
type_filter: str | None,
|
||||||
) -> str:
|
) -> 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 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"
|
return "database"
|
||||||
if not self._can_read_datasets:
|
if not self._can_read_datasets:
|
||||||
return "semantic_layer"
|
return "semantic_layer"
|
||||||
|
|||||||
@@ -22,18 +22,20 @@ from __future__ import annotations
|
|||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from sqlalchemy.exc import StatementError
|
from sqlalchemy.exc import StatementError
|
||||||
|
|
||||||
from superset_core.semantic_layers.daos import (
|
from superset_core.semantic_layers.daos import (
|
||||||
AbstractSemanticLayerDAO,
|
AbstractSemanticLayerDAO,
|
||||||
AbstractSemanticViewDAO,
|
AbstractSemanticViewDAO,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from superset.daos.base import BaseDAO
|
||||||
from superset.extensions import db
|
from superset.extensions import db
|
||||||
from superset.semantic_layers.models import SemanticLayer, SemanticView
|
from superset.semantic_layers.models import SemanticLayer, SemanticView
|
||||||
from superset.utils import json
|
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.
|
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."""
|
"""Data Access Object for SemanticView model."""
|
||||||
|
|
||||||
model_cls = SemanticView
|
model_cls = SemanticView
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ from superset.commands.semantic_layer.update import (
|
|||||||
UpdateSemanticViewCommand,
|
UpdateSemanticViewCommand,
|
||||||
)
|
)
|
||||||
from superset.constants import MODEL_API_RW_METHOD_PERMISSION_MAP
|
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.datasets.schemas import get_delete_ids_schema
|
||||||
from superset.models.core import Database
|
from superset.models.core import Database
|
||||||
from superset.semantic_layers.models import SemanticLayer, SemanticView
|
from superset.semantic_layers.models import SemanticLayer, SemanticView
|
||||||
@@ -175,6 +175,11 @@ class SemanticViewRestApi(BaseSupersetModelRestApi):
|
|||||||
class_permission_name = "SemanticView"
|
class_permission_name = "SemanticView"
|
||||||
method_permission_name = MODEL_API_RW_METHOD_PERMISSION_MAP
|
method_permission_name = MODEL_API_RW_METHOD_PERMISSION_MAP
|
||||||
include_route_methods = {"put", "post", "delete", "bulk_delete"}
|
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()
|
edit_model_schema = SemanticViewPutSchema()
|
||||||
|
|
||||||
@@ -635,7 +640,7 @@ class SemanticLayerRestApi(BaseSupersetApi):
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Check which views already exist with the same runtime config
|
# 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()
|
existing_keys: set[tuple[str, str]] = set()
|
||||||
for v in existing:
|
for v in existing:
|
||||||
config = v.configuration
|
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 [])]
|
metrics = [all_metrics[metric] for metric in (query_object.metrics or [])]
|
||||||
|
|
||||||
grain = (
|
grain = _convert_time_grain(query_object.extras.get("time_grain_sqla"))
|
||||||
_convert_time_grain(query_object.extras["time_grain_sqla"])
|
|
||||||
if "time_grain_sqla" in query_object.extras
|
|
||||||
else None
|
|
||||||
)
|
|
||||||
dimensions = [
|
dimensions = [
|
||||||
dimension
|
dimension
|
||||||
for dimension in semantic_view.dimensions
|
for dimension in semantic_view.dimensions
|
||||||
@@ -740,13 +736,17 @@ def _get_group_limit_filters(
|
|||||||
return filters if filters else None
|
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.
|
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:
|
try:
|
||||||
return Grains.get(time_grain)
|
return Grains.get(time_grain)
|
||||||
except (ValueError, isodate.ISO8601Error):
|
except (TypeError, ValueError, isodate.ISO8601Error):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1796,8 +1796,7 @@ def test_get_views(
|
|||||||
mock_dao = mocker.patch("superset.semantic_layers.api.SemanticLayerDAO")
|
mock_dao = mocker.patch("superset.semantic_layers.api.SemanticLayerDAO")
|
||||||
mock_dao.find_by_uuid.return_value = mock_layer
|
mock_dao.find_by_uuid.return_value = mock_layer
|
||||||
|
|
||||||
mock_sv_dao = mocker.patch("superset.semantic_layers.api.SemanticViewDAO")
|
mock_dao.get_semantic_views.return_value = []
|
||||||
mock_sv_dao.find_by_semantic_layer.return_value = []
|
|
||||||
|
|
||||||
response = client.post(
|
response = client.post(
|
||||||
f"/api/v1/semantic_layer/{test_uuid}/views",
|
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.name = "Existing View"
|
||||||
existing_view.configuration = '{"database": "mydb"}'
|
existing_view.configuration = '{"database": "mydb"}'
|
||||||
|
|
||||||
mock_sv_dao = mocker.patch("superset.semantic_layers.api.SemanticViewDAO")
|
mock_dao.get_semantic_views.return_value = [existing_view]
|
||||||
mock_sv_dao.find_by_semantic_layer.return_value = [existing_view]
|
|
||||||
|
|
||||||
response = client.post(
|
response = client.post(
|
||||||
f"/api/v1/semantic_layer/{test_uuid}/views",
|
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.name = "View X"
|
||||||
existing_view.configuration = {"key": "val"} # dict, not string
|
existing_view.configuration = {"key": "val"} # dict, not string
|
||||||
|
|
||||||
mock_sv_dao = mocker.patch("superset.semantic_layers.api.SemanticViewDAO")
|
mock_dao.get_semantic_views.return_value = [existing_view]
|
||||||
mock_sv_dao.find_by_semantic_layer.return_value = [existing_view]
|
|
||||||
|
|
||||||
response = client.post(
|
response = client.post(
|
||||||
f"/api/v1/semantic_layer/{test_uuid}/views",
|
f"/api/v1/semantic_layer/{test_uuid}/views",
|
||||||
|
|||||||
Reference in New Issue
Block a user