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 2023/12/04 21:05:49 UTC

(superset) 04/15: chore(tags): Allow for lookup via ids vs. name in the API (#25996)

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 2c3bf2895fdc5fc0001bd8466cbb81bc76f349ca
Author: Hugh A. Miles II <hu...@gmail.com>
AuthorDate: Wed Nov 29 13:59:59 2023 -0500

    chore(tags): Allow for lookup via ids vs. name in the API (#25996)
    
    (cherry picked from commit ee308fbc6459273bd6234de03c418e9d1dcbc3a7)
---
 superset-frontend/src/features/tags/TagModal.tsx  |  6 ++---
 superset-frontend/src/features/tags/tags.ts       | 17 ++++++++++++
 superset-frontend/src/pages/AllEntities/index.tsx | 10 ++++---
 superset/daos/tag.py                              |  8 ++++++
 superset/tags/api.py                              | 15 ++++++++---
 tests/integration_tests/tags/dao_tests.py         | 33 +++++++++++++++++++++++
 6 files changed, 80 insertions(+), 9 deletions(-)

diff --git a/superset-frontend/src/features/tags/TagModal.tsx b/superset-frontend/src/features/tags/TagModal.tsx
index a0ac8636a5..5057c8441d 100644
--- a/superset-frontend/src/features/tags/TagModal.tsx
+++ b/superset-frontend/src/features/tags/TagModal.tsx
@@ -26,7 +26,7 @@ import { Input } from 'antd';
 import { Divider } from 'src/components';
 import Button from 'src/components/Button';
 import { Tag } from 'src/views/CRUD/types';
-import { fetchObjects } from 'src/features/tags/tags';
+import { fetchObjectsByTagIds } from 'src/features/tags/tags';
 
 const StyledModalBody = styled.div`
   .ant-select-dropdown {
@@ -115,8 +115,8 @@ const TagModal: React.FC<TagModalProps> = ({
     };
     clearResources();
     if (isEditMode) {
-      fetchObjects(
-        { tags: editTag.name, types: null },
+      fetchObjectsByTagIds(
+        { tagIds: [editTag.id], types: null },
         (data: Tag[]) => {
           data.forEach(updateResourceOptions);
           setDashboardsToTag(resourceMap[TaggableResources.Dashboard]);
diff --git a/superset-frontend/src/features/tags/tags.ts b/superset-frontend/src/features/tags/tags.ts
index 45c4e88fc5..db172681cb 100644
--- a/superset-frontend/src/features/tags/tags.ts
+++ b/superset-frontend/src/features/tags/tags.ts
@@ -194,3 +194,20 @@ export function fetchObjects(
     .then(({ json }) => callback(json.result))
     .catch(response => error(response));
 }
+
+export function fetchObjectsByTagIds(
+  {
+    tagIds = [],
+    types,
+  }: { tagIds: number[] | undefined; types: string | null },
+  callback: (json: JsonObject) => void,
+  error: (response: Response) => void,
+) {
+  let url = `/api/v1/tag/get_objects/?tagIds=${tagIds}`;
+  if (types) {
+    url += `&types=${types}`;
+  }
+  SupersetClient.get({ endpoint: url })
+    .then(({ json }) => callback(json.result))
+    .catch(response => error(response));
+}
diff --git a/superset-frontend/src/pages/AllEntities/index.tsx b/superset-frontend/src/pages/AllEntities/index.tsx
index ca815795d6..a1e2c52fe4 100644
--- a/superset-frontend/src/pages/AllEntities/index.tsx
+++ b/superset-frontend/src/pages/AllEntities/index.tsx
@@ -33,7 +33,7 @@ import { PageHeaderWithActions } from 'src/components/PageHeaderWithActions';
 import { Tag } from 'src/views/CRUD/types';
 import TagModal from 'src/features/tags/TagModal';
 import withToasts, { useToasts } from 'src/components/MessageToasts/withToasts';
-import { fetchObjects, fetchSingleTag } from 'src/features/tags/tags';
+import { fetchObjectsByTagIds, fetchSingleTag } from 'src/features/tags/tags';
 import Loading from 'src/components/Loading';
 
 interface TaggedObject {
@@ -146,8 +146,12 @@ function AllEntities() {
 
   const fetchTaggedObjects = () => {
     setLoading(true);
-    fetchObjects(
-      { tags: tag?.name || '', types: null },
+    if (!tag) {
+      addDangerToast('Error tag object is not referenced!');
+      return;
+    }
+    fetchObjectsByTagIds(
+      { tagIds: [tag?.id] || '', types: null },
       (data: TaggedObject[]) => {
         const objects = { dashboard: [], chart: [], query: [] };
         data.forEach(function (object) {
diff --git a/superset/daos/tag.py b/superset/daos/tag.py
index fbc9aa229e..60362bfbbd 100644
--- a/superset/daos/tag.py
+++ b/superset/daos/tag.py
@@ -167,6 +167,14 @@ class TagDAO(BaseDAO[Tag]):
             .first()
         )
 
+    @staticmethod
+    def get_tagged_objects_by_tag_id(
+        tag_ids: Optional[list[int]], obj_types: Optional[list[str]] = None
+    ) -> list[dict[str, Any]]:
+        tags = db.session.query(Tag).filter(Tag.id.in_(tag_ids)).all()
+        tag_names = [tag.name for tag in tags]
+        return TagDAO.get_tagged_objects_for_tags(tag_names, obj_types)
+
     @staticmethod
     def get_tagged_objects_for_tags(
         tags: Optional[list[str]] = None, obj_types: Optional[list[str]] = None
diff --git a/superset/tags/api.py b/superset/tags/api.py
index a4fc185f29..a3c95a5814 100644
--- a/superset/tags/api.py
+++ b/superset/tags/api.py
@@ -584,12 +584,21 @@ class TagRestApi(BaseSupersetModelRestApi):
             500:
               $ref: '#/components/responses/500'
         """
+        tag_ids = [
+            tag_id for tag_id in request.args.get("tagIds", "").split(",") if tag_id
+        ]
         tags = [tag for tag in request.args.get("tags", "").split(",") if tag]
         # filter types
         types = [type_ for type_ in request.args.get("types", "").split(",") if type_]
 
         try:
-            tagged_objects = TagDAO.get_tagged_objects_for_tags(tags, types)
+            if tag_ids:
+                # priotize using ids for lookups vs. names mainly using this
+                # for backward compatibility
+                tagged_objects = TagDAO.get_tagged_objects_by_tag_id(tag_ids, types)
+            else:
+                tagged_objects = TagDAO.get_tagged_objects_for_tags(tags, types)
+
             result = [
                 self.object_entity_response_schema.dump(tagged_object)
                 for tagged_object in tagged_objects
@@ -609,11 +618,11 @@ class TagRestApi(BaseSupersetModelRestApi):
         log_to_statsd=False,
     )
     def favorite_status(self, **kwargs: Any) -> Response:
-        """Favorite Stars for Dashboards
+        """Favorite Stars for Tags
         ---
         get:
           description: >-
-            Check favorited dashboards for current user
+            Get favorited tags for current user
           parameters:
           - in: query
             name: q
diff --git a/tests/integration_tests/tags/dao_tests.py b/tests/integration_tests/tags/dao_tests.py
index ea4b3ba783..272ba43ed3 100644
--- a/tests/integration_tests/tags/dao_tests.py
+++ b/tests/integration_tests/tags/dao_tests.py
@@ -207,6 +207,39 @@ class TestTagsDAO(SupersetTestCase):
         tagged_objects = TagDAO.get_tagged_objects_for_tags(obj_types=["chart"])
         assert len(tagged_objects) == num_charts
 
+    @pytest.mark.usefixtures("load_world_bank_dashboard_with_slices")
+    @pytest.mark.usefixtures("with_tagging_system_feature")
+    @pytest.mark.usefixtures("create_tags")
+    # test get objects from tag
+    def test_get_objects_from_tag_with_id(self):
+        # create tagged objects
+        dashboard = (
+            db.session.query(Dashboard)
+            .filter(Dashboard.dashboard_title == "World Bank's Data")
+            .first()
+        )
+        dashboard_id = dashboard.id
+        tag_1 = db.session.query(Tag).filter_by(name="example_tag_1").one()
+        tag_2 = db.session.query(Tag).filter_by(name="example_tag_2").one()
+        tag_ids = [tag_1.id, tag_2.id]
+        self.insert_tagged_object(
+            object_id=dashboard_id, object_type=ObjectType.dashboard, tag_id=tag_1.id
+        )
+        # get objects
+        tagged_objects = TagDAO.get_tagged_objects_by_tag_id(tag_ids)
+        assert len(tagged_objects) == 1
+
+        # test get objects from tag with type
+        tagged_objects = TagDAO.get_tagged_objects_by_tag_id(
+            tag_ids, obj_types=["dashboard", "chart"]
+        )
+        assert len(tagged_objects) == 1
+
+        tagged_objects = TagDAO.get_tagged_objects_by_tag_id(
+            tag_ids, obj_types=["chart"]
+        )
+        assert len(tagged_objects) == 0
+
     @pytest.mark.usefixtures("load_world_bank_dashboard_with_slices")
     @pytest.mark.usefixtures("with_tagging_system_feature")
     @pytest.mark.usefixtures("create_tagged_objects")