You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by vi...@apache.org on 2022/09/23 08:01:30 UTC

[superset] branch master updated: feat: Add dataset tagging to the back-end (#20892)

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

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


The following commit(s) were added to refs/heads/master by this push:
     new 2e564897f8 feat: Add dataset tagging to the back-end (#20892)
2e564897f8 is described below

commit 2e564897f860192c3e3ecbe41cfbac6b3e557b35
Author: cccs-Dustin <96...@users.noreply.github.com>
AuthorDate: Fri Sep 23 04:01:17 2022 -0400

    feat: Add dataset tagging to the back-end (#20892)
    
    Co-authored-by: Ville Brofeldt <33...@users.noreply.github.com>
---
 superset/common/tags.py                            | 315 +++++++++++++++------
 superset/initialization/__init__.py                |   4 +
 ...8-07-26_11-10_c82ee8a39623_add_implicit_tags.py |   2 +-
 superset/models/core.py                            |   9 +-
 superset/models/dashboard.py                       |   7 -
 superset/models/slice.py                           |   8 -
 superset/models/sql_lab.py                         |   7 -
 superset/tags/core.py                              |  88 ++++++
 superset/{models/tags.py => tags/models.py}        |  46 ++-
 superset/tasks/cache.py                            |   2 +-
 superset/utils/url_map_converters.py               |   2 +-
 superset/views/tags.py                             |  30 +-
 .../integration_tests/fixtures/tags.py             |  30 +-
 tests/integration_tests/strategy_tests.py          |   2 +-
 tests/integration_tests/tagging_tests.py           | 276 ++++++++++++++++++
 15 files changed, 673 insertions(+), 155 deletions(-)

diff --git a/superset/common/tags.py b/superset/common/tags.py
index 74c882cf92..d85a33b84e 100644
--- a/superset/common/tags.py
+++ b/superset/common/tags.py
@@ -14,76 +14,20 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-from sqlalchemy import Metadata
+from typing import Any, List
+
+from sqlalchemy import MetaData
 from sqlalchemy.engine import Engine
 from sqlalchemy.exc import IntegrityError
-from sqlalchemy.sql import and_, func, functions, join, literal, select
-
-from superset.models.tags import ObjectTypes, TagTypes
-
-
-def add_types(engine: Engine, metadata: Metadata) -> None:
-    """
-    Tag every object according to its type:
-
-      INSERT INTO tagged_object (tag_id, object_id, object_type)
-      SELECT
-        tag.id AS tag_id,
-        slices.id AS object_id,
-        'chart' AS object_type
-      FROM slices
-      JOIN tag
-        ON tag.name = 'type:chart'
-      LEFT OUTER JOIN tagged_object
-        ON tagged_object.tag_id = tag.id
-        AND tagged_object.object_id = slices.id
-        AND tagged_object.object_type = 'chart'
-      WHERE tagged_object.tag_id IS NULL;
-
-      INSERT INTO tagged_object (tag_id, object_id, object_type)
-      SELECT
-        tag.id AS tag_id,
-        dashboards.id AS object_id,
-        'dashboard' AS object_type
-      FROM dashboards
-      JOIN tag
-      ON tag.name = 'type:dashboard'
-      LEFT OUTER JOIN tagged_object
-        ON tagged_object.tag_id = tag.id
-        AND tagged_object.object_id = dashboards.id
-        AND tagged_object.object_type = 'dashboard'
-      WHERE tagged_object.tag_id IS NULL;
+from sqlalchemy.sql import and_, func, join, literal, select
 
-      INSERT INTO tagged_object (tag_id, object_id, object_type)
-      SELECT
-        tag.id AS tag_id,
-        saved_query.id AS object_id,
-        'query' AS object_type
-      FROM saved_query
-      JOIN tag
-      ON tag.name = 'type:query';
-      LEFT OUTER JOIN tagged_object
-        ON tagged_object.tag_id = tag.id
-        AND tagged_object.object_id = saved_query.id
-        AND tagged_object.object_type = 'query'
-      WHERE tagged_object.tag_id IS NULL;
+from superset.tags.models import ObjectTypes, TagTypes
 
-    """
 
-    tag = metadata.tables["tag"]
-    tagged_object = metadata.tables["tagged_object"]
+def add_types_to_charts(
+    engine: Engine, metadata: MetaData, tag: Any, tagged_object: Any, columns: List[str]
+) -> None:
     slices = metadata.tables["slices"]
-    dashboards = metadata.tables["dashboards"]
-    saved_query = metadata.tables["saved_query"]
-    columns = ["tag_id", "object_id", "object_type"]
-
-    # add a tag for each object type
-    insert = tag.insert()
-    for type_ in ObjectTypes.__members__:
-        try:
-            engine.execute(insert, name=f"type:{type_}", type=TagTypes.type)
-        except IntegrityError:
-            pass  # already exists
 
     charts = (
         select(
@@ -111,21 +55,27 @@ def add_types(engine: Engine, metadata: Metadata) -> None:
     query = tagged_object.insert().from_select(columns, charts)
     engine.execute(query)
 
+
+def add_types_to_dashboards(
+    engine: Engine, metadata: MetaData, tag: Any, tagged_object: Any, columns: List[str]
+) -> None:
+    dashboard_table = metadata.tables["dashboards"]
+
     dashboards = (
         select(
             [
                 tag.c.id.label("tag_id"),
-                dashboards.c.id.label("object_id"),
+                dashboard_table.c.id.label("object_id"),
                 literal(ObjectTypes.dashboard.name).label("object_type"),
             ]
         )
         .select_from(
             join(
-                join(dashboards, tag, tag.c.name == "type:dashboard"),
+                join(dashboard_table, tag, tag.c.name == "type:dashboard"),
                 tagged_object,
                 and_(
                     tagged_object.c.tag_id == tag.c.id,
-                    tagged_object.c.object_id == dashboards.c.id,
+                    tagged_object.c.object_id == dashboard_table.c.id,
                     tagged_object.c.object_type == "dashboard",
                 ),
                 isouter=True,
@@ -137,6 +87,12 @@ def add_types(engine: Engine, metadata: Metadata) -> None:
     query = tagged_object.insert().from_select(columns, dashboards)
     engine.execute(query)
 
+
+def add_types_to_saved_queries(
+    engine: Engine, metadata: MetaData, tag: Any, tagged_object: Any, columns: List[str]
+) -> None:
+    saved_query = metadata.tables["saved_query"]
+
     saved_queries = (
         select(
             [
@@ -164,9 +120,41 @@ def add_types(engine: Engine, metadata: Metadata) -> None:
     engine.execute(query)
 
 
-def add_owners(engine: Engine, metadata: Metadata) -> None:
+def add_types_to_datasets(
+    engine: Engine, metadata: MetaData, tag: Any, tagged_object: Any, columns: List[str]
+) -> None:
+    tables = metadata.tables["tables"]
+
+    datasets = (
+        select(
+            [
+                tag.c.id.label("tag_id"),
+                tables.c.id.label("object_id"),
+                literal(ObjectTypes.dataset.name).label("object_type"),
+            ]
+        )
+        .select_from(
+            join(
+                join(tables, tag, tag.c.name == "type:dataset"),
+                tagged_object,
+                and_(
+                    tagged_object.c.tag_id == tag.c.id,
+                    tagged_object.c.object_id == tables.c.id,
+                    tagged_object.c.object_type == "dataset",
+                ),
+                isouter=True,
+                full=False,
+            )
+        )
+        .where(tagged_object.c.tag_id.is_(None))
+    )
+    query = tagged_object.insert().from_select(columns, datasets)
+    engine.execute(query)
+
+
+def add_types(engine: Engine, metadata: MetaData) -> None:
     """
-    Tag every object according to its owner:
+    Tag every object according to its type:
 
       INSERT INTO tagged_object (tag_id, object_id, object_type)
       SELECT
@@ -175,58 +163,80 @@ def add_owners(engine: Engine, metadata: Metadata) -> None:
         'chart' AS object_type
       FROM slices
       JOIN tag
-      ON tag.name = CONCAT('owner:', slices.created_by_fk)
+        ON tag.name = 'type:chart'
       LEFT OUTER JOIN tagged_object
         ON tagged_object.tag_id = tag.id
         AND tagged_object.object_id = slices.id
         AND tagged_object.object_type = 'chart'
       WHERE tagged_object.tag_id IS NULL;
 
+      INSERT INTO tagged_object (tag_id, object_id, object_type)
       SELECT
         tag.id AS tag_id,
         dashboards.id AS object_id,
         'dashboard' AS object_type
       FROM dashboards
       JOIN tag
-      ON tag.name = CONCAT('owner:', dashboards.created_by_fk)
+      ON tag.name = 'type:dashboard'
       LEFT OUTER JOIN tagged_object
         ON tagged_object.tag_id = tag.id
         AND tagged_object.object_id = dashboards.id
         AND tagged_object.object_type = 'dashboard'
       WHERE tagged_object.tag_id IS NULL;
 
+      INSERT INTO tagged_object (tag_id, object_id, object_type)
       SELECT
         tag.id AS tag_id,
         saved_query.id AS object_id,
         'query' AS object_type
       FROM saved_query
       JOIN tag
-      ON tag.name = CONCAT('owner:', saved_query.created_by_fk)
+      ON tag.name = 'type:query';
       LEFT OUTER JOIN tagged_object
         ON tagged_object.tag_id = tag.id
         AND tagged_object.object_id = saved_query.id
         AND tagged_object.object_type = 'query'
       WHERE tagged_object.tag_id IS NULL;
 
+      INSERT INTO tagged_object (tag_id, object_id, object_type)
+      SELECT
+        tag.id AS tag_id,
+        tables.id AS object_id,
+        'dataset' AS object_type
+      FROM tables
+      JOIN tag
+        ON tag.name = 'type:dataset'
+      LEFT OUTER JOIN tagged_object
+        ON tagged_object.tag_id = tag.id
+        AND tagged_object.object_id = tables.id
+        AND tagged_object.object_type = 'dataset'
+      WHERE tagged_object.tag_id IS NULL;
+
     """
 
     tag = metadata.tables["tag"]
     tagged_object = metadata.tables["tagged_object"]
-    users = metadata.tables["ab_user"]
-    slices = metadata.tables["slices"]
-    dashboards = metadata.tables["dashboards"]
-    saved_query = metadata.tables["saved_query"]
     columns = ["tag_id", "object_id", "object_type"]
 
-    # create a custom tag for each user
-    ids = select([users.c.id])
+    # add a tag for each object type
     insert = tag.insert()
-    for (id_,) in engine.execute(ids):
+    for type_ in ObjectTypes.__members__:
         try:
-            engine.execute(insert, name=f"owner:{id_}", type=TagTypes.owner)
+            engine.execute(insert, name=f"type:{type_}", type=TagTypes.type)
         except IntegrityError:
             pass  # already exists
 
+    add_types_to_charts(engine, metadata, tag, tagged_object, columns)
+    add_types_to_dashboards(engine, metadata, tag, tagged_object, columns)
+    add_types_to_saved_queries(engine, metadata, tag, tagged_object, columns)
+    add_types_to_datasets(engine, metadata, tag, tagged_object, columns)
+
+
+def add_owners_to_charts(
+    engine: Engine, metadata: MetaData, tag: Any, tagged_object: Any, columns: List[str]
+) -> None:
+    slices = metadata.tables["slices"]
+
     charts = (
         select(
             [
@@ -240,7 +250,7 @@ def add_owners(engine: Engine, metadata: Metadata) -> None:
                 join(
                     slices,
                     tag,
-                    tag.c.name == functions.concat("owner:", slices.c.created_by_fk),
+                    tag.c.name == "owner:" + slices.c.created_by_fk,
                 ),
                 tagged_object,
                 and_(
@@ -257,26 +267,31 @@ def add_owners(engine: Engine, metadata: Metadata) -> None:
     query = tagged_object.insert().from_select(columns, charts)
     engine.execute(query)
 
+
+def add_owners_to_dashboards(
+    engine: Engine, metadata: MetaData, tag: Any, tagged_object: Any, columns: List[str]
+) -> None:
+    dashboard_table = metadata.tables["dashboards"]
+
     dashboards = (
         select(
             [
                 tag.c.id.label("tag_id"),
-                dashboards.c.id.label("object_id"),
+                dashboard_table.c.id.label("object_id"),
                 literal(ObjectTypes.dashboard.name).label("object_type"),
             ]
         )
         .select_from(
             join(
                 join(
-                    dashboards,
+                    dashboard_table,
                     tag,
-                    tag.c.name
-                    == functions.concat("owner:", dashboards.c.created_by_fk),
+                    tag.c.name == "owner:" + dashboard_table.c.created_by_fk,
                 ),
                 tagged_object,
                 and_(
                     tagged_object.c.tag_id == tag.c.id,
-                    tagged_object.c.object_id == dashboards.c.id,
+                    tagged_object.c.object_id == dashboard_table.c.id,
                     tagged_object.c.object_type == "dashboard",
                 ),
                 isouter=True,
@@ -288,6 +303,12 @@ def add_owners(engine: Engine, metadata: Metadata) -> None:
     query = tagged_object.insert().from_select(columns, dashboards)
     engine.execute(query)
 
+
+def add_owners_to_saved_queries(
+    engine: Engine, metadata: MetaData, tag: Any, tagged_object: Any, columns: List[str]
+) -> None:
+    saved_query = metadata.tables["saved_query"]
+
     saved_queries = (
         select(
             [
@@ -301,8 +322,7 @@ def add_owners(engine: Engine, metadata: Metadata) -> None:
                 join(
                     saved_query,
                     tag,
-                    tag.c.name
-                    == functions.concat("owner:", saved_query.c.created_by_fk),
+                    tag.c.name == "owner:" + saved_query.c.created_by_fk,
                 ),
                 tagged_object,
                 and_(
@@ -320,7 +340,122 @@ def add_owners(engine: Engine, metadata: Metadata) -> None:
     engine.execute(query)
 
 
-def add_favorites(engine: Engine, metadata: Metadata) -> None:
+def add_owners_to_datasets(
+    engine: Engine, metadata: MetaData, tag: Any, tagged_object: Any, columns: List[str]
+) -> None:
+    tables = metadata.tables["tables"]
+
+    datasets = (
+        select(
+            [
+                tag.c.id.label("tag_id"),
+                tables.c.id.label("object_id"),
+                literal(ObjectTypes.dataset.name).label("object_type"),
+            ]
+        )
+        .select_from(
+            join(
+                join(
+                    tables,
+                    tag,
+                    tag.c.name == "owner:" + tables.c.created_by_fk,
+                ),
+                tagged_object,
+                and_(
+                    tagged_object.c.tag_id == tag.c.id,
+                    tagged_object.c.object_id == tables.c.id,
+                    tagged_object.c.object_type == "dataset",
+                ),
+                isouter=True,
+                full=False,
+            )
+        )
+        .where(tagged_object.c.tag_id.is_(None))
+    )
+    query = tagged_object.insert().from_select(columns, datasets)
+    engine.execute(query)
+
+
+def add_owners(engine: Engine, metadata: MetaData) -> None:
+    """
+    Tag every object according to its owner:
+
+      INSERT INTO tagged_object (tag_id, object_id, object_type)
+      SELECT
+        tag.id AS tag_id,
+        slices.id AS object_id,
+        'chart' AS object_type
+      FROM slices
+      JOIN tag
+      ON tag.name = CONCAT('owner:', slices.created_by_fk)
+      LEFT OUTER JOIN tagged_object
+        ON tagged_object.tag_id = tag.id
+        AND tagged_object.object_id = slices.id
+        AND tagged_object.object_type = 'chart'
+      WHERE tagged_object.tag_id IS NULL;
+
+      SELECT
+        tag.id AS tag_id,
+        dashboards.id AS object_id,
+        'dashboard' AS object_type
+      FROM dashboards
+      JOIN tag
+      ON tag.name = CONCAT('owner:', dashboards.created_by_fk)
+      LEFT OUTER JOIN tagged_object
+        ON tagged_object.tag_id = tag.id
+        AND tagged_object.object_id = dashboards.id
+        AND tagged_object.object_type = 'dashboard'
+      WHERE tagged_object.tag_id IS NULL;
+
+      SELECT
+        tag.id AS tag_id,
+        saved_query.id AS object_id,
+        'query' AS object_type
+      FROM saved_query
+      JOIN tag
+      ON tag.name = CONCAT('owner:', saved_query.created_by_fk)
+      LEFT OUTER JOIN tagged_object
+        ON tagged_object.tag_id = tag.id
+        AND tagged_object.object_id = saved_query.id
+        AND tagged_object.object_type = 'query'
+      WHERE tagged_object.tag_id IS NULL;
+
+      SELECT
+        tag.id AS tag_id,
+        tables.id AS object_id,
+        'dataset' AS object_type
+      FROM tables
+      JOIN tag
+      ON tag.name = CONCAT('owner:', tables.created_by_fk)
+      LEFT OUTER JOIN tagged_object
+        ON tagged_object.tag_id = tag.id
+        AND tagged_object.object_id = tables.id
+        AND tagged_object.object_type = 'dataset'
+      WHERE tagged_object.tag_id IS NULL;
+
+    """
+
+    tag = metadata.tables["tag"]
+    tagged_object = metadata.tables["tagged_object"]
+    users = metadata.tables["ab_user"]
+    columns = ["tag_id", "object_id", "object_type"]
+
+    # create a custom tag for each user
+    ids = select([users.c.id])
+    insert = tag.insert()
+    for (id_,) in engine.execute(ids):
+        try:
+            engine.execute(insert, name=f"owner:{id_}", type=TagTypes.owner)
+        except IntegrityError:
+            pass  # already exists
+
+    add_owners_to_charts(engine, metadata, tag, tagged_object, columns)
+    add_owners_to_dashboards(engine, metadata, tag, tagged_object, columns)
+    add_owners_to_saved_queries(engine, metadata, tag, tagged_object, columns)
+    add_owners_to_datasets(engine, metadata, tag, tagged_object, columns)
+
+
+def add_favorites(engine: Engine, metadata: MetaData) -> None:
     """
     Tag every object that was favorited:
 
@@ -368,7 +503,7 @@ def add_favorites(engine: Engine, metadata: Metadata) -> None:
                 join(
                     favstar,
                     tag,
-                    tag.c.name == functions.concat("favorited_by:", favstar.c.user_id),
+                    tag.c.name == "favorited_by:" + favstar.c.user_id,
                 ),
                 tagged_object,
                 and_(
diff --git a/superset/initialization/__init__.py b/superset/initialization/__init__.py
index 12d3692ac9..598cf94e05 100644
--- a/superset/initialization/__init__.py
+++ b/superset/initialization/__init__.py
@@ -49,6 +49,7 @@ from superset.extensions import (
 )
 from superset.security import SupersetSecurityManager
 from superset.superset_typing import FlaskResponse
+from superset.tags.core import register_sqla_event_listeners
 from superset.utils.core import pessimistic_connection_handling
 from superset.utils.log import DBEventLogger, get_event_logger_from_cfg_value
 
@@ -426,6 +427,9 @@ class SupersetAppInitializer:  # pylint: disable=too-many-public-methods
         if flask_app_mutator:
             flask_app_mutator(self.superset_app)
 
+        if feature_flag_manager.is_feature_enabled("TAGGING_SYSTEM"):
+            register_sqla_event_listeners()
+
         self.init_views()
 
     def check_secret_key(self) -> None:
diff --git a/superset/migrations/versions/2018-07-26_11-10_c82ee8a39623_add_implicit_tags.py b/superset/migrations/versions/2018-07-26_11-10_c82ee8a39623_add_implicit_tags.py
index 8a1a5f989e..0179ba7d03 100644
--- a/superset/migrations/versions/2018-07-26_11-10_c82ee8a39623_add_implicit_tags.py
+++ b/superset/migrations/versions/2018-07-26_11-10_c82ee8a39623_add_implicit_tags.py
@@ -33,7 +33,7 @@ from flask_appbuilder.models.mixins import AuditMixin
 from sqlalchemy import Column, DateTime, Enum, ForeignKey, Integer, String
 from sqlalchemy.ext.declarative import declarative_base, declared_attr
 
-from superset.models.tags import ObjectTypes, TagTypes
+from superset.tags.models import ObjectTypes, TagTypes
 from superset.utils.core import get_user_id
 
 Base = declarative_base()
diff --git a/superset/models/core.py b/superset/models/core.py
index 512adfebc6..a8ab4df6b0 100755
--- a/superset/models/core.py
+++ b/superset/models/core.py
@@ -53,13 +53,12 @@ from sqlalchemy.pool import NullPool
 from sqlalchemy.schema import UniqueConstraint
 from sqlalchemy.sql import expression, Select
 
-from superset import app, db_engine_specs, is_feature_enabled
+from superset import app, db_engine_specs
 from superset.constants import PASSWORD_MASK
 from superset.databases.utils import make_url_safe
 from superset.db_engine_specs.base import MetricType, TimeGrain
 from superset.extensions import cache_manager, encrypted_field_factory, security_manager
 from superset.models.helpers import AuditMixinNullable, ImportExportMixin
-from superset.models.tags import FavStarUpdater
 from superset.result_set import SupersetResultSet
 from superset.utils import cache as cache_util, core as utils
 from superset.utils.core import get_username
@@ -809,9 +808,3 @@ class FavStar(Model):  # pylint: disable=too-few-public-methods
     class_name = Column(String(50))
     obj_id = Column(Integer)
     dttm = Column(DateTime, default=datetime.utcnow)
-
-
-# events for updating tags
-if is_feature_enabled("TAGGING_SYSTEM"):
-    sqla.event.listen(FavStar, "after_insert", FavStarUpdater.after_insert)
-    sqla.event.listen(FavStar, "after_delete", FavStarUpdater.after_delete)
diff --git a/superset/models/dashboard.py b/superset/models/dashboard.py
index b8dbf37e7d..57567e6164 100644
--- a/superset/models/dashboard.py
+++ b/superset/models/dashboard.py
@@ -53,7 +53,6 @@ from superset.extensions import cache_manager
 from superset.models.filter_set import FilterSet
 from superset.models.helpers import AuditMixinNullable, ImportExportMixin
 from superset.models.slice import Slice
-from superset.models.tags import DashboardUpdater
 from superset.models.user_attributes import UserAttribute
 from superset.tasks.thumbnails import cache_dashboard_thumbnail
 from superset.utils import core as utils
@@ -454,12 +453,6 @@ def id_or_slug_filter(id_or_slug: Union[int, str]) -> BinaryExpression:
 
 OnDashboardChange = Callable[[Mapper, Connection, Dashboard], Any]
 
-# events for updating tags
-if is_feature_enabled("TAGGING_SYSTEM"):
-    sqla.event.listen(Dashboard, "after_insert", DashboardUpdater.after_insert)
-    sqla.event.listen(Dashboard, "after_update", DashboardUpdater.after_update)
-    sqla.event.listen(Dashboard, "after_delete", DashboardUpdater.after_delete)
-
 if is_feature_enabled("THUMBNAILS_SQLA_LISTENERS"):
     update_thumbnail: OnDashboardChange = lambda _, __, dash: dash.update_thumbnail()
     sqla.event.listen(Dashboard, "after_insert", update_thumbnail)
diff --git a/superset/models/slice.py b/superset/models/slice.py
index de0f3df596..d644e7b747 100644
--- a/superset/models/slice.py
+++ b/superset/models/slice.py
@@ -42,7 +42,6 @@ from sqlalchemy.orm.mapper import Mapper
 from superset import db, is_feature_enabled, security_manager
 from superset.legacy import update_time_range
 from superset.models.helpers import AuditMixinNullable, ImportExportMixin
-from superset.models.tags import ChartUpdater
 from superset.tasks.thumbnails import cache_chart_thumbnail
 from superset.utils import core as utils
 from superset.utils.hashing import md5_sha_from_str
@@ -367,13 +366,6 @@ def event_after_chart_changed(
 sqla.event.listen(Slice, "before_insert", set_related_perm)
 sqla.event.listen(Slice, "before_update", set_related_perm)
 
-# events for updating tags
-if is_feature_enabled("TAGGING_SYSTEM"):
-    sqla.event.listen(Slice, "after_insert", ChartUpdater.after_insert)
-    sqla.event.listen(Slice, "after_update", ChartUpdater.after_update)
-    sqla.event.listen(Slice, "after_delete", ChartUpdater.after_delete)
-
-# events for updating tags
 if is_feature_enabled("THUMBNAILS_SQLA_LISTENERS"):
     sqla.event.listen(Slice, "after_insert", event_after_chart_changed)
     sqla.event.listen(Slice, "after_update", event_after_chart_changed)
diff --git a/superset/models/sql_lab.py b/superset/models/sql_lab.py
index d12af49081..408bc708df 100644
--- a/superset/models/sql_lab.py
+++ b/superset/models/sql_lab.py
@@ -49,7 +49,6 @@ from superset.models.helpers import (
     ExtraJSONMixin,
     ImportExportMixin,
 )
-from superset.models.tags import QueryUpdater
 from superset.sql_parse import CtasMethod, ParsedQuery, Table
 from superset.sqllab.limiting_factor import LimitingFactor
 from superset.superset_typing import ResultSetColumnType
@@ -509,9 +508,3 @@ class TableSchema(Model, AuditMixinNullable, ExtraJSONMixin):
             "description": description,
             "expanded": self.expanded,
         }
-
-
-# events for updating tags
-sqla.event.listen(SavedQuery, "after_insert", QueryUpdater.after_insert)
-sqla.event.listen(SavedQuery, "after_update", QueryUpdater.after_update)
-sqla.event.listen(SavedQuery, "after_delete", QueryUpdater.after_delete)
diff --git a/superset/tags/core.py b/superset/tags/core.py
new file mode 100644
index 0000000000..f1f832c7a5
--- /dev/null
+++ b/superset/tags/core.py
@@ -0,0 +1,88 @@
+# 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.
+
+
+def register_sqla_event_listeners() -> None:
+    import sqlalchemy as sqla
+
+    from superset.connectors.sqla.models import SqlaTable
+    from superset.models.core import FavStar
+    from superset.models.dashboard import Dashboard
+    from superset.models.slice import Slice
+    from superset.models.sql_lab import SavedQuery
+    from superset.tags.models import (
+        ChartUpdater,
+        DashboardUpdater,
+        DatasetUpdater,
+        FavStarUpdater,
+        QueryUpdater,
+    )
+
+    sqla.event.listen(SqlaTable, "after_insert", DatasetUpdater.after_insert)
+    sqla.event.listen(SqlaTable, "after_update", DatasetUpdater.after_update)
+    sqla.event.listen(SqlaTable, "after_delete", DatasetUpdater.after_delete)
+
+    sqla.event.listen(Slice, "after_insert", ChartUpdater.after_insert)
+    sqla.event.listen(Slice, "after_update", ChartUpdater.after_update)
+    sqla.event.listen(Slice, "after_delete", ChartUpdater.after_delete)
+
+    sqla.event.listen(Dashboard, "after_insert", DashboardUpdater.after_insert)
+    sqla.event.listen(Dashboard, "after_update", DashboardUpdater.after_update)
+    sqla.event.listen(Dashboard, "after_delete", DashboardUpdater.after_delete)
+
+    sqla.event.listen(FavStar, "after_insert", FavStarUpdater.after_insert)
+    sqla.event.listen(FavStar, "after_delete", FavStarUpdater.after_delete)
+
+    sqla.event.listen(SavedQuery, "after_insert", QueryUpdater.after_insert)
+    sqla.event.listen(SavedQuery, "after_update", QueryUpdater.after_update)
+    sqla.event.listen(SavedQuery, "after_delete", QueryUpdater.after_delete)
+
+
+def clear_sqla_event_listeners() -> None:
+    import sqlalchemy as sqla
+
+    from superset.connectors.sqla.models import SqlaTable
+    from superset.models.core import FavStar
+    from superset.models.dashboard import Dashboard
+    from superset.models.slice import Slice
+    from superset.models.sql_lab import SavedQuery
+    from superset.tags.models import (
+        ChartUpdater,
+        DashboardUpdater,
+        DatasetUpdater,
+        FavStarUpdater,
+        QueryUpdater,
+    )
+
+    sqla.event.remove(SqlaTable, "after_insert", DatasetUpdater.after_insert)
+    sqla.event.remove(SqlaTable, "after_update", DatasetUpdater.after_update)
+    sqla.event.remove(SqlaTable, "after_delete", DatasetUpdater.after_delete)
+
+    sqla.event.remove(Slice, "after_insert", ChartUpdater.after_insert)
+    sqla.event.remove(Slice, "after_update", ChartUpdater.after_update)
+    sqla.event.remove(Slice, "after_delete", ChartUpdater.after_delete)
+
+    sqla.event.remove(Dashboard, "after_insert", DashboardUpdater.after_insert)
+    sqla.event.remove(Dashboard, "after_update", DashboardUpdater.after_update)
+    sqla.event.remove(Dashboard, "after_delete", DashboardUpdater.after_delete)
+
+    sqla.event.remove(FavStar, "after_insert", FavStarUpdater.after_insert)
+    sqla.event.remove(FavStar, "after_delete", FavStarUpdater.after_delete)
+
+    sqla.event.remove(SavedQuery, "after_insert", QueryUpdater.after_insert)
+    sqla.event.remove(SavedQuery, "after_update", QueryUpdater.after_update)
+    sqla.event.remove(SavedQuery, "after_delete", QueryUpdater.after_delete)
diff --git a/superset/models/tags.py b/superset/tags/models.py
similarity index 84%
rename from superset/models/tags.py
rename to superset/tags/models.py
index 528206e672..89505146e2 100644
--- a/superset/models/tags.py
+++ b/superset/tags/models.py
@@ -14,7 +14,13 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-from __future__ import absolute_import, division, print_function, unicode_literals
+from __future__ import (
+    absolute_import,
+    annotations,
+    division,
+    print_function,
+    unicode_literals,
+)
 
 import enum
 from typing import List, Optional, TYPE_CHECKING, Union
@@ -28,6 +34,7 @@ from sqlalchemy.orm.mapper import Mapper
 from superset.models.helpers import AuditMixinNullable
 
 if TYPE_CHECKING:
+    from superset.connectors.sqla.models import SqlaTable
     from superset.models.core import FavStar
     from superset.models.dashboard import Dashboard
     from superset.models.slice import Slice
@@ -41,7 +48,7 @@ class TagTypes(enum.Enum):
     """
     Types for tags.
 
-    Objects (queries, charts and dashboards) will have with implicit tags based
+    Objects (queries, charts, dashboards, and datasets) will have with implicit tags based
     on metadata: types, owners and who favorited them. This way, user "alice"
     can find all their objects by querying for the tag `owner:alice`.
     """
@@ -64,11 +71,12 @@ class ObjectTypes(enum.Enum):
     query = 1
     chart = 2
     dashboard = 3
+    dataset = 4
 
 
 class Tag(Model, AuditMixinNullable):
 
-    """A tag attached to an object (query, chart or dashboard)."""
+    """A tag attached to an object (query, chart, dashboard, or dataset)."""
 
     __tablename__ = "tag"
     id = Column(Integer, primary_key=True)
@@ -103,6 +111,7 @@ def get_object_type(class_name: str) -> ObjectTypes:
         "slice": ObjectTypes.chart,
         "dashboard": ObjectTypes.dashboard,
         "query": ObjectTypes.query,
+        "dataset": ObjectTypes.dataset,
     }
     try:
         return mapping[class_name.lower()]
@@ -116,13 +125,15 @@ class ObjectUpdater:
 
     @classmethod
     def get_owners_ids(
-        cls, target: Union["Dashboard", "FavStar", "Slice"]
+        cls, target: Union[Dashboard, FavStar, Slice, Query, SqlaTable]
     ) -> List[int]:
         raise NotImplementedError("Subclass should implement `get_owners_ids`")
 
     @classmethod
     def _add_owners(
-        cls, session: Session, target: Union["Dashboard", "FavStar", "Slice"]
+        cls,
+        session: Session,
+        target: Union[Dashboard, FavStar, Slice, Query, SqlaTable],
     ) -> None:
         for owner_id in cls.get_owners_ids(target):
             name = "owner:{0}".format(owner_id)
@@ -137,7 +148,7 @@ class ObjectUpdater:
         cls,
         _mapper: Mapper,
         connection: Connection,
-        target: Union["Dashboard", "FavStar", "Slice"],
+        target: Union[Dashboard, FavStar, Slice, Query, SqlaTable],
     ) -> None:
         session = Session(bind=connection)
 
@@ -158,7 +169,7 @@ class ObjectUpdater:
         cls,
         _mapper: Mapper,
         connection: Connection,
-        target: Union["Dashboard", "FavStar", "Slice"],
+        target: Union[Dashboard, FavStar, Slice, Query, SqlaTable],
     ) -> None:
         session = Session(bind=connection)
 
@@ -187,7 +198,7 @@ class ObjectUpdater:
         cls,
         _mapper: Mapper,
         connection: Connection,
-        target: Union["Dashboard", "FavStar", "Slice"],
+        target: Union[Dashboard, FavStar, Slice, Query, SqlaTable],
     ) -> None:
         session = Session(bind=connection)
 
@@ -205,7 +216,7 @@ class ChartUpdater(ObjectUpdater):
     object_type = "chart"
 
     @classmethod
-    def get_owners_ids(cls, target: "Slice") -> List[int]:
+    def get_owners_ids(cls, target: Slice) -> List[int]:
         return [owner.id for owner in target.owners]
 
 
@@ -214,7 +225,7 @@ class DashboardUpdater(ObjectUpdater):
     object_type = "dashboard"
 
     @classmethod
-    def get_owners_ids(cls, target: "Dashboard") -> List[int]:
+    def get_owners_ids(cls, target: Dashboard) -> List[int]:
         return [owner.id for owner in target.owners]
 
 
@@ -223,14 +234,23 @@ class QueryUpdater(ObjectUpdater):
     object_type = "query"
 
     @classmethod
-    def get_owners_ids(cls, target: "Query") -> List[int]:
+    def get_owners_ids(cls, target: Query) -> List[int]:
         return [target.user_id]
 
 
+class DatasetUpdater(ObjectUpdater):
+
+    object_type = "dataset"
+
+    @classmethod
+    def get_owners_ids(cls, target: SqlaTable) -> List[int]:
+        return [owner.id for owner in target.owners]
+
+
 class FavStarUpdater:
     @classmethod
     def after_insert(
-        cls, _mapper: Mapper, connection: Connection, target: "FavStar"
+        cls, _mapper: Mapper, connection: Connection, target: FavStar
     ) -> None:
         session = Session(bind=connection)
         name = "favorited_by:{0}".format(target.user_id)
@@ -246,7 +266,7 @@ class FavStarUpdater:
 
     @classmethod
     def after_delete(
-        cls, _mapper: Mapper, connection: Connection, target: "FavStar"
+        cls, _mapper: Mapper, connection: Connection, target: FavStar
     ) -> None:
         session = Session(bind=connection)
         name = "favorited_by:{0}".format(target.user_id)
diff --git a/superset/tasks/cache.py b/superset/tasks/cache.py
index 137ec068e8..0bda1e7084 100644
--- a/superset/tasks/cache.py
+++ b/superset/tasks/cache.py
@@ -28,7 +28,7 @@ from superset.extensions import celery_app
 from superset.models.core import Log
 from superset.models.dashboard import Dashboard
 from superset.models.slice import Slice
-from superset.models.tags import Tag, TaggedObject
+from superset.tags.models import Tag, TaggedObject
 from superset.utils.date_parser import parse_human_datetime
 from superset.utils.machine_auth import MachineAuthProvider
 
diff --git a/superset/utils/url_map_converters.py b/superset/utils/url_map_converters.py
index c6a14f3fd8..c5eaf3b359 100644
--- a/superset/utils/url_map_converters.py
+++ b/superset/utils/url_map_converters.py
@@ -18,7 +18,7 @@ from typing import Any, List
 
 from werkzeug.routing import BaseConverter, Map
 
-from superset.models.tags import ObjectTypes
+from superset.tags.models import ObjectTypes
 
 
 class RegexConverter(BaseConverter):
diff --git a/superset/views/tags.py b/superset/views/tags.py
index 8ab2798f5d..985d26179f 100644
--- a/superset/views/tags.py
+++ b/superset/views/tags.py
@@ -28,12 +28,13 @@ from sqlalchemy import and_, func
 from werkzeug.exceptions import NotFound
 
 from superset import db, is_feature_enabled, utils
+from superset.connectors.sqla.models import SqlaTable
 from superset.jinja_context import ExtraCache
 from superset.models.dashboard import Dashboard
 from superset.models.slice import Slice
 from superset.models.sql_lab import SavedQuery
-from superset.models.tags import ObjectTypes, Tag, TaggedObject, TagTypes
 from superset.superset_typing import FlaskResponse
+from superset.tags.models import ObjectTypes, Tag, TaggedObject, TagTypes
 
 from .base import BaseSupersetView, json_success
 
@@ -238,4 +239,31 @@ class TagView(BaseSupersetView):
                 for obj in saved_queries
             )
 
+        # datasets
+        if not types or "dataset" in types:
+            datasets = (
+                db.session.query(SqlaTable)
+                .join(
+                    TaggedObject,
+                    and_(
+                        TaggedObject.object_id == SqlaTable.id,
+                        TaggedObject.object_type == ObjectTypes.dataset,
+                    ),
+                )
+                .join(Tag, TaggedObject.tag_id == Tag.id)
+                .filter(Tag.name.in_(tags))
+            )
+            results.extend(
+                {
+                    "id": obj.id,
+                    "type": ObjectTypes.dataset.name,
+                    "name": obj.table_name,
+                    "url": obj.sql_url(),
+                    "changed_on": obj.changed_on,
+                    "created_by": obj.created_by_fk,
+                    "creator": obj.creator(),
+                }
+                for obj in datasets
+            )
+
         return json_success(json.dumps(results, default=utils.core.json_int_dttm_ser))
diff --git a/superset/utils/url_map_converters.py b/tests/integration_tests/fixtures/tags.py
similarity index 57%
copy from superset/utils/url_map_converters.py
copy to tests/integration_tests/fixtures/tags.py
index c6a14f3fd8..57fd4ec719 100644
--- a/superset/utils/url_map_converters.py
+++ b/tests/integration_tests/fixtures/tags.py
@@ -14,24 +14,20 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-from typing import Any, List
 
-from werkzeug.routing import BaseConverter, Map
+import pytest
 
-from superset.models.tags import ObjectTypes
+from superset.tags.core import clear_sqla_event_listeners, register_sqla_event_listeners
+from tests.integration_tests.test_app import app
 
 
-class RegexConverter(BaseConverter):
-    def __init__(self, url_map: Map, *items: List[str]) -> None:
-        super().__init__(url_map)  # type: ignore
-        self.regex = items[0]
-
-
-class ObjectTypeConverter(BaseConverter):
-    """Validate that object_type is indeed an object type."""
-
-    def to_python(self, value: str) -> Any:
-        return ObjectTypes[value]
-
-    def to_url(self, value: Any) -> str:
-        return value.name
+@pytest.fixture
+def with_tagging_system_feature():
+    with app.app_context():
+        is_enabled = app.config["DEFAULT_FEATURE_FLAGS"]["TAGGING_SYSTEM"]
+        if not is_enabled:
+            app.config["DEFAULT_FEATURE_FLAGS"]["TAGGING_SYSTEM"] = True
+            register_sqla_event_listeners()
+            yield
+            app.config["DEFAULT_FEATURE_FLAGS"]["TAGGING_SYSTEM"] = False
+            clear_sqla_event_listeners()
diff --git a/tests/integration_tests/strategy_tests.py b/tests/integration_tests/strategy_tests.py
index f31489bb04..e54ae865e3 100644
--- a/tests/integration_tests/strategy_tests.py
+++ b/tests/integration_tests/strategy_tests.py
@@ -35,7 +35,7 @@ from superset.utils.database import get_example_database
 from superset import db
 
 from superset.models.core import Log
-from superset.models.tags import get_tag, ObjectTypes, TaggedObject, TagTypes
+from superset.tags.models import get_tag, ObjectTypes, TaggedObject, TagTypes
 from superset.tasks.cache import (
     DashboardTagsStrategy,
     TopNDashboardsStrategy,
diff --git a/tests/integration_tests/tagging_tests.py b/tests/integration_tests/tagging_tests.py
index 9ae8764d40..4ee10041d2 100644
--- a/tests/integration_tests/tagging_tests.py
+++ b/tests/integration_tests/tagging_tests.py
@@ -15,11 +15,33 @@
 # specific language governing permissions and limitations
 # under the License.
 
+from unittest import mock
+
+import pytest
+
+from superset.connectors.sqla.models import SqlaTable
+from superset.extensions import db
+from superset.models.core import FavStar
+from superset.models.dashboard import Dashboard
+from superset.models.slice import Slice
+from superset.models.sql_lab import SavedQuery
+from superset.tags.models import TaggedObject
+from superset.utils.core import DatasourceType
+from superset.utils.database import get_main_database
 from tests.integration_tests.base_tests import SupersetTestCase
 from tests.integration_tests.conftest import with_feature_flags
+from tests.integration_tests.fixtures.tags import with_tagging_system_feature
 
 
 class TestTagging(SupersetTestCase):
+    def query_tagged_object_table(self):
+        query = db.session.query(TaggedObject).all()
+        return query
+
+    def clear_tagged_object_table(self):
+        db.session.query(TaggedObject).delete()
+        db.session.commit()
+
     @with_feature_flags(TAGGING_SYSTEM=False)
     def test_tag_view_disabled(self):
         self.login("admin")
@@ -31,3 +53,257 @@ class TestTagging(SupersetTestCase):
         self.login("admin")
         response = self.client.get("/tagview/tags/suggestions/")
         self.assertNotEqual(404, response.status_code)
+
+    @pytest.mark.usefixtures("with_tagging_system_feature")
+    def test_dataset_tagging(self):
+        """
+        Test to make sure that when a new dataset is created,
+        a corresponding tag in the tagged_objects table
+        is created
+        """
+
+        # Remove all existing rows in the tagged_object table
+        self.clear_tagged_object_table()
+
+        # Test to make sure nothing is in the tagged_object table
+        self.assertEqual([], self.query_tagged_object_table())
+
+        # Create a dataset and add it to the db
+        test_dataset = SqlaTable(
+            table_name="foo",
+            schema=None,
+            owners=[],
+            database=get_main_database(),
+            sql=None,
+            extra='{"certification": 1}',
+        )
+        db.session.add(test_dataset)
+        db.session.commit()
+
+        # Test to make sure that a dataset tag was added to the tagged_object table
+        tags = self.query_tagged_object_table()
+        self.assertEqual(1, len(tags))
+        self.assertEqual("ObjectTypes.dataset", str(tags[0].object_type))
+        self.assertEqual(test_dataset.id, tags[0].object_id)
+
+        # Cleanup the db
+        db.session.delete(test_dataset)
+        db.session.commit()
+
+        # Test to make sure the tag is deleted when the associated object is deleted
+        self.assertEqual([], self.query_tagged_object_table())
+
+    @pytest.mark.usefixtures("with_tagging_system_feature")
+    def test_chart_tagging(self):
+        """
+        Test to make sure that when a new chart is created,
+        a corresponding tag in the tagged_objects table
+        is created
+        """
+
+        # Remove all existing rows in the tagged_object table
+        self.clear_tagged_object_table()
+
+        # Test to make sure nothing is in the tagged_object table
+        self.assertEqual([], self.query_tagged_object_table())
+
+        # Create a chart and add it to the db
+        test_chart = Slice(
+            slice_name="test_chart",
+            datasource_type=DatasourceType.TABLE,
+            viz_type="bubble",
+            datasource_id=1,
+            id=1,
+        )
+        db.session.add(test_chart)
+        db.session.commit()
+
+        # Test to make sure that a chart tag was added to the tagged_object table
+        tags = self.query_tagged_object_table()
+        self.assertEqual(1, len(tags))
+        self.assertEqual("ObjectTypes.chart", str(tags[0].object_type))
+        self.assertEqual(test_chart.id, tags[0].object_id)
+
+        # Cleanup the db
+        db.session.delete(test_chart)
+        db.session.commit()
+
+        # Test to make sure the tag is deleted when the associated object is deleted
+        self.assertEqual([], self.query_tagged_object_table())
+
+    @pytest.mark.usefixtures("with_tagging_system_feature")
+    def test_dashboard_tagging(self):
+        """
+        Test to make sure that when a new dashboard is created,
+        a corresponding tag in the tagged_objects table
+        is created
+        """
+
+        # Remove all existing rows in the tagged_object table
+        self.clear_tagged_object_table()
+
+        # Test to make sure nothing is in the tagged_object table
+        self.assertEqual([], self.query_tagged_object_table())
+
+        # Create a dashboard and add it to the db
+        test_dashboard = Dashboard()
+        test_dashboard.dashboard_title = "test_dashboard"
+        test_dashboard.slug = "test_slug"
+        test_dashboard.slices = []
+        test_dashboard.published = True
+
+        db.session.add(test_dashboard)
+        db.session.commit()
+
+        # Test to make sure that a dashboard tag was added to the tagged_object table
+        tags = self.query_tagged_object_table()
+        self.assertEqual(1, len(tags))
+        self.assertEqual("ObjectTypes.dashboard", str(tags[0].object_type))
+        self.assertEqual(test_dashboard.id, tags[0].object_id)
+
+        # Cleanup the db
+        db.session.delete(test_dashboard)
+        db.session.commit()
+
+        # Test to make sure the tag is deleted when the associated object is deleted
+        self.assertEqual([], self.query_tagged_object_table())
+
+    @pytest.mark.usefixtures("with_tagging_system_feature")
+    def test_saved_query_tagging(self):
+        """
+        Test to make sure that when a new saved query is
+        created, a corresponding tag in the tagged_objects
+        table is created
+        """
+
+        # Remove all existing rows in the tagged_object table
+        self.clear_tagged_object_table()
+
+        # Test to make sure nothing is in the tagged_object table
+        self.assertEqual([], self.query_tagged_object_table())
+
+        # Create a saved query and add it to the db
+        test_saved_query = SavedQuery(id=1, label="test saved query")
+        db.session.add(test_saved_query)
+        db.session.commit()
+
+        # Test to make sure that a saved query tag was added to the tagged_object table
+        tags = self.query_tagged_object_table()
+
+        self.assertEqual(2, len(tags))
+
+        self.assertEqual("ObjectTypes.query", str(tags[0].object_type))
+        self.assertEqual("owner:None", str(tags[0].tag.name))
+        self.assertEqual("TagTypes.owner", str(tags[0].tag.type))
+        self.assertEqual(test_saved_query.id, tags[0].object_id)
+
+        self.assertEqual("ObjectTypes.query", str(tags[1].object_type))
+        self.assertEqual("type:query", str(tags[1].tag.name))
+        self.assertEqual("TagTypes.type", str(tags[1].tag.type))
+        self.assertEqual(test_saved_query.id, tags[1].object_id)
+
+        # Cleanup the db
+        db.session.delete(test_saved_query)
+        db.session.commit()
+
+        # Test to make sure the tag is deleted when the associated object is deleted
+        self.assertEqual([], self.query_tagged_object_table())
+
+    @pytest.mark.usefixtures("with_tagging_system_feature")
+    def test_favorite_tagging(self):
+        """
+        Test to make sure that when a new favorite object is
+        created, a corresponding tag in the tagged_objects
+        table is created
+        """
+
+        # Remove all existing rows in the tagged_object table
+        self.clear_tagged_object_table()
+
+        # Test to make sure nothing is in the tagged_object table
+        self.assertEqual([], self.query_tagged_object_table())
+
+        # Create a favorited object and add it to the db
+        test_saved_query = FavStar(user_id=1, class_name="slice", obj_id=1)
+        db.session.add(test_saved_query)
+        db.session.commit()
+
+        # Test to make sure that a favorited object tag was added to the tagged_object table
+        tags = self.query_tagged_object_table()
+        self.assertEqual(1, len(tags))
+        self.assertEqual("ObjectTypes.chart", str(tags[0].object_type))
+        self.assertEqual(test_saved_query.obj_id, tags[0].object_id)
+
+        # Cleanup the db
+        db.session.delete(test_saved_query)
+        db.session.commit()
+
+        # Test to make sure the tag is deleted when the associated object is deleted
+        self.assertEqual([], self.query_tagged_object_table())
+
+    @with_feature_flags(TAGGING_SYSTEM=False)
+    def test_tagging_system(self):
+        """
+        Test to make sure that when the TAGGING_SYSTEM
+        feature flag is false, that no tags are created
+        """
+
+        # Remove all existing rows in the tagged_object table
+        self.clear_tagged_object_table()
+
+        # Test to make sure nothing is in the tagged_object table
+        self.assertEqual([], self.query_tagged_object_table())
+
+        # Create a dataset and add it to the db
+        test_dataset = SqlaTable(
+            table_name="foo",
+            schema=None,
+            owners=[],
+            database=get_main_database(),
+            sql=None,
+            extra='{"certification": 1}',
+        )
+
+        # Create a chart and add it to the db
+        test_chart = Slice(
+            slice_name="test_chart",
+            datasource_type=DatasourceType.TABLE,
+            viz_type="bubble",
+            datasource_id=1,
+            id=1,
+        )
+
+        # Create a dashboard and add it to the db
+        test_dashboard = Dashboard()
+        test_dashboard.dashboard_title = "test_dashboard"
+        test_dashboard.slug = "test_slug"
+        test_dashboard.slices = []
+        test_dashboard.published = True
+
+        # Create a saved query and add it to the db
+        test_saved_query = SavedQuery(id=1, label="test saved query")
+
+        # Create a favorited object and add it to the db
+        test_favorited_object = FavStar(user_id=1, class_name="slice", obj_id=1)
+
+        db.session.add(test_dataset)
+        db.session.add(test_chart)
+        db.session.add(test_dashboard)
+        db.session.add(test_saved_query)
+        db.session.add(test_favorited_object)
+        db.session.commit()
+
+        # Test to make sure that no tags were added to the tagged_object table
+        tags = self.query_tagged_object_table()
+        self.assertEqual(0, len(tags))
+
+        # Cleanup the db
+        db.session.delete(test_dataset)
+        db.session.delete(test_chart)
+        db.session.delete(test_dashboard)
+        db.session.delete(test_saved_query)
+        db.session.delete(test_favorited_object)
+        db.session.commit()
+
+        # Test to make sure all the tags are deleted when the associated objects are deleted
+        self.assertEqual([], self.query_tagged_object_table())