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")