You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by mi...@apache.org on 2024/01/26 21:11:18 UTC

(superset) 02/03: fix(tags): Filter system tags from the tags list (#26701)

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

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

commit 84f0d748909ce16d08bc95d72cca5c23fb5f1f73
Author: Vitor Avila <96...@users.noreply.github.com>
AuthorDate: Fri Jan 26 17:00:27 2024 -0300

    fix(tags): Filter system tags from the tags list (#26701)
    
    (cherry picked from commit 4f56f5ee84dffc401aced7cd705a2df910072e92)
---
 superset-frontend/src/pages/Tags/index.tsx | 20 ++++++++++++-
 superset/tags/api.py                       |  3 ++
 superset/tags/filters.py                   | 39 +++++++++++++++++++++++++
 tests/integration_tests/tags/api_tests.py  | 46 ++++++++++++++++++++++++++++++
 4 files changed, 107 insertions(+), 1 deletion(-)

diff --git a/superset-frontend/src/pages/Tags/index.tsx b/superset-frontend/src/pages/Tags/index.tsx
index b0c998c3f3..30bdc99343 100644
--- a/superset-frontend/src/pages/Tags/index.tsx
+++ b/superset-frontend/src/pages/Tags/index.tsx
@@ -59,6 +59,17 @@ function TagList(props: TagListProps) {
   const { addDangerToast, addSuccessToast, user } = props;
   const { userId } = user;
 
+  const initialFilters = useMemo(
+    () => [
+      {
+        id: 'type',
+        operator: 'custom_tag',
+        value: true,
+      },
+    ],
+    [],
+  );
+
   const {
     state: {
       loading,
@@ -70,7 +81,14 @@ function TagList(props: TagListProps) {
     fetchData,
     toggleBulkSelect,
     refreshData,
-  } = useListViewResource<Tag>('tag', t('tag'), addDangerToast);
+  } = useListViewResource<Tag>(
+    'tag',
+    t('tag'),
+    addDangerToast,
+    undefined,
+    undefined,
+    initialFilters,
+  );
 
   const [showTagModal, setShowTagModal] = useState<boolean>(false);
   const [tagToEdit, setTagToEdit] = useState<Tag | null>(null);
diff --git a/superset/tags/api.py b/superset/tags/api.py
index c0df921e3e..ad25ffe7c9 100644
--- a/superset/tags/api.py
+++ b/superset/tags/api.py
@@ -40,6 +40,7 @@ from superset.constants import MODEL_API_RW_METHOD_PERMISSION_MAP, RouteMethod
 from superset.daos.tag import TagDAO
 from superset.exceptions import MissingUserContextException
 from superset.extensions import event_logger
+from superset.tags.filters import UserCreatedTagTypeFilter
 from superset.tags.models import ObjectType, Tag
 from superset.tags.schemas import (
     delete_tags_schema,
@@ -119,6 +120,8 @@ class TagRestApi(BaseSupersetModelRestApi):
     }
     allowed_rel_fields = {"created_by", "changed_by"}
 
+    search_filters = {"type": [UserCreatedTagTypeFilter]}
+
     add_model_schema = TagPostSchema()
     edit_model_schema = TagPutSchema()
     tag_get_response_schema = TagGetResponseSchema()
diff --git a/superset/tags/filters.py b/superset/tags/filters.py
new file mode 100644
index 0000000000..ff6be712d3
--- /dev/null
+++ b/superset/tags/filters.py
@@ -0,0 +1,39 @@
+# 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.
+from flask_babel import lazy_gettext as _
+from sqlalchemy.orm import Query
+
+from superset.tags.models import Tag, TagType
+from superset.views.base import BaseFilter
+
+
+class UserCreatedTagTypeFilter(BaseFilter):  # pylint: disable=too-few-public-methods
+    """
+    Filter for tag type.
+    When set to True, only user-created tags are returned.
+    When set to False, only system tags are returned.
+    """
+
+    name = _("Is custom tag")
+    arg_name = "custom_tag"
+
+    def apply(self, query: Query, value: bool) -> Query:
+        if value:
+            return query.filter(Tag.type == TagType.custom)
+        if value is False:
+            return query.filter(Tag.type != TagType.custom)
+        return query
diff --git a/tests/integration_tests/tags/api_tests.py b/tests/integration_tests/tags/api_tests.py
index 863288a3e7..af58eaa195 100644
--- a/tests/integration_tests/tags/api_tests.py
+++ b/tests/integration_tests/tags/api_tests.py
@@ -17,6 +17,7 @@
 # isort:skip_file
 """Unit tests for Superset"""
 import json
+import prison
 from datetime import datetime
 
 from flask import g
@@ -30,6 +31,7 @@ from superset.models.slice import Slice
 from superset.models.sql_lab import SavedQuery
 from superset.tags.models import user_favorite_tag_table
 from unittest.mock import patch
+from urllib import parse
 
 
 import tests.integration_tests.test_app
@@ -175,6 +177,50 @@ class TestTagApi(SupersetTestCase):
         # check expected columns
         assert data["list_columns"] == TAGS_LIST_COLUMNS
 
+    def test_get_list_tag_filtered(self):
+        """
+        Query API: Test get list query applying filters for
+        type == "custom" and type != "custom"
+        """
+        tags = [
+            {"name": "Test custom Tag", "type": "custom"},
+            {"name": "type:dashboard", "type": "type"},
+            {"name": "owner:1", "type": "owner"},
+            {"name": "Another Tag", "type": "custom"},
+            {"name": "favorited_by:1", "type": "favorited_by"},
+        ]
+
+        for tag in tags:
+            self.insert_tag(
+                name=tag["name"],
+                tag_type=tag["type"],
+            )
+        self.login(username="admin")
+
+        # Only user-created tags
+        query = {
+            "filters": [
+                {
+                    "col": "type",
+                    "opr": "custom_tag",
+                    "value": True,
+                }
+            ],
+        }
+        uri = f"api/v1/tag/?{parse.urlencode({'q': prison.dumps(query)})}"
+        rv = self.client.get(uri)
+        self.assertEqual(rv.status_code, 200)
+        data = json.loads(rv.data.decode("utf-8"))
+        assert data["count"] == 2
+
+        # Only system tags
+        query["filters"][0]["value"] = False
+        uri = f"api/v1/tag/?{parse.urlencode({'q': prison.dumps(query)})}"
+        rv = self.client.get(uri)
+        self.assertEqual(rv.status_code, 200)
+        data = json.loads(rv.data.decode("utf-8"))
+        assert data["count"] == 3
+
     # test add tagged objects
     @pytest.mark.usefixtures("load_world_bank_dashboard_with_slices")
     @pytest.mark.usefixtures("load_birth_names_dashboard_with_slices")