fix(Tags filter): Filter assets by tag ID (#29412)
This commit is contained in:
parent
dd74757032
commit
33b934cbb3
|
|
@ -117,7 +117,10 @@ export enum FilterOperator {
|
|||
DatasetIsCertified = 'dataset_is_certified',
|
||||
DashboardHasCreatedBy = 'dashboard_has_created_by',
|
||||
ChartHasCreatedBy = 'chart_has_created_by',
|
||||
DashboardTags = 'dashboard_tags',
|
||||
ChartTags = 'chart_tags',
|
||||
SavedQueryTags = 'saved_query_tags',
|
||||
DashboardTagByName = 'dashboard_tags',
|
||||
DashboardTagById = 'dashboard_tag_id',
|
||||
ChartTagByName = 'chart_tags',
|
||||
ChartTagById = 'chart_tag_id',
|
||||
SavedQueryTagByName = 'saved_query_tags',
|
||||
SavedQueryTagById = 'saved_query_tag_id',
|
||||
}
|
||||
|
|
|
|||
|
|
@ -614,7 +614,7 @@ function ChartList(props: ChartListProps) {
|
|||
key: 'tags',
|
||||
id: 'tags',
|
||||
input: 'select',
|
||||
operator: FilterOperator.ChartTags,
|
||||
operator: FilterOperator.ChartTagById,
|
||||
unfilteredLabel: t('All'),
|
||||
fetchSelects: loadTags,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -547,7 +547,7 @@ function DashboardList(props: DashboardListProps) {
|
|||
key: 'tags',
|
||||
id: 'tags',
|
||||
input: 'select',
|
||||
operator: FilterOperator.DashboardTags,
|
||||
operator: FilterOperator.DashboardTagById,
|
||||
unfilteredLabel: t('All'),
|
||||
fetchSelects: loadTags,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -501,7 +501,7 @@ function SavedQueryList({
|
|||
id: 'tags',
|
||||
key: 'tags',
|
||||
input: 'select',
|
||||
operator: FilterOperator.SavedQueryTags,
|
||||
operator: FilterOperator.SavedQueryTagById,
|
||||
fetchSelects: loadTags,
|
||||
},
|
||||
]
|
||||
|
|
|
|||
|
|
@ -39,7 +39,8 @@ from superset.charts.filters import (
|
|||
ChartFilter,
|
||||
ChartHasCreatedByFilter,
|
||||
ChartOwnedCreatedFavoredByMeFilter,
|
||||
ChartTagFilter,
|
||||
ChartTagIdFilter,
|
||||
ChartTagNameFilter,
|
||||
)
|
||||
from superset.charts.schemas import (
|
||||
CHART_SCHEMAS,
|
||||
|
|
@ -238,7 +239,7 @@ class ChartRestApi(BaseSupersetModelRestApi):
|
|||
],
|
||||
"slice_name": [ChartAllTextFilter],
|
||||
"created_by": [ChartHasCreatedByFilter, ChartCreatedByMeFilter],
|
||||
"tags": [ChartTagFilter],
|
||||
"tags": [ChartTagNameFilter, ChartTagIdFilter],
|
||||
}
|
||||
# Will just affect _info endpoint
|
||||
edit_columns = ["slice_name"]
|
||||
|
|
|
|||
|
|
@ -26,10 +26,11 @@ from superset.connectors.sqla import models
|
|||
from superset.connectors.sqla.models import SqlaTable
|
||||
from superset.models.core import FavStar
|
||||
from superset.models.slice import Slice
|
||||
from superset.tags.filters import BaseTagIdFilter, BaseTagNameFilter
|
||||
from superset.utils.core import get_user_id
|
||||
from superset.utils.filters import get_dataset_access_filters
|
||||
from superset.views.base import BaseFilter
|
||||
from superset.views.base_api import BaseFavoriteFilter, BaseTagFilter
|
||||
from superset.views.base_api import BaseFavoriteFilter
|
||||
|
||||
|
||||
class ChartAllTextFilter(BaseFilter): # pylint: disable=too-few-public-methods
|
||||
|
|
@ -60,9 +61,10 @@ class ChartFavoriteFilter(BaseFavoriteFilter): # pylint: disable=too-few-public
|
|||
model = Slice
|
||||
|
||||
|
||||
class ChartTagFilter(BaseTagFilter): # pylint: disable=too-few-public-methods
|
||||
class ChartTagNameFilter(BaseTagNameFilter): # pylint: disable=too-few-public-methods
|
||||
"""
|
||||
Custom filter for the GET list that filters all dashboards that a user has favored
|
||||
Custom filter for the GET list that filters all charts associated with
|
||||
a certain tag (by its name).
|
||||
"""
|
||||
|
||||
arg_name = "chart_tags"
|
||||
|
|
@ -70,6 +72,17 @@ class ChartTagFilter(BaseTagFilter): # pylint: disable=too-few-public-methods
|
|||
model = Slice
|
||||
|
||||
|
||||
class ChartTagIdFilter(BaseTagIdFilter): # pylint: disable=too-few-public-methods
|
||||
"""
|
||||
Custom filter for the GET list that filters all charts associated with
|
||||
a certain tag (by its ID).
|
||||
"""
|
||||
|
||||
arg_name = "chart_tag_id"
|
||||
class_name = "slice"
|
||||
model = Slice
|
||||
|
||||
|
||||
class ChartCertifiedFilter(BaseFilter): # pylint: disable=too-few-public-methods
|
||||
"""
|
||||
Custom filter for the GET list that filters all certified charts
|
||||
|
|
|
|||
|
|
@ -60,7 +60,8 @@ from superset.dashboards.filters import (
|
|||
DashboardCreatedByMeFilter,
|
||||
DashboardFavoriteFilter,
|
||||
DashboardHasCreatedByFilter,
|
||||
DashboardTagFilter,
|
||||
DashboardTagIdFilter,
|
||||
DashboardTagNameFilter,
|
||||
DashboardTitleOrSlugFilter,
|
||||
FilterRelatedRoles,
|
||||
)
|
||||
|
|
@ -244,7 +245,7 @@ class DashboardRestApi(BaseSupersetModelRestApi):
|
|||
"dashboard_title": [DashboardTitleOrSlugFilter],
|
||||
"id": [DashboardFavoriteFilter, DashboardCertifiedFilter],
|
||||
"created_by": [DashboardCreatedByMeFilter, DashboardHasCreatedByFilter],
|
||||
"tags": [DashboardTagFilter],
|
||||
"tags": [DashboardTagIdFilter, DashboardTagNameFilter],
|
||||
}
|
||||
|
||||
base_order = ("changed_on", "desc")
|
||||
|
|
|
|||
|
|
@ -29,10 +29,11 @@ from superset.models.dashboard import Dashboard, is_uuid
|
|||
from superset.models.embedded_dashboard import EmbeddedDashboard
|
||||
from superset.models.slice import Slice
|
||||
from superset.security.guest_token import GuestTokenResourceType, GuestUser
|
||||
from superset.tags.filters import BaseTagIdFilter, BaseTagNameFilter
|
||||
from superset.utils.core import get_user_id
|
||||
from superset.utils.filters import get_dataset_access_filters
|
||||
from superset.views.base import BaseFilter
|
||||
from superset.views.base_api import BaseFavoriteFilter, BaseTagFilter
|
||||
from superset.views.base_api import BaseFavoriteFilter
|
||||
|
||||
|
||||
class DashboardTitleOrSlugFilter(BaseFilter): # pylint: disable=too-few-public-methods
|
||||
|
|
@ -78,9 +79,10 @@ class DashboardFavoriteFilter( # pylint: disable=too-few-public-methods
|
|||
model = Dashboard
|
||||
|
||||
|
||||
class DashboardTagFilter(BaseTagFilter): # pylint: disable=too-few-public-methods
|
||||
class DashboardTagNameFilter(BaseTagNameFilter): # pylint: disable=too-few-public-methods
|
||||
"""
|
||||
Custom filter for the GET list that filters all dashboards that a user has favored
|
||||
Custom filter for the GET list that filters all dashboards associated with
|
||||
a certain tag (by its name).
|
||||
"""
|
||||
|
||||
arg_name = "dashboard_tags"
|
||||
|
|
@ -88,6 +90,17 @@ class DashboardTagFilter(BaseTagFilter): # pylint: disable=too-few-public-metho
|
|||
model = Dashboard
|
||||
|
||||
|
||||
class DashboardTagIdFilter(BaseTagIdFilter): # pylint: disable=too-few-public-methods
|
||||
"""
|
||||
Custom filter for the GET list that filters all dashboards associated with
|
||||
a certain tag (by its ID).
|
||||
"""
|
||||
|
||||
arg_name = "dashboard_tag_id"
|
||||
class_name = "Dashboard"
|
||||
model = Dashboard
|
||||
|
||||
|
||||
class DashboardAccessFilter(BaseFilter): # pylint: disable=too-few-public-methods
|
||||
"""
|
||||
List dashboards with the following criteria:
|
||||
|
|
|
|||
|
|
@ -25,7 +25,6 @@ from flask_appbuilder.api import expose, protect, rison, safe
|
|||
from flask_appbuilder.models.sqla.interface import SQLAInterface
|
||||
from flask_babel import ngettext
|
||||
|
||||
from superset import is_feature_enabled
|
||||
from superset.commands.importers.exceptions import (
|
||||
IncorrectFormatError,
|
||||
NoValidFilesFoundError,
|
||||
|
|
@ -46,7 +45,8 @@ from superset.queries.saved_queries.filters import (
|
|||
SavedQueryAllTextFilter,
|
||||
SavedQueryFavoriteFilter,
|
||||
SavedQueryFilter,
|
||||
SavedQueryTagFilter,
|
||||
SavedQueryTagIdFilter,
|
||||
SavedQueryTagNameFilter,
|
||||
)
|
||||
from superset.queries.saved_queries.schemas import (
|
||||
get_delete_ids_schema,
|
||||
|
|
@ -124,9 +124,10 @@ class SavedQueryRestApi(BaseSupersetModelRestApi):
|
|||
"schema",
|
||||
"sql",
|
||||
"sql_tables",
|
||||
"tags.id",
|
||||
"tags.name",
|
||||
"tags.type",
|
||||
]
|
||||
if is_feature_enabled("TAGGING_SYSTEM"):
|
||||
list_columns += ["tags.id", "tags.name", "tags.type"]
|
||||
list_select_columns = list_columns + ["changed_by_fk", "changed_on"]
|
||||
add_columns = [
|
||||
"db_id",
|
||||
|
|
@ -161,15 +162,13 @@ class SavedQueryRestApi(BaseSupersetModelRestApi):
|
|||
"schema",
|
||||
"created_by",
|
||||
"changed_by",
|
||||
"tags",
|
||||
]
|
||||
if is_feature_enabled("TAGGING_SYSTEM"):
|
||||
search_columns += ["tags"]
|
||||
search_filters = {
|
||||
"id": [SavedQueryFavoriteFilter],
|
||||
"label": [SavedQueryAllTextFilter],
|
||||
"tags": [SavedQueryTagNameFilter, SavedQueryTagIdFilter],
|
||||
}
|
||||
if is_feature_enabled("TAGGING_SYSTEM"):
|
||||
search_filters["tags"] = [SavedQueryTagFilter]
|
||||
|
||||
apispec_parameter_schemas = {
|
||||
"get_delete_ids_schema": get_delete_ids_schema,
|
||||
|
|
|
|||
|
|
@ -23,8 +23,9 @@ from sqlalchemy import or_
|
|||
from sqlalchemy.orm.query import Query
|
||||
|
||||
from superset.models.sql_lab import SavedQuery
|
||||
from superset.tags.filters import BaseTagIdFilter, BaseTagNameFilter
|
||||
from superset.views.base import BaseFilter
|
||||
from superset.views.base_api import BaseFavoriteFilter, BaseTagFilter
|
||||
from superset.views.base_api import BaseFavoriteFilter
|
||||
|
||||
|
||||
class SavedQueryAllTextFilter(BaseFilter): # pylint: disable=too-few-public-methods
|
||||
|
|
@ -56,9 +57,10 @@ class SavedQueryFavoriteFilter(BaseFavoriteFilter): # pylint: disable=too-few-p
|
|||
model = SavedQuery
|
||||
|
||||
|
||||
class SavedQueryTagFilter(BaseTagFilter): # pylint: disable=too-few-public-methods
|
||||
class SavedQueryTagNameFilter(BaseTagNameFilter): # pylint: disable=too-few-public-methods
|
||||
"""
|
||||
Custom filter for the GET list that filters all dashboards that a user has favored
|
||||
Custom filter for the GET list that filters all saved queries associated with
|
||||
a certain tag (by its name).
|
||||
"""
|
||||
|
||||
arg_name = "saved_query_tags"
|
||||
|
|
@ -66,6 +68,17 @@ class SavedQueryTagFilter(BaseTagFilter): # pylint: disable=too-few-public-meth
|
|||
model = SavedQuery
|
||||
|
||||
|
||||
class SavedQueryTagIdFilter(BaseTagIdFilter): # pylint: disable=too-few-public-methods
|
||||
"""
|
||||
Custom filter for the GET list that filters all saved queries associated with
|
||||
a certain tag (by its ID).
|
||||
"""
|
||||
|
||||
arg_name = "saved_query_tag_id"
|
||||
class_name = "query"
|
||||
model = SavedQuery
|
||||
|
||||
|
||||
class SavedQueryFilter(BaseFilter): # pylint: disable=too-few-public-methods
|
||||
def apply(self, query: BaseQuery, value: Any) -> BaseQuery:
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -14,9 +14,18 @@
|
|||
# KIND, either express or implied. See the License for the
|
||||
# specific language governing permissions and limitations
|
||||
# under the License.
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from flask_babel import lazy_gettext as _
|
||||
from sqlalchemy.orm import Query
|
||||
|
||||
from superset.connectors.sqla.models import SqlaTable
|
||||
from superset.extensions import db
|
||||
from superset.models.dashboard import Dashboard
|
||||
from superset.models.slice import Slice
|
||||
from superset.sql_lab import Query as SqllabQuery
|
||||
from superset.tags.models import Tag, TagType
|
||||
from superset.views.base import BaseFilter
|
||||
|
||||
|
|
@ -37,3 +46,48 @@ class UserCreatedTagTypeFilter(BaseFilter): # pylint: disable=too-few-public-me
|
|||
if value is False:
|
||||
return query.filter(Tag.type != TagType.custom)
|
||||
return query
|
||||
|
||||
|
||||
class BaseTagNameFilter(BaseFilter): # pylint: disable=too-few-public-methods
|
||||
"""
|
||||
Base Custom filter for the GET list that filters all dashboards, slices
|
||||
and saved queries associated with a tag (by the tag name).
|
||||
"""
|
||||
|
||||
name = _("Is tagged")
|
||||
arg_name = ""
|
||||
class_name = ""
|
||||
""" The Tag class_name to user """
|
||||
model: type[Dashboard | Slice | SqllabQuery | SqlaTable] = Dashboard
|
||||
""" The SQLAlchemy model """
|
||||
|
||||
def apply(self, query: Query, value: Any) -> Query:
|
||||
ilike_value = f"%{value}%"
|
||||
tags_query = (
|
||||
db.session.query(self.model.id)
|
||||
.join(self.model.tags)
|
||||
.filter(Tag.name.ilike(ilike_value))
|
||||
)
|
||||
return query.filter(self.model.id.in_(tags_query))
|
||||
|
||||
|
||||
class BaseTagIdFilter(BaseFilter): # pylint: disable=too-few-public-methods
|
||||
"""
|
||||
Base Custom filter for the GET list that filters all dashboards, slices
|
||||
and saved queries associated with a tag (by the tag ID).
|
||||
"""
|
||||
|
||||
name = _("Is tagged")
|
||||
arg_name = ""
|
||||
class_name = ""
|
||||
""" The Tag class_name to user """
|
||||
model: type[Dashboard | Slice | SqllabQuery | SqlaTable] = Dashboard
|
||||
""" The SQLAlchemy model """
|
||||
|
||||
def apply(self, query: Query, value: Any) -> Query:
|
||||
tags_query = (
|
||||
db.session.query(self.model.id)
|
||||
.join(self.model.tags)
|
||||
.filter(Tag.id == value)
|
||||
)
|
||||
return query.filter(self.model.id.in_(tags_query))
|
||||
|
|
|
|||
|
|
@ -31,7 +31,6 @@ from marshmallow import fields, Schema
|
|||
from sqlalchemy import and_, distinct, func
|
||||
from sqlalchemy.orm.query import Query
|
||||
|
||||
from superset.connectors.sqla.models import SqlaTable
|
||||
from superset.exceptions import InvalidPayloadFormatError
|
||||
from superset.extensions import db, event_logger, security_manager, stats_logger_manager
|
||||
from superset.models.core import FavStar
|
||||
|
|
@ -40,7 +39,6 @@ from superset.models.slice import Slice
|
|||
from superset.schemas import error_payload_content
|
||||
from superset.sql_lab import Query as SqllabQuery
|
||||
from superset.superset_typing import FlaskResponse
|
||||
from superset.tags.models import Tag
|
||||
from superset.utils.core import get_user_id, time_function
|
||||
from superset.views.error_handling import handle_api_exception
|
||||
|
||||
|
|
@ -168,29 +166,6 @@ class BaseFavoriteFilter(BaseFilter): # pylint: disable=too-few-public-methods
|
|||
return query.filter(and_(~self.model.id.in_(users_favorite_query)))
|
||||
|
||||
|
||||
class BaseTagFilter(BaseFilter): # pylint: disable=too-few-public-methods
|
||||
"""
|
||||
Base Custom filter for the GET list that filters all dashboards, slices
|
||||
that a user has favored or not
|
||||
"""
|
||||
|
||||
name = _("Is tagged")
|
||||
arg_name = ""
|
||||
class_name = ""
|
||||
""" The Tag class_name to user """
|
||||
model: type[Dashboard | Slice | SqllabQuery | SqlaTable] = Dashboard
|
||||
""" The SQLAlchemy model """
|
||||
|
||||
def apply(self, query: Query, value: Any) -> Query:
|
||||
ilike_value = f"%{value}%"
|
||||
tags_query = (
|
||||
db.session.query(self.model.id)
|
||||
.join(self.model.tags)
|
||||
.filter(Tag.name.ilike(ilike_value))
|
||||
)
|
||||
return query.filter(self.model.id.in_(tags_query))
|
||||
|
||||
|
||||
class BaseSupersetApiMixin:
|
||||
csrf_exempt = False
|
||||
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ from typing import Any, Union, Optional
|
|||
from unittest.mock import Mock, patch, MagicMock
|
||||
|
||||
import pandas as pd
|
||||
import prison
|
||||
from flask import Response, g
|
||||
from flask_appbuilder.security.sqla import models as ab_models
|
||||
from flask_testing import TestCase
|
||||
|
|
@ -33,6 +34,7 @@ from sqlalchemy.orm import Session # noqa: F401
|
|||
from sqlalchemy.sql import func
|
||||
from sqlalchemy.dialects.mysql import dialect
|
||||
|
||||
from tests.integration_tests.constants import ADMIN_USERNAME
|
||||
from tests.integration_tests.test_app import app, login
|
||||
from superset.sql_parse import CtasMethod
|
||||
from superset import db, security_manager
|
||||
|
|
@ -589,6 +591,20 @@ class SupersetTestCase(TestCase):
|
|||
db.session.commit()
|
||||
return dashboard
|
||||
|
||||
def get_list(
|
||||
self,
|
||||
asset_type: str,
|
||||
filter: dict[str, Any] = {},
|
||||
username: str = ADMIN_USERNAME,
|
||||
) -> Response:
|
||||
"""
|
||||
Get list of assets, by default using admin account. Can be filtered.
|
||||
"""
|
||||
self.login(username)
|
||||
uri = f"api/v1/{asset_type}/?q={prison.dumps(filter)}"
|
||||
response = self.get_assert_metric(uri, "get_list")
|
||||
return response
|
||||
|
||||
|
||||
@contextmanager
|
||||
def db_insert_temp_object(obj: DeclarativeMeta):
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ from sqlalchemy.sql import func
|
|||
from superset.commands.chart.data.get_data_command import ChartDataCommand
|
||||
from superset.commands.chart.exceptions import ChartDataQueryFailedError
|
||||
from superset.connectors.sqla.models import SqlaTable
|
||||
from superset.extensions import cache_manager, db, security_manager # noqa: F401
|
||||
from superset.extensions import cache_manager, db, security_manager
|
||||
from superset.models.core import Database, FavStar, FavStarClassName
|
||||
from superset.models.dashboard import Dashboard
|
||||
from superset.models.slice import Slice
|
||||
|
|
@ -39,11 +39,8 @@ from superset.reports.models import ReportSchedule, ReportScheduleType
|
|||
from superset.tags.models import ObjectType, Tag, TaggedObject, TagType
|
||||
from superset.utils import json
|
||||
from superset.utils.core import get_example_default_schema
|
||||
from superset.utils.database import get_example_database # noqa: F401
|
||||
from superset.viz import viz_types # noqa: F401
|
||||
from tests.integration_tests.base_api_tests import ApiOwnersTestCaseMixin
|
||||
from tests.integration_tests.base_tests import SupersetTestCase
|
||||
from tests.integration_tests.conftest import with_feature_flags # noqa: F401
|
||||
from tests.integration_tests.constants import (
|
||||
ADMIN_USERNAME,
|
||||
ALPHA_USERNAME,
|
||||
|
|
@ -64,6 +61,10 @@ from tests.integration_tests.fixtures.importexport import (
|
|||
dataset_config,
|
||||
dataset_metadata_config,
|
||||
)
|
||||
from tests.integration_tests.fixtures.tags import (
|
||||
create_custom_tags, # noqa: F401
|
||||
get_filter_params,
|
||||
)
|
||||
from tests.integration_tests.fixtures.unicode_dashboard import (
|
||||
load_unicode_dashboard_with_slice, # noqa: F401
|
||||
load_unicode_data, # noqa: F401
|
||||
|
|
@ -200,27 +201,8 @@ class TestChartApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCase):
|
|||
db.session.delete(self.chart)
|
||||
db.session.commit()
|
||||
|
||||
@pytest.fixture()
|
||||
def create_custom_tags(self):
|
||||
with self.create_app().app_context():
|
||||
tags: list[Tag] = []
|
||||
for tag_name in {"one_tag", "new_tag"}:
|
||||
tag = Tag(
|
||||
name=tag_name,
|
||||
type="custom",
|
||||
)
|
||||
db.session.add(tag)
|
||||
db.session.commit()
|
||||
tags.append(tag)
|
||||
|
||||
yield tags
|
||||
|
||||
for tags in tags:
|
||||
db.session.delete(tags)
|
||||
db.session.commit()
|
||||
|
||||
@pytest.fixture()
|
||||
def create_chart_with_tag(self, create_custom_tags):
|
||||
@pytest.fixture
|
||||
def create_chart_with_tag(self, create_custom_tags): # noqa: F811
|
||||
with self.create_app().app_context():
|
||||
alpha_user = self.get_user(ALPHA_USERNAME)
|
||||
|
||||
|
|
@ -230,7 +212,7 @@ class TestChartApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCase):
|
|||
1,
|
||||
)
|
||||
|
||||
tag = db.session.query(Tag).filter(Tag.name == "one_tag").first()
|
||||
tag = db.session.query(Tag).filter(Tag.name == "first_tag").first()
|
||||
tag_association = TaggedObject(
|
||||
object_id=chart.id,
|
||||
object_type=ObjectType.chart,
|
||||
|
|
@ -247,6 +229,70 @@ class TestChartApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCase):
|
|||
db.session.delete(chart)
|
||||
db.session.commit()
|
||||
|
||||
@pytest.fixture
|
||||
def create_charts_some_with_tags(self, create_custom_tags): # noqa: F811
|
||||
"""
|
||||
Fixture that creates 4 charts:
|
||||
- ``first_chart`` is associated with ``first_tag``
|
||||
- ``second_chart`` is associated with ``second_tag``
|
||||
- ``third_chart`` is associated with both ``first_tag`` and ``second_tag``
|
||||
- ``fourth_chart`` is not associated with any tag
|
||||
|
||||
Relies on the ``create_custom_tags`` fixture for the tag creation.
|
||||
"""
|
||||
with self.create_app().app_context():
|
||||
admin_user = self.get_user(ADMIN_USERNAME)
|
||||
|
||||
tags = {
|
||||
"first_tag": db.session.query(Tag)
|
||||
.filter(Tag.name == "first_tag")
|
||||
.first(),
|
||||
"second_tag": db.session.query(Tag)
|
||||
.filter(Tag.name == "second_tag")
|
||||
.first(),
|
||||
}
|
||||
|
||||
chart_names = ["first_chart", "second_chart", "third_chart", "fourth_chart"]
|
||||
charts = [
|
||||
self.insert_chart(name, [admin_user.id], 1) for name in chart_names
|
||||
]
|
||||
|
||||
tag_associations = [
|
||||
TaggedObject(
|
||||
object_id=charts[0].id,
|
||||
object_type=ObjectType.chart,
|
||||
tag=tags["first_tag"],
|
||||
),
|
||||
TaggedObject(
|
||||
object_id=charts[1].id,
|
||||
object_type=ObjectType.chart,
|
||||
tag=tags["second_tag"],
|
||||
),
|
||||
TaggedObject(
|
||||
object_id=charts[2].id,
|
||||
object_type=ObjectType.chart,
|
||||
tag=tags["first_tag"],
|
||||
),
|
||||
TaggedObject(
|
||||
object_id=charts[2].id,
|
||||
object_type=ObjectType.chart,
|
||||
tag=tags["second_tag"],
|
||||
),
|
||||
]
|
||||
|
||||
for association in tag_associations:
|
||||
db.session.add(association)
|
||||
db.session.commit()
|
||||
|
||||
yield charts
|
||||
|
||||
# rollback changes
|
||||
for association in tag_associations:
|
||||
db.session.delete(association)
|
||||
for chart in charts:
|
||||
db.session.delete(chart)
|
||||
db.session.commit()
|
||||
|
||||
def test_info_security_chart(self):
|
||||
"""
|
||||
Chart API: Test info security
|
||||
|
|
@ -1131,6 +1177,55 @@ class TestChartApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCase):
|
|||
assert len(result) == 1
|
||||
assert result[0]["slice_name"] == self.chart.slice_name
|
||||
|
||||
@pytest.mark.usefixtures("create_charts_some_with_tags")
|
||||
def test_get_charts_tag_filters(self):
|
||||
"""
|
||||
Chart API: Test get charts with tag filters
|
||||
"""
|
||||
# Get custom tags relationship
|
||||
tags = {
|
||||
"first_tag": db.session.query(Tag).filter(Tag.name == "first_tag").first(),
|
||||
"second_tag": db.session.query(Tag)
|
||||
.filter(Tag.name == "second_tag")
|
||||
.first(),
|
||||
"third_tag": db.session.query(Tag).filter(Tag.name == "third_tag").first(),
|
||||
}
|
||||
chart_tag_relationship = {
|
||||
tag.name: db.session.query(Slice.id)
|
||||
.join(Slice.tags)
|
||||
.filter(Tag.id == tag.id)
|
||||
.all()
|
||||
for tag in tags.values()
|
||||
}
|
||||
|
||||
# Validate API results for each tag
|
||||
for tag_name, tag in tags.items():
|
||||
expected_charts = chart_tag_relationship[tag_name]
|
||||
|
||||
# Filter by tag ID
|
||||
filter_params = get_filter_params("chart_tag_id", tag.id)
|
||||
response_by_id = self.get_list("chart", filter_params)
|
||||
self.assertEqual(response_by_id.status_code, 200)
|
||||
data_by_id = json.loads(response_by_id.data.decode("utf-8"))
|
||||
|
||||
# Filter by tag name
|
||||
filter_params = get_filter_params("chart_tags", tag.name)
|
||||
response_by_name = self.get_list("chart", filter_params)
|
||||
self.assertEqual(response_by_name.status_code, 200)
|
||||
data_by_name = json.loads(response_by_name.data.decode("utf-8"))
|
||||
|
||||
# Compare results
|
||||
self.assertEqual(
|
||||
data_by_id["count"],
|
||||
data_by_name["count"],
|
||||
len(expected_charts),
|
||||
)
|
||||
self.assertEqual(
|
||||
set(chart["id"] for chart in data_by_id["result"]),
|
||||
set(chart["id"] for chart in data_by_name["result"]),
|
||||
set(chart.id for chart in expected_charts),
|
||||
)
|
||||
|
||||
def test_get_charts_changed_on(self):
|
||||
"""
|
||||
Dashboard API: Test get charts changed on
|
||||
|
|
@ -2059,7 +2154,7 @@ class TestChartApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCase):
|
|||
chart = (
|
||||
db.session.query(Slice).filter(Slice.slice_name == "chart with tag").first()
|
||||
)
|
||||
new_tag = db.session.query(Tag).filter(Tag.name == "new_tag").one()
|
||||
new_tag = db.session.query(Tag).filter(Tag.name == "second_tag").one()
|
||||
|
||||
# get existing tag and add a new one
|
||||
new_tags = [tag.id for tag in chart.tags if tag.type == TagType.custom]
|
||||
|
|
@ -2118,7 +2213,7 @@ class TestChartApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCase):
|
|||
chart = (
|
||||
db.session.query(Slice).filter(Slice.slice_name == "chart with tag").first()
|
||||
)
|
||||
new_tag = db.session.query(Tag).filter(Tag.name == "new_tag").one()
|
||||
new_tag = db.session.query(Tag).filter(Tag.name == "second_tag").one()
|
||||
|
||||
# get existing tag and add a new one
|
||||
new_tags = [tag.id for tag in chart.tags if tag.type == TagType.custom]
|
||||
|
|
@ -2183,7 +2278,7 @@ class TestChartApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCase):
|
|||
chart = (
|
||||
db.session.query(Slice).filter(Slice.slice_name == "chart with tag").first()
|
||||
)
|
||||
new_tag = db.session.query(Tag).filter(Tag.name == "new_tag").one()
|
||||
new_tag = db.session.query(Tag).filter(Tag.name == "second_tag").one()
|
||||
|
||||
# get existing tag and add a new one
|
||||
new_tags = [tag.id for tag in chart.tags if tag.type == TagType.custom]
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ import yaml
|
|||
|
||||
from freezegun import freeze_time
|
||||
from sqlalchemy import and_
|
||||
from superset import app, db, security_manager # noqa: F401
|
||||
from superset import db, security_manager # noqa: F401
|
||||
from superset.models.dashboard import Dashboard
|
||||
from superset.models.core import FavStar, FavStarClassName
|
||||
from superset.reports.models import ReportSchedule, ReportScheduleType
|
||||
|
|
@ -41,7 +41,6 @@ from superset.utils import json
|
|||
|
||||
from tests.integration_tests.base_api_tests import ApiOwnersTestCaseMixin
|
||||
from tests.integration_tests.base_tests import SupersetTestCase
|
||||
from tests.integration_tests.conftest import with_feature_flags # noqa: F401
|
||||
from tests.integration_tests.constants import (
|
||||
ADMIN_USERNAME,
|
||||
ALPHA_USERNAME,
|
||||
|
|
@ -56,6 +55,10 @@ from tests.integration_tests.fixtures.importexport import (
|
|||
dataset_config,
|
||||
dataset_metadata_config,
|
||||
)
|
||||
from tests.integration_tests.fixtures.tags import (
|
||||
create_custom_tags, # noqa: F401
|
||||
get_filter_params,
|
||||
)
|
||||
from tests.integration_tests.utils.get_dashboards import get_dashboards_ids
|
||||
from tests.integration_tests.fixtures.birth_names_dashboard import (
|
||||
load_birth_names_dashboard_with_slices, # noqa: F401
|
||||
|
|
@ -169,27 +172,8 @@ class TestDashboardApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCas
|
|||
db.session.delete(dashboard)
|
||||
db.session.commit()
|
||||
|
||||
@pytest.fixture()
|
||||
def create_custom_tags(self):
|
||||
with self.create_app().app_context():
|
||||
tags: list[Tag] = []
|
||||
for tag_name in {"one_tag", "new_tag"}:
|
||||
tag = Tag(
|
||||
name=tag_name,
|
||||
type="custom",
|
||||
)
|
||||
db.session.add(tag)
|
||||
db.session.commit()
|
||||
tags.append(tag)
|
||||
|
||||
yield tags
|
||||
|
||||
for tags in tags:
|
||||
db.session.delete(tags)
|
||||
db.session.commit()
|
||||
|
||||
@pytest.fixture()
|
||||
def create_dashboard_with_tag(self, create_custom_tags):
|
||||
@pytest.fixture
|
||||
def create_dashboard_with_tag(self, create_custom_tags): # noqa: F811
|
||||
with self.create_app().app_context():
|
||||
gamma = self.get_user("gamma")
|
||||
|
||||
|
|
@ -198,7 +182,7 @@ class TestDashboardApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCas
|
|||
None,
|
||||
[gamma.id],
|
||||
)
|
||||
tag = db.session.query(Tag).filter(Tag.name == "one_tag").first()
|
||||
tag = db.session.query(Tag).filter(Tag.name == "first_tag").first()
|
||||
tag_association = TaggedObject(
|
||||
object_id=dashboard.id,
|
||||
object_type=ObjectType.dashboard,
|
||||
|
|
@ -215,6 +199,76 @@ class TestDashboardApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCas
|
|||
db.session.delete(dashboard)
|
||||
db.session.commit()
|
||||
|
||||
@pytest.fixture
|
||||
def create_dashboards_some_with_tags(self, create_custom_tags): # noqa: F811
|
||||
"""
|
||||
Fixture that creates 4 dashboards:
|
||||
- ``first_dashboard`` is associated with ``first_tag``
|
||||
- ``second_dashboard`` is associated with ``second_tag``
|
||||
- ``third_dashboard`` is associated with both ``first_tag`` and ``second_tag``
|
||||
- ``fourth_dashboard`` is not associated with any tag
|
||||
|
||||
Relies on the ``create_custom_tags`` fixture for the tag creation.
|
||||
"""
|
||||
with self.create_app().app_context():
|
||||
admin_user = self.get_user(ADMIN_USERNAME)
|
||||
|
||||
tags = {
|
||||
"first_tag": db.session.query(Tag)
|
||||
.filter(Tag.name == "first_tag")
|
||||
.first(),
|
||||
"second_tag": db.session.query(Tag)
|
||||
.filter(Tag.name == "second_tag")
|
||||
.first(),
|
||||
}
|
||||
|
||||
dashboard_names = [
|
||||
"first_dashboard",
|
||||
"second_dashboard",
|
||||
"third_dashboard",
|
||||
"fourth_dashboard",
|
||||
]
|
||||
dashboards = [
|
||||
self.insert_dashboard(name, None, [admin_user.id])
|
||||
for name in dashboard_names
|
||||
]
|
||||
|
||||
tag_associations = [
|
||||
TaggedObject(
|
||||
object_id=dashboards[0].id,
|
||||
object_type=ObjectType.chart,
|
||||
tag=tags["first_tag"],
|
||||
),
|
||||
TaggedObject(
|
||||
object_id=dashboards[1].id,
|
||||
object_type=ObjectType.chart,
|
||||
tag=tags["second_tag"],
|
||||
),
|
||||
TaggedObject(
|
||||
object_id=dashboards[2].id,
|
||||
object_type=ObjectType.chart,
|
||||
tag=tags["first_tag"],
|
||||
),
|
||||
TaggedObject(
|
||||
object_id=dashboards[2].id,
|
||||
object_type=ObjectType.chart,
|
||||
tag=tags["second_tag"],
|
||||
),
|
||||
]
|
||||
|
||||
for association in tag_associations:
|
||||
db.session.add(association)
|
||||
db.session.commit()
|
||||
|
||||
yield dashboards
|
||||
|
||||
# rollback changes
|
||||
for association in tag_associations:
|
||||
db.session.delete(association)
|
||||
for chart in dashboards:
|
||||
db.session.delete(chart)
|
||||
db.session.commit()
|
||||
|
||||
@pytest.mark.usefixtures("load_world_bank_dashboard_with_slices")
|
||||
def test_get_dashboard_datasets(self):
|
||||
self.login(ADMIN_USERNAME)
|
||||
|
|
@ -710,6 +764,55 @@ class TestDashboardApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCas
|
|||
expected_model.dashboard_title == data["result"][i]["dashboard_title"]
|
||||
)
|
||||
|
||||
@pytest.mark.usefixtures("create_dashboards_some_with_tags")
|
||||
def test_get_dashboards_tag_filters(self):
|
||||
"""
|
||||
Dashboard API: Test get dashboards with tag filters
|
||||
"""
|
||||
# Get custom tags relationship
|
||||
tags = {
|
||||
"first_tag": db.session.query(Tag).filter(Tag.name == "first_tag").first(),
|
||||
"second_tag": db.session.query(Tag)
|
||||
.filter(Tag.name == "second_tag")
|
||||
.first(),
|
||||
"third_tag": db.session.query(Tag).filter(Tag.name == "third_tag").first(),
|
||||
}
|
||||
dashboard_tag_relationship = {
|
||||
tag.name: db.session.query(Dashboard.id)
|
||||
.join(Dashboard.tags)
|
||||
.filter(Tag.id == tag.id)
|
||||
.all()
|
||||
for tag in tags.values()
|
||||
}
|
||||
|
||||
# Validate API results for each tag
|
||||
for tag_name, tag in tags.items():
|
||||
expected_dashboards = dashboard_tag_relationship[tag_name]
|
||||
|
||||
# Filter by tag ID
|
||||
filter_params = get_filter_params("dashboard_tag_id", tag.id)
|
||||
response_by_id = self.get_list("dashboard", filter_params)
|
||||
self.assertEqual(response_by_id.status_code, 200)
|
||||
data_by_id = json.loads(response_by_id.data.decode("utf-8"))
|
||||
|
||||
# Filter by tag name
|
||||
filter_params = get_filter_params("dashboard_tags", tag.name)
|
||||
response_by_name = self.get_list("dashboard", filter_params)
|
||||
self.assertEqual(response_by_name.status_code, 200)
|
||||
data_by_name = json.loads(response_by_name.data.decode("utf-8"))
|
||||
|
||||
# Compare results
|
||||
self.assertEqual(
|
||||
data_by_id["count"],
|
||||
data_by_name["count"],
|
||||
len(expected_dashboards),
|
||||
)
|
||||
self.assertEqual(
|
||||
set(chart["id"] for chart in data_by_id["result"]),
|
||||
set(chart["id"] for chart in data_by_name["result"]),
|
||||
set(chart.id for chart in expected_dashboards),
|
||||
)
|
||||
|
||||
@pytest.mark.usefixtures("create_dashboards")
|
||||
def test_get_current_user_favorite_status(self):
|
||||
"""
|
||||
|
|
@ -2504,7 +2607,7 @@ class TestDashboardApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCas
|
|||
.filter(Dashboard.dashboard_title == "dash with tag")
|
||||
.first()
|
||||
)
|
||||
new_tag = db.session.query(Tag).filter(Tag.name == "new_tag").one()
|
||||
new_tag = db.session.query(Tag).filter(Tag.name == "second_tag").one()
|
||||
|
||||
# get existing tag and add a new one
|
||||
new_tags = [tag.id for tag in dashboard.tags if tag.type == TagType.custom]
|
||||
|
|
@ -2566,7 +2669,7 @@ class TestDashboardApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCas
|
|||
.filter(Dashboard.dashboard_title == "dash with tag")
|
||||
.first()
|
||||
)
|
||||
new_tag = db.session.query(Tag).filter(Tag.name == "new_tag").one()
|
||||
new_tag = db.session.query(Tag).filter(Tag.name == "second_tag").one()
|
||||
|
||||
# get existing tag and add a new one
|
||||
new_tags = [tag.id for tag in dashboard.tags if tag.type == TagType.custom]
|
||||
|
|
@ -2580,7 +2683,7 @@ class TestDashboardApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCas
|
|||
|
||||
# Clean up system tags
|
||||
tag_list = [tag.id for tag in model.tags if tag.type == TagType.custom]
|
||||
self.assertEqual(tag_list, new_tags)
|
||||
self.assertEqual(sorted(tag_list), sorted(new_tags))
|
||||
|
||||
security_manager.add_permission_role(gamma_role, write_tags_perm)
|
||||
|
||||
|
|
@ -2635,7 +2738,7 @@ class TestDashboardApi(ApiOwnersTestCaseMixin, InsertChartMixin, SupersetTestCas
|
|||
.filter(Dashboard.dashboard_title == "dash with tag")
|
||||
.first()
|
||||
)
|
||||
new_tag = db.session.query(Tag).filter(Tag.name == "new_tag").one()
|
||||
new_tag = db.session.query(Tag).filter(Tag.name == "second_tag").one()
|
||||
|
||||
# get existing tag and add a new one
|
||||
new_tags = [tag.id for tag in dashboard.tags if tag.type == TagType.custom]
|
||||
|
|
|
|||
|
|
@ -17,7 +17,9 @@
|
|||
|
||||
import pytest
|
||||
|
||||
from superset import db
|
||||
from superset.tags.core import clear_sqla_event_listeners, register_sqla_event_listeners
|
||||
from superset.tags.models import Tag
|
||||
from tests.integration_tests.test_app import app
|
||||
|
||||
|
||||
|
|
@ -31,3 +33,36 @@ def with_tagging_system_feature():
|
|||
yield
|
||||
app.config["DEFAULT_FEATURE_FLAGS"]["TAGGING_SYSTEM"] = False
|
||||
clear_sqla_event_listeners()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def create_custom_tags():
|
||||
with app.app_context():
|
||||
tags: list[Tag] = []
|
||||
for tag_name in {"first_tag", "second_tag", "third_tag"}:
|
||||
tag = Tag(
|
||||
name=tag_name,
|
||||
type="custom",
|
||||
)
|
||||
db.session.add(tag)
|
||||
db.session.commit()
|
||||
tags.append(tag)
|
||||
|
||||
yield tags
|
||||
|
||||
for tags in tags:
|
||||
db.session.delete(tags)
|
||||
db.session.commit()
|
||||
|
||||
|
||||
# Helper function to return filter parameters
|
||||
def get_filter_params(opr, value):
|
||||
return {
|
||||
"filters": [
|
||||
{
|
||||
"col": "tags",
|
||||
"opr": opr,
|
||||
"value": value,
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ from superset import db
|
|||
from superset.models.core import Database
|
||||
from superset.models.core import FavStar
|
||||
from superset.models.sql_lab import SavedQuery
|
||||
from superset.tags.models import ObjectType, Tag, TaggedObject
|
||||
from superset.utils.database import get_example_database
|
||||
from superset.utils import json
|
||||
|
||||
|
|
@ -42,6 +43,10 @@ from tests.integration_tests.fixtures.importexport import (
|
|||
saved_queries_config,
|
||||
saved_queries_metadata_config,
|
||||
)
|
||||
from tests.integration_tests.fixtures.tags import (
|
||||
create_custom_tags, # noqa: F401
|
||||
get_filter_params,
|
||||
)
|
||||
|
||||
|
||||
SAVED_QUERIES_FIXTURE_COUNT = 10
|
||||
|
|
@ -123,6 +128,73 @@ class TestSavedQueryApi(SupersetTestCase):
|
|||
db.session.delete(fav_saved_query)
|
||||
db.session.commit()
|
||||
|
||||
@pytest.fixture
|
||||
def create_saved_queries_some_with_tags(self, create_custom_tags): # noqa: F811
|
||||
"""
|
||||
Fixture that creates 4 saved queries:
|
||||
- ``first_query`` is associated with ``first_tag``
|
||||
- ``second_query`` is associated with ``second_tag``
|
||||
- ``third_query`` is associated with both ``first_tag`` and ``second_tag``
|
||||
- ``fourth_query`` is not associated with any tag
|
||||
|
||||
Relies on the ``create_custom_tags`` fixture for the tag creation.
|
||||
"""
|
||||
with self.create_app().app_context():
|
||||
tags = {
|
||||
"first_tag": db.session.query(Tag)
|
||||
.filter(Tag.name == "first_tag")
|
||||
.first(),
|
||||
"second_tag": db.session.query(Tag)
|
||||
.filter(Tag.name == "second_tag")
|
||||
.first(),
|
||||
}
|
||||
|
||||
query_labels = [
|
||||
"first_query",
|
||||
"second_query",
|
||||
"third_query",
|
||||
"fourth_query",
|
||||
]
|
||||
queries = [
|
||||
self.insert_default_saved_query(label=name) for name in query_labels
|
||||
]
|
||||
|
||||
tag_associations = [
|
||||
TaggedObject(
|
||||
object_id=queries[0].id,
|
||||
object_type=ObjectType.chart,
|
||||
tag=tags["first_tag"],
|
||||
),
|
||||
TaggedObject(
|
||||
object_id=queries[1].id,
|
||||
object_type=ObjectType.chart,
|
||||
tag=tags["second_tag"],
|
||||
),
|
||||
TaggedObject(
|
||||
object_id=queries[2].id,
|
||||
object_type=ObjectType.chart,
|
||||
tag=tags["first_tag"],
|
||||
),
|
||||
TaggedObject(
|
||||
object_id=queries[2].id,
|
||||
object_type=ObjectType.chart,
|
||||
tag=tags["second_tag"],
|
||||
),
|
||||
]
|
||||
|
||||
for association in tag_associations:
|
||||
db.session.add(association)
|
||||
db.session.commit()
|
||||
|
||||
yield queries
|
||||
|
||||
# rollback changes
|
||||
for association in tag_associations:
|
||||
db.session.delete(association)
|
||||
for chart in queries:
|
||||
db.session.delete(chart)
|
||||
db.session.commit()
|
||||
|
||||
@pytest.mark.usefixtures("create_saved_queries")
|
||||
def test_get_list_saved_query(self):
|
||||
"""
|
||||
|
|
@ -366,6 +438,55 @@ class TestSavedQueryApi(SupersetTestCase):
|
|||
data = json.loads(rv.data.decode("utf-8"))
|
||||
assert data["count"] == len(all_queries)
|
||||
|
||||
@pytest.mark.usefixtures("create_saved_queries_some_with_tags")
|
||||
def test_get_saved_queries_tag_filters(self):
|
||||
"""
|
||||
Saved Query API: Test get saved queries with tag filters
|
||||
"""
|
||||
# Get custom tags relationship
|
||||
tags = {
|
||||
"first_tag": db.session.query(Tag).filter(Tag.name == "first_tag").first(),
|
||||
"second_tag": db.session.query(Tag)
|
||||
.filter(Tag.name == "second_tag")
|
||||
.first(),
|
||||
"third_tag": db.session.query(Tag).filter(Tag.name == "third_tag").first(),
|
||||
}
|
||||
saved_queries_tag_relationship = {
|
||||
tag.name: db.session.query(SavedQuery.id)
|
||||
.join(SavedQuery.tags)
|
||||
.filter(Tag.id == tag.id)
|
||||
.all()
|
||||
for tag in tags.values()
|
||||
}
|
||||
|
||||
# Validate API results for each tag
|
||||
for tag_name, tag in tags.items():
|
||||
expected_saved_queries = saved_queries_tag_relationship[tag_name]
|
||||
|
||||
# Filter by tag ID
|
||||
filter_params = get_filter_params("saved_query_tag_id", tag.id)
|
||||
response_by_id = self.get_list("saved_query", filter_params)
|
||||
self.assertEqual(response_by_id.status_code, 200)
|
||||
data_by_id = json.loads(response_by_id.data.decode("utf-8"))
|
||||
|
||||
# Filter by tag name
|
||||
filter_params = get_filter_params("saved_query_tags", tag.name)
|
||||
response_by_name = self.get_list("saved_query", filter_params)
|
||||
self.assertEqual(response_by_name.status_code, 200)
|
||||
data_by_name = json.loads(response_by_name.data.decode("utf-8"))
|
||||
|
||||
# Compare results
|
||||
self.assertEqual(
|
||||
data_by_id["count"],
|
||||
data_by_name["count"],
|
||||
len(expected_saved_queries),
|
||||
)
|
||||
self.assertEqual(
|
||||
set(query["id"] for query in data_by_id["result"]),
|
||||
set(query["id"] for query in data_by_name["result"]),
|
||||
set(query.id for query in expected_saved_queries),
|
||||
)
|
||||
|
||||
@pytest.mark.usefixtures("create_saved_queries")
|
||||
def test_get_saved_query_favorite_filter(self):
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -0,0 +1,85 @@
|
|||
# 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.
|
||||
|
||||
import pytest
|
||||
from flask_appbuilder import Model
|
||||
from flask_appbuilder.models.sqla.interface import SQLAInterface
|
||||
from sqlalchemy.orm.session import Session
|
||||
|
||||
from superset.models.dashboard import Dashboard
|
||||
from superset.models.slice import Slice
|
||||
from superset.models.sql_lab import SavedQuery
|
||||
from superset.tags.filters import BaseTagIdFilter, BaseTagNameFilter
|
||||
|
||||
FILTER_MODELS = [Slice, Dashboard, SavedQuery]
|
||||
OBJECT_TYPES = {
|
||||
"dashboards": "dashboard",
|
||||
"slices": "chart",
|
||||
"saved_query": "query",
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.parametrize("model", FILTER_MODELS)
|
||||
@pytest.mark.parametrize("name", ["my_tag", "test tag", "blaah"])
|
||||
def test_base_tag_filter_by_name(session: Session, model: Model, name: str) -> None:
|
||||
table = model.__tablename__
|
||||
engine = session.get_bind()
|
||||
query = session.query(model)
|
||||
filter = BaseTagNameFilter("tags", SQLAInterface(model))
|
||||
final_query = filter.apply(query, name)
|
||||
compiled_query = final_query.statement.compile(
|
||||
engine,
|
||||
compile_kwargs={"literal_binds": True},
|
||||
)
|
||||
|
||||
# Assert the JOIN clause is correct
|
||||
assert (
|
||||
f"FROM {table} JOIN tagged_object AS tagged_object_1 ON {table}.id "
|
||||
"= tagged_object_1.object_id AND tagged_object_1.object_type = "
|
||||
f"'{OBJECT_TYPES.get(table)}' JOIN tag ON tagged_object_1.tag_id = tag.id"
|
||||
) in str(compiled_query)
|
||||
|
||||
# Assert the WHERE clause is correct
|
||||
assert str(compiled_query).endswith(
|
||||
f"WHERE lower(tag.name) LIKE lower('%{name}%'))"
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("model", FILTER_MODELS)
|
||||
@pytest.mark.parametrize("id", [3, 5, 8])
|
||||
def test_base_tag_filter_by_id(session: Session, model: Model, id: int) -> None:
|
||||
table = model.__tablename__
|
||||
engine = session.get_bind()
|
||||
query = session.query(model)
|
||||
|
||||
filter = BaseTagIdFilter("tags", SQLAInterface(model))
|
||||
filter.id_based_filter = True
|
||||
final_query = filter.apply(query, id)
|
||||
compiled_query = final_query.statement.compile(
|
||||
engine,
|
||||
compile_kwargs={"literal_binds": True},
|
||||
)
|
||||
|
||||
# Assert the JOIN clause is correct
|
||||
assert (
|
||||
f"FROM {table} JOIN tagged_object AS tagged_object_1 ON {table}.id "
|
||||
"= tagged_object_1.object_id AND tagged_object_1.object_type = "
|
||||
f"'{OBJECT_TYPES.get(table)}' JOIN tag ON tagged_object_1.tag_id = tag.id"
|
||||
) in str(compiled_query)
|
||||
|
||||
# Assert the WHERE clause is correct
|
||||
assert str(compiled_query).endswith(f"WHERE tag.id = {id})")
|
||||
Loading…
Reference in New Issue