bito-code-review[bot] commented on code in PR #40130:
URL: https://github.com/apache/superset/pull/40130#discussion_r3510299550
##########
superset/translations/mi/LC_MESSAGES/messages.po:
##########
@@ -4618,6 +4618,9 @@ msgstr "Kāore i taea te hanga i te rārangi raraunga."
msgid "Dataset could not be duplicated."
msgstr "Kāore i taea te tārua i te rārangi raraunga."
+msgid "Dataset could not be restored."
+msgstr ""
Review Comment:
<!-- Bito Reply -->
The suggestion is appropriate because it maintains consistency with the
existing Maori localization pattern used for other dataset error messages in
the file. Providing a translation instead of an empty string ensures that users
receive localized feedback when a restoration operation fails, rather than
falling back to the source text.
**superset/translations/mi/LC_MESSAGES/messages.po**
```
msgid "Dataset could not be restored."
-msgstr ""
+msgstr "Kāore i taea te whakaora i te rārangi raraunga."
```
##########
superset/translations/fi/LC_MESSAGES/messages.po:
##########
@@ -8222,6 +8222,9 @@ msgstr "Tietojoukkoa ei voitu monistaa."
# de, es, fa, fr, it, ja, lv, mi, nl, pl, pt, pt_BR, ru, sk, sl, tr, uk, zh,
# zh_TW]
#, fuzzy
+msgid "Dataset could not be restored."
+msgstr ""
Review Comment:
<!-- Bito Reply -->
The suggestion is appropriate as it provides a missing Finnish translation
for a dataset error message, ensuring consistency with other similar error
messages in the file. Applying this change improves the user experience by
providing a localized message instead of falling back to English.
**superset/translations/fi/LC_MESSAGES/messages.po**
```
8226: -msgstr ""
8226:+msgstr "Tietojoukkoa ei voitu palauttaa."
```
##########
tests/integration_tests/datasets/soft_delete_tests.py:
##########
@@ -0,0 +1,462 @@
+# 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.
+"""Integration tests for dataset soft-delete and restore."""
+
+from datetime import datetime
+
+from superset import security_manager
+from superset.connectors.sqla.models import SqlaTable
+from superset.constants import SKIP_VISIBILITY_FILTER_CLASSES
+from superset.extensions import db
+from superset.models.core import Database
+from superset.models.slice import Slice
+from superset.utils import json
+from tests.integration_tests.base_tests import SupersetTestCase
+from tests.integration_tests.constants import (
+ ADMIN_USERNAME,
+ ALPHA_USERNAME,
+ GAMMA_USERNAME,
+)
+
+
+def _restore_dataset(dataset_id: int) -> None:
+ """Restore a soft-deleted dataset (cleanup helper).
+
+ Module-level so every test class in this file can use it. Used in
+ ``finally`` blocks so a failed assertion can't strand a soft-deleted
+ row and leak it into later tests; re-queries with the
+ visibility-filter bypass and only restores if still soft-deleted.
+ """
+ row = (
+ db.session.query(SqlaTable)
+ .execution_options(**{SKIP_VISIBILITY_FILTER_CLASSES: {SqlaTable}})
+ .filter(SqlaTable.id == dataset_id)
+ .one_or_none()
+ )
+ if row is not None and row.deleted_at is not None:
+ row.restore()
+ db.session.commit()
+
+
+class TestDatasetSoftDelete(SupersetTestCase):
+ """Tests for dataset soft-delete behaviour (T015, T018)."""
+
+ def _get_example_dataset_id(self) -> int:
+ """Get an existing example dataset ID for testing."""
+ dataset = db.session.query(SqlaTable).first()
+ assert dataset is not None, "No datasets found — load examples first"
+ return dataset.id
+
+ def _restore_dataset(self, dataset_id: int) -> None:
+ """Class-method shim over the module-level helper (existing call
sites)."""
+ _restore_dataset(dataset_id)
+
+ def test_delete_dataset_soft_deletes(self) -> None:
+ """DELETE should set deleted_at instead of removing the row."""
+ dataset_id = self._get_example_dataset_id()
+ self.login(ADMIN_USERNAME)
+
+ try:
+ rv = self.client.delete(f"/api/v1/dataset/{dataset_id}")
+ assert rv.status_code == 200
+
+ row = (
+ db.session.query(SqlaTable)
+ .execution_options(**{SKIP_VISIBILITY_FILTER_CLASSES:
{SqlaTable}})
+ .filter(SqlaTable.id == dataset_id)
+ .one_or_none()
+ )
+ assert row is not None
+ assert row.deleted_at is not None
+ finally:
+ self._restore_dataset(dataset_id)
+
+ def test_soft_deleted_dataset_excluded_from_list(self) -> None:
+ """GET /api/v1/dataset/ should not include soft-deleted datasets."""
+ dataset_id = self._get_example_dataset_id()
+ self.login(ADMIN_USERNAME)
+
+ try:
+ rv = self.client.delete(f"/api/v1/dataset/{dataset_id}")
+ assert rv.status_code == 200
+
+ rv = self.client.get("/api/v1/dataset/")
+ data = json.loads(rv.data)
+ ids = [d["id"] for d in data["result"]]
+ assert dataset_id not in ids
+ finally:
+ self._restore_dataset(dataset_id)
+
+ def test_soft_deleted_dataset_included_in_list_when_requested(self) ->
None:
+ """GET /api/v1/dataset/ with dataset_deleted_state=include returns
deleted datasets.""" # noqa: E501
+ dataset_id = self._get_example_dataset_id()
+ self.login(ADMIN_USERNAME)
+
+ try:
+ rv = self.client.delete(f"/api/v1/dataset/{dataset_id}")
+ assert rv.status_code == 200
+
+ rison_query = (
+ "(filters:!((col:id,opr:dataset_deleted_state,value:include)))"
+ )
+ rv = self.client.get(f"/api/v1/dataset/?q={rison_query}")
+ assert rv.status_code == 200
+
+ data = json.loads(rv.data)
+ deleted_row = next(
+ (row for row in data["result"] if row["id"] == dataset_id),
+ None,
+ )
+ assert deleted_row is not None
+ assert deleted_row["deleted_at"] is not None
+ finally:
+ self._restore_dataset(dataset_id)
+
+ def test_only_filter_returns_only_soft_deleted_datasets(self) -> None:
+ """dataset_deleted_state=only excludes live rows and returns only
deleted ones.""" # noqa: E501
+ ids = [row.id for row in db.session.query(SqlaTable).limit(2).all()]
+ assert len(ids) >= 2, "Need at least two example datasets for this
test"
+ live_id, deleted_id = ids[0], ids[1]
+ self.login(ADMIN_USERNAME)
+
+ try:
+ rv = self.client.delete(f"/api/v1/dataset/{deleted_id}")
+ assert rv.status_code == 200
+
+ rison_query =
"(filters:!((col:id,opr:dataset_deleted_state,value:only)))"
+ rv = self.client.get(f"/api/v1/dataset/?q={rison_query}")
+ assert rv.status_code == 200
+
+ data = json.loads(rv.data)
+ returned_ids = {row["id"] for row in data["result"]}
+ assert deleted_id in returned_ids
+ assert live_id not in returned_ids
+ finally:
+ self._restore_dataset(deleted_id)
+
+ def _hard_delete_created(self, dataset_id: int, database: Database) ->
None:
+ """Remove a test-created dataset + its database (visibility
bypassed)."""
+ row = (
+ db.session.query(SqlaTable)
+ .execution_options(**{SKIP_VISIBILITY_FILTER_CLASSES: {SqlaTable}})
+ .filter(SqlaTable.id == dataset_id)
+ .one_or_none()
+ )
+ if row:
+ db.session.delete(row)
+ db.session.delete(database)
+ db.session.commit()
+
+ def test_deleted_state_list_shows_owner_their_own_deleted(self) -> None:
+ """A non-admin owner can still enumerate their own soft-deleted
datasets.
+ Deleted-state scoping mirrors the restore audience, so it must not lock
+ owners out of their own trash."""
+ alpha = self.get_user(ALPHA_USERNAME)
+ database = Database(database_name="sd_owner_db",
sqlalchemy_uri="sqlite://")
+ db.session.add(database)
+ db.session.flush()
+ dataset = SqlaTable(
+ table_name="sd_owner_tbl", database=database, owners=[alpha]
+ )
+ db.session.add(dataset)
+ db.session.commit()
+ dataset_id = dataset.id
+
+ dataset.deleted_at = datetime(2026, 1, 1, 12, 0, 0)
Review Comment:
<!-- Bito Reply -->
The decision to maintain the current implementation is appropriate. These
helpers are scoped as internal test scaffolding, and adhering to the existing
style conventions of the test suite is a reasonable approach for this context.
##########
superset/translations/ko/LC_MESSAGES/messages.po:
##########
@@ -4578,6 +4578,9 @@ msgstr ""
msgid "Dataset could not be duplicated."
msgstr "데이터베이스를 업데이트할 수 없습니다."
+msgid "Dataset could not be restored."
+msgstr ""
Review Comment:
<!-- Bito Reply -->
The suggestion to add a Korean translation for the error message is
appropriate. Providing localized error messages ensures consistency across the
user interface, as other similar error messages in the same group already
include Korean translations.
**superset/translations/ko/LC_MESSAGES/messages.po**
```
+msgid "Dataset could not be restored."
+msgstr "데이터셋을 복원할 수 없습니다."
```
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]