You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by ma...@apache.org on 2025/07/16 19:26:49 UTC

(superset) branch fix_cube_dataset_issue created (now 4be5ee2f4b)

This is an automated email from the ASF dual-hosted git repository.

maximebeauchemin pushed a change to branch fix_cube_dataset_issue
in repository https://gitbox.apache.org/repos/asf/superset.git


      at 4be5ee2f4b fix: dataset endpoint @ /rowlevelsecurity/related/tables doesn't apply filters as expected

This branch includes the following new commits:

     new 4be5ee2f4b fix: dataset endpoint @ /rowlevelsecurity/related/tables doesn't apply filters as expected

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



(superset) 01/01: fix: dataset endpoint @ /rowlevelsecurity/related/tables doesn't apply filters as expected

Posted by ma...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

maximebeauchemin pushed a commit to branch fix_cube_dataset_issue
in repository https://gitbox.apache.org/repos/asf/superset.git

commit 4be5ee2f4b54f5a22921cb9aee8d92735d7ea6c7
Author: Maxime Beauchemin <ma...@gmail.com>
AuthorDate: Wed Jul 16 12:24:57 2025 -0700

    fix: dataset endpoint @ /rowlevelsecurity/related/tables doesn't apply filters as expected
    
      ## Summary
    
      This PR fixes an issue where the filter parameter in the Row Level Security (RLS) related tables endpoint
      (`/api/v1/rowlevelsecurity/related/tables`) was not being applied. When users tried to search for specific tables
      in the RLS CRUD UI, all tables were returned regardless of the filter value.
    
      ## Problem
    
      The `/api/v1/rowlevelsecurity/related/tables?q=(filter:mult,page:1,page_size:100)` endpoint was ignoring the
      `filter` parameter because:
      - The RLS API was missing the proper filter configuration for the `tables` field
      - Without this configuration, the `_get_related_filter` method in the base API didn't know which field to filter on
    
      ## Solution
    
      1. **Created a new filter class** `FilterRelatedTables` in `superset/views/filters.py` that:
         - Performs case-insensitive substring matching using SQLAlchemy's `ilike` with wildcards
         - Filters tables by their `table_name` field
         - Follows the same pattern as other "contains" filters in the codebase (e.g., `FilterRelatedOwners`,
      `DashboardTitleOrSlugFilter`)
    
      2. **Updated the RLS API configuration** to use this filter:
         ```python
         related_field_filters = {
             "tables": RelatedFieldFilter("table_name", FilterRelatedTables),
             "changed_by": RelatedFieldFilter("first_name", FilterRelatedOwners),
         }
    
      Testing
    
      Added a new test test_rls_tables_related_api_with_filter that verifies:
      - Filtering with a matching substring returns only tables containing that substring
      - Filtering with a non-existent substring returns no results
      - The filter is case-insensitive
    
      Changes
    
      - superset/views/filters.py: Added FilterRelatedTables class
      - superset/row_level_security/api.py: Added filter configuration for tables field
      - tests/integration_tests/security/row_level_security_tests.py: Added test for filter functionality
    
      Impact
    
      This fix improves the user experience in the RLS CRUD interface by allowing users to efficiently search and filter
      tables when creating or editing row-level security rules.
---
 superset/row_level_security/api.py                 |  2 ++
 superset/views/filters.py                          | 19 +++++++++++++++++++
 .../security/row_level_security_tests.py           | 22 ++++++++++++++++++++++
 3 files changed, 43 insertions(+)

diff --git a/superset/row_level_security/api.py b/superset/row_level_security/api.py
index 7ea0959842..24e9dd4935 100644
--- a/superset/row_level_security/api.py
+++ b/superset/row_level_security/api.py
@@ -55,6 +55,7 @@ from superset.views.filters import (
     BaseFilterRelatedRoles,
     BaseFilterRelatedUsers,
     FilterRelatedOwners,
+    FilterRelatedTables,
 )
 
 logger = logging.getLogger(__name__)
@@ -135,6 +136,7 @@ class RLSRestApi(BaseSupersetModelRestApi):
 
     allowed_rel_fields = {"tables", "roles", "created_by", "changed_by"}
     related_field_filters = {
+        "tables": RelatedFieldFilter("table_name", FilterRelatedTables),
         "changed_by": RelatedFieldFilter("first_name", FilterRelatedOwners),
     }
     base_related_field_filters = {
diff --git a/superset/views/filters.py b/superset/views/filters.py
index 19407219e2..ad9dc8c38a 100644
--- a/superset/views/filters.py
+++ b/superset/views/filters.py
@@ -102,3 +102,22 @@ class BaseFilterRelatedRoles(BaseFilter):  # pylint: disable=too-few-public-meth
             return extra_filters(query)
 
         return query
+
+
+class FilterRelatedTables(BaseFilter):  # pylint: disable=too-few-public-methods
+    """
+    A filter to allow searching for related tables.
+    Use in the api by adding something like:
+    related_field_filters = {
+      "tables": RelatedFieldFilter("table_name", FilterRelatedTables),
+    }
+    """
+
+    name = lazy_gettext("Table")
+    arg_name = "tables"
+
+    def apply(self, query: Query, value: Optional[Any]) -> Query:
+        from superset.connectors.sqla.models import SqlaTable
+
+        like_value = "%" + cast(str, value) + "%"
+        return query.filter(SqlaTable.table_name.ilike(like_value))
diff --git a/tests/integration_tests/security/row_level_security_tests.py b/tests/integration_tests/security/row_level_security_tests.py
index b31c4fa136..653b1feaf7 100644
--- a/tests/integration_tests/security/row_level_security_tests.py
+++ b/tests/integration_tests/security/row_level_security_tests.py
@@ -555,6 +555,28 @@ class TestRowLevelSecurityWithRelatedAPI(SupersetTestCase):
         assert len(result) == len(db_tables)
         assert db_table_names == received_tables
 
+    def test_rls_tables_related_api_with_filter(self):
+        self.login(ADMIN_USERNAME)
+        # Test with filter that should match 'birth_names'
+        params = prison.dumps({"filter": "birth", "page": 0, "page_size": 100})
+        rv = self.client.get(f"/api/v1/rowlevelsecurity/related/tables?q={params}")
+        assert rv.status_code == 200
+        data = json.loads(rv.data.decode("utf-8"))
+        result = data["result"]
+        received_tables = {table["text"] for table in result}
+        # Should only return tables with 'birth' in the name
+        assert all("birth" in table_name.lower() for table_name in received_tables)
+        assert len(result) >= 1  # At least birth_names should be returned
+
+        # Test with filter that should match nothing
+        params = prison.dumps({"filter": "nonexistent", "page": 0, "page_size": 100})
+        rv = self.client.get(f"/api/v1/rowlevelsecurity/related/tables?q={params}")
+        assert rv.status_code == 200
+        data = json.loads(rv.data.decode("utf-8"))
+        result = data["result"]
+        assert len(result) == 0
+        assert data["count"] == 0
+
     def test_rls_roles_related_api(self):
         self.login(ADMIN_USERNAME)
         params = prison.dumps({"page": 0, "page_size": 100})