Small fixes

This commit is contained in:
Beto Dealmeida
2026-04-20 15:55:34 -04:00
parent 99167ecf55
commit 68ccf47f0d
5 changed files with 76 additions and 10 deletions

View File

@@ -1045,17 +1045,18 @@ const DatasetList: FunctionComponent<DatasetListProps> = ({
);
}
Promise.all(promises).then(
() => {
refreshData();
Promise.allSettled(promises).then(results => {
const failures = results.filter(r => r.status === 'rejected');
// Always refresh so the list reflects whatever actually got deleted.
refreshData();
if (failures.length === 0) {
addSuccessToast(t('Deleted %s item(s)', datasetsToDelete.length));
},
createErrorHandler(errMsg =>
} else {
addDangerToast(
t('There was an issue deleting the selected datasets: %s', errMsg),
),
),
);
t('There was an issue deleting the selected items'),
);
}
});
};
const handleDatasetDuplicate = (newDatasetName: string) => {

View File

@@ -21,14 +21,17 @@ from functools import partial
from sqlalchemy.exc import SQLAlchemyError
from superset import security_manager
from superset.commands.base import BaseCommand
from superset.commands.semantic_layer.exceptions import (
SemanticLayerDeleteFailedError,
SemanticLayerNotFoundError,
SemanticViewDeleteFailedError,
SemanticViewForbiddenError,
SemanticViewNotFoundError,
)
from superset.daos.semantic_layer import SemanticLayerDAO, SemanticViewDAO
from superset.exceptions import SupersetSecurityException
from superset.semantic_layers.models import SemanticLayer, SemanticView
from superset.utils.decorators import on_error, transaction
@@ -79,6 +82,10 @@ class DeleteSemanticViewCommand(BaseCommand):
self._model = SemanticViewDAO.find_by_id(self._pk, id_column="id")
if not self._model:
raise SemanticViewNotFoundError()
try:
security_manager.raise_for_ownership(self._model)
except SupersetSecurityException as ex:
raise SemanticViewForbiddenError() from ex
class BulkDeleteSemanticViewCommand(BaseCommand):
@@ -101,3 +108,8 @@ class BulkDeleteSemanticViewCommand(BaseCommand):
self._models = SemanticViewDAO.find_by_ids(self._model_ids, id_column="id")
if len(self._models) != len(self._model_ids):
raise SemanticViewNotFoundError()
for model in self._models:
try:
security_manager.raise_for_ownership(model)
except SupersetSecurityException as ex:
raise SemanticViewForbiddenError() from ex

View File

@@ -362,6 +362,8 @@ class SemanticViewRestApi(BaseSupersetModelRestApi):
return self.response(200, message="OK")
except SemanticViewNotFoundError:
return self.response_404()
except SemanticViewForbiddenError:
return self.response_403()
except SemanticViewDeleteFailedError as ex:
logger.error(
"Error deleting semantic view: %s",
@@ -422,6 +424,8 @@ class SemanticViewRestApi(BaseSupersetModelRestApi):
)
except SemanticViewNotFoundError:
return self.response_404()
except SemanticViewForbiddenError:
return self.response_403()
except SemanticViewDeleteFailedError as ex:
logger.error(
"Error bulk deleting semantic views: %s",

View File

@@ -59,6 +59,11 @@ def test_delete_semantic_view_success(mocker: MockerFixture) -> None:
)
dao.find_by_id.return_value = mock_model
# Admin is owner of everything — no exception raised
mocker.patch(
"superset.commands.semantic_layer.delete.security_manager"
).raise_for_ownership.return_value = None
from superset.commands.semantic_layer.delete import DeleteSemanticViewCommand
DeleteSemanticViewCommand(42).run()
@@ -67,6 +72,26 @@ def test_delete_semantic_view_success(mocker: MockerFixture) -> None:
dao.delete.assert_called_once_with([mock_model])
def test_delete_semantic_view_forbidden(mocker: MockerFixture) -> None:
"""Test that SemanticViewForbiddenError is raised for non-owners."""
from superset.commands.semantic_layer.delete import DeleteSemanticViewCommand
from superset.commands.semantic_layer.exceptions import SemanticViewForbiddenError
from superset.exceptions import SupersetSecurityException
dao = mocker.patch(
"superset.commands.semantic_layer.delete.SemanticViewDAO",
)
dao.find_by_id.return_value = MagicMock()
mocker.patch(
"superset.security_manager.raise_for_ownership",
side_effect=SupersetSecurityException(MagicMock()),
)
with pytest.raises(SemanticViewForbiddenError):
DeleteSemanticViewCommand(42).run()
def test_delete_semantic_view_not_found(mocker: MockerFixture) -> None:
"""Test that SemanticViewNotFoundError is raised when view is missing."""
dao = mocker.patch(
@@ -92,6 +117,10 @@ def test_bulk_delete_semantic_view_success(mocker: MockerFixture) -> None:
)
dao.find_by_ids.return_value = mock_models
mocker.patch(
"superset.commands.semantic_layer.delete.security_manager"
).raise_for_ownership.return_value = None
from superset.commands.semantic_layer.delete import BulkDeleteSemanticViewCommand
BulkDeleteSemanticViewCommand([1, 2]).run()
@@ -100,6 +129,26 @@ def test_bulk_delete_semantic_view_success(mocker: MockerFixture) -> None:
dao.delete.assert_called_once_with(mock_models)
def test_bulk_delete_semantic_view_forbidden(mocker: MockerFixture) -> None:
"""Test that SemanticViewForbiddenError is raised for non-owners."""
from superset.commands.semantic_layer.delete import BulkDeleteSemanticViewCommand
from superset.commands.semantic_layer.exceptions import SemanticViewForbiddenError
from superset.exceptions import SupersetSecurityException
dao = mocker.patch(
"superset.commands.semantic_layer.delete.SemanticViewDAO",
)
dao.find_by_ids.return_value = [MagicMock(), MagicMock()]
mocker.patch(
"superset.security_manager.raise_for_ownership",
side_effect=SupersetSecurityException(MagicMock()),
)
with pytest.raises(SemanticViewForbiddenError):
BulkDeleteSemanticViewCommand([1, 2]).run()
def test_bulk_delete_semantic_view_not_found(mocker: MockerFixture) -> None:
"""Test that SemanticViewNotFoundError is raised when any id is missing."""
dao = mocker.patch(

View File

@@ -1907,7 +1907,7 @@ def test_get_views_exception(
)
assert response.status_code == 400
assert "Connection failed" in response.json["message"]
assert "Unable to fetch semantic views" in response.json["message"]
@SEMANTIC_LAYERS_APP