feat: custom favorite filter for dashboards, charts and saved queries (#11083)
* feat: custom favorite filter for dashboards * lint and sort * add favored for charts * fix tests and lint * more tests and saved query filter * fix tests * fix tests * lint * lint and fix conflict * remove unnecessary prop * separate tests
This commit is contained in:
parent
07716ffd76
commit
4c85d33109
|
|
@ -41,7 +41,7 @@ from superset.charts.commands.exceptions import (
|
|||
ChartUpdateFailedError,
|
||||
)
|
||||
from superset.charts.commands.update import UpdateChartCommand
|
||||
from superset.charts.filters import ChartAllTextFilter, ChartFilter
|
||||
from superset.charts.filters import ChartAllTextFilter, ChartFavoriteFilter, ChartFilter
|
||||
from superset.charts.schemas import (
|
||||
CHART_SCHEMAS,
|
||||
ChartDataQueryContextSchema,
|
||||
|
|
@ -143,13 +143,17 @@ class ChartRestApi(BaseSupersetModelRestApi):
|
|||
"datasource_name",
|
||||
"datasource_type",
|
||||
"description",
|
||||
"id",
|
||||
"owners",
|
||||
"slice_name",
|
||||
"viz_type",
|
||||
]
|
||||
base_order = ("changed_on", "desc")
|
||||
base_filters = [["id", ChartFilter, lambda: []]]
|
||||
search_filters = {"slice_name": [ChartAllTextFilter]}
|
||||
search_filters = {
|
||||
"id": [ChartFavoriteFilter],
|
||||
"slice_name": [ChartAllTextFilter],
|
||||
}
|
||||
|
||||
# Will just affect _info endpoint
|
||||
edit_columns = ["slice_name"]
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ from superset import security_manager
|
|||
from superset.connectors.sqla.models import SqlaTable
|
||||
from superset.models.slice import Slice
|
||||
from superset.views.base import BaseFilter
|
||||
from superset.views.base_api import BaseFavoriteFilter
|
||||
|
||||
|
||||
class ChartAllTextFilter(BaseFilter): # pylint: disable=too-few-public-methods
|
||||
|
|
@ -44,6 +45,16 @@ class ChartAllTextFilter(BaseFilter): # pylint: disable=too-few-public-methods
|
|||
)
|
||||
|
||||
|
||||
class ChartFavoriteFilter(BaseFavoriteFilter): # pylint: disable=too-few-public-methods
|
||||
"""
|
||||
Custom filter for the GET list that filters all charts that a user has favored
|
||||
"""
|
||||
|
||||
arg_name = "chart_is_fav"
|
||||
class_name = "slice"
|
||||
model = Slice
|
||||
|
||||
|
||||
class ChartFilter(BaseFilter): # pylint: disable=too-few-public-methods
|
||||
def apply(self, query: Query, value: Any) -> Query:
|
||||
if security_manager.can_access_all_datasources():
|
||||
|
|
|
|||
|
|
@ -40,7 +40,11 @@ from superset.dashboards.commands.exceptions import (
|
|||
DashboardUpdateFailedError,
|
||||
)
|
||||
from superset.dashboards.commands.update import UpdateDashboardCommand
|
||||
from superset.dashboards.filters import DashboardFilter, DashboardTitleOrSlugFilter
|
||||
from superset.dashboards.filters import (
|
||||
DashboardFavoriteFilter,
|
||||
DashboardFilter,
|
||||
DashboardTitleOrSlugFilter,
|
||||
)
|
||||
from superset.dashboards.schemas import (
|
||||
DashboardPostSchema,
|
||||
DashboardPutSchema,
|
||||
|
|
@ -142,8 +146,18 @@ class DashboardRestApi(BaseSupersetModelRestApi):
|
|||
]
|
||||
edit_columns = add_columns
|
||||
|
||||
search_columns = ("dashboard_title", "slug", "owners", "published", "created_by")
|
||||
search_filters = {"dashboard_title": [DashboardTitleOrSlugFilter]}
|
||||
search_columns = (
|
||||
"created_by",
|
||||
"dashboard_title",
|
||||
"id",
|
||||
"owners",
|
||||
"published",
|
||||
"slug",
|
||||
)
|
||||
search_filters = {
|
||||
"dashboard_title": [DashboardTitleOrSlugFilter],
|
||||
"id": [DashboardFavoriteFilter],
|
||||
}
|
||||
base_order = ("changed_on", "desc")
|
||||
|
||||
add_model_schema = DashboardPostSchema()
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ from superset.models.core import FavStar
|
|||
from superset.models.dashboard import Dashboard
|
||||
from superset.models.slice import Slice
|
||||
from superset.views.base import BaseFilter, get_user_roles
|
||||
from superset.views.base_api import BaseFavoriteFilter
|
||||
|
||||
|
||||
class DashboardTitleOrSlugFilter(BaseFilter): # pylint: disable=too-few-public-methods
|
||||
|
|
@ -43,6 +44,18 @@ class DashboardTitleOrSlugFilter(BaseFilter): # pylint: disable=too-few-public-
|
|||
)
|
||||
|
||||
|
||||
class DashboardFavoriteFilter(
|
||||
BaseFavoriteFilter
|
||||
): # pylint: disable=too-few-public-methods
|
||||
"""
|
||||
Custom filter for the GET list that filters all dashboards that a user has favored
|
||||
"""
|
||||
|
||||
arg_name = "dashboard_is_fav"
|
||||
class_name = "Dashboard"
|
||||
model = Dashboard
|
||||
|
||||
|
||||
class DashboardFilter(BaseFilter): # pylint: disable=too-few-public-methods
|
||||
"""
|
||||
List dashboards with the following criteria:
|
||||
|
|
|
|||
|
|
@ -182,6 +182,9 @@ class SavedQuery(Model, AuditMixinNullable, ExtraJSONMixin):
|
|||
backref=backref("saved_queries", cascade="all, delete-orphan"),
|
||||
)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return str(self.label)
|
||||
|
||||
@property
|
||||
def pop_tab_link(self) -> Markup:
|
||||
return Markup(
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ from superset.queries.saved_queries.commands.exceptions import (
|
|||
)
|
||||
from superset.queries.saved_queries.filters import (
|
||||
SavedQueryAllTextFilter,
|
||||
SavedQueryFavoriteFilter,
|
||||
SavedQueryFilter,
|
||||
)
|
||||
from superset.queries.saved_queries.schemas import (
|
||||
|
|
@ -101,7 +102,11 @@ class SavedQueryRestApi(BaseSupersetModelRestApi):
|
|||
"changed_on_delta_humanized",
|
||||
]
|
||||
|
||||
search_filters = {"label": [SavedQueryAllTextFilter]}
|
||||
search_columns = ["id", "label"]
|
||||
search_filters = {
|
||||
"id": [SavedQueryFavoriteFilter],
|
||||
"label": [SavedQueryAllTextFilter],
|
||||
}
|
||||
|
||||
apispec_parameter_schemas = {
|
||||
"get_delete_ids_schema": get_delete_ids_schema,
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ from sqlalchemy.orm.query import Query
|
|||
|
||||
from superset.models.sql_lab import SavedQuery
|
||||
from superset.views.base import BaseFilter
|
||||
from superset.views.base_api import BaseFavoriteFilter
|
||||
|
||||
|
||||
class SavedQueryAllTextFilter(BaseFilter): # pylint: disable=too-few-public-methods
|
||||
|
|
@ -44,6 +45,19 @@ class SavedQueryAllTextFilter(BaseFilter): # pylint: disable=too-few-public-met
|
|||
)
|
||||
|
||||
|
||||
class SavedQueryFavoriteFilter(
|
||||
BaseFavoriteFilter
|
||||
): # pylint: disable=too-few-public-methods
|
||||
"""
|
||||
Custom filter for the GET list that filters all saved queries that a user has
|
||||
favored
|
||||
"""
|
||||
|
||||
arg_name = "saved_query_is_fav"
|
||||
class_name = "query"
|
||||
model = SavedQuery
|
||||
|
||||
|
||||
class SavedQueryFilter(BaseFilter): # pylint: disable=too-few-public-methods
|
||||
def apply(self, query: BaseQuery, value: Any) -> BaseQuery:
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -20,15 +20,22 @@ from typing import Any, Callable, cast, Dict, List, Optional, Set, Tuple, Type,
|
|||
|
||||
from apispec import APISpec
|
||||
from apispec.exceptions import DuplicateComponentNameError
|
||||
from flask import Blueprint, Response
|
||||
from flask import Blueprint, g, Response
|
||||
from flask_appbuilder import AppBuilder, ModelRestApi
|
||||
from flask_appbuilder.api import expose, protect, rison, safe
|
||||
from flask_appbuilder.models.filters import BaseFilter, Filters
|
||||
from flask_appbuilder.models.sqla.filters import FilterStartsWith
|
||||
from flask_appbuilder.models.sqla.interface import SQLAInterface
|
||||
from flask_babel import lazy_gettext as _
|
||||
from marshmallow import fields, Schema
|
||||
from sqlalchemy import distinct, func
|
||||
from sqlalchemy import and_, distinct, func
|
||||
from sqlalchemy.orm.query import Query
|
||||
|
||||
from superset.extensions import db, security_manager
|
||||
from superset.models.core import FavStar
|
||||
from superset.models.dashboard import Dashboard
|
||||
from superset.models.slice import Slice
|
||||
from superset.sql_lab import Query as SqllabQuery
|
||||
from superset.stats_logger import BaseStatsLogger
|
||||
from superset.typing import FlaskResponse
|
||||
from superset.utils.core import time_function
|
||||
|
|
@ -84,6 +91,31 @@ class RelatedFieldFilter:
|
|||
self.filter_class = filter_class
|
||||
|
||||
|
||||
class BaseFavoriteFilter(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 favorite")
|
||||
arg_name = ""
|
||||
class_name = ""
|
||||
""" The FavStar class_name to user """
|
||||
model: Type[Union[Dashboard, Slice, SqllabQuery]] = Dashboard
|
||||
""" The SQLAlchemy model """
|
||||
|
||||
def apply(self, query: Query, value: Any) -> Query:
|
||||
# If anonymous user filter nothing
|
||||
if security_manager.current_user is None:
|
||||
return query
|
||||
users_favorite_query = db.session.query(FavStar.obj_id).filter(
|
||||
and_(FavStar.user_id == g.user.id, FavStar.class_name == self.class_name)
|
||||
)
|
||||
if value:
|
||||
return query.filter(and_(self.model.id.in_(users_favorite_query)))
|
||||
return query.filter(and_(~self.model.id.in_(users_favorite_query)))
|
||||
|
||||
|
||||
class BaseSupersetModelRestApi(ModelRestApi):
|
||||
"""
|
||||
Extends FAB's ModelResApi to implement specific superset generic functionality
|
||||
|
|
|
|||
|
|
@ -24,12 +24,14 @@ from unittest import mock
|
|||
import humanize
|
||||
import prison
|
||||
import pytest
|
||||
from sqlalchemy import and_
|
||||
from sqlalchemy.sql import func
|
||||
|
||||
from superset.utils.core import get_example_database
|
||||
from tests.test_app import app
|
||||
from superset.connectors.connector_registry import ConnectorRegistry
|
||||
from superset.extensions import db, security_manager
|
||||
from superset.models.core import FavStar
|
||||
from superset.models.dashboard import Dashboard
|
||||
from superset.models.slice import Slice
|
||||
from superset.utils import core as utils
|
||||
|
|
@ -38,6 +40,7 @@ from tests.base_tests import SupersetTestCase
|
|||
from tests.fixtures.query_context import get_query_context
|
||||
|
||||
CHART_DATA_URI = "api/v1/chart/data"
|
||||
CHARTS_FIXTURE_COUNT = 10
|
||||
|
||||
|
||||
class TestChartApi(SupersetTestCase, ApiOwnersTestCaseMixin):
|
||||
|
|
@ -78,6 +81,30 @@ class TestChartApi(SupersetTestCase, ApiOwnersTestCaseMixin):
|
|||
db.session.commit()
|
||||
return slice
|
||||
|
||||
@pytest.fixture()
|
||||
def create_charts(self):
|
||||
with self.create_app().app_context():
|
||||
charts = []
|
||||
admin = self.get_user("admin")
|
||||
for cx in range(CHARTS_FIXTURE_COUNT - 1):
|
||||
charts.append(self.insert_chart(f"name{cx}", [admin.id], 1))
|
||||
fav_charts = []
|
||||
for cx in range(round(CHARTS_FIXTURE_COUNT / 2)):
|
||||
fav_star = FavStar(
|
||||
user_id=admin.id, class_name="slice", obj_id=charts[cx].id
|
||||
)
|
||||
db.session.add(fav_star)
|
||||
db.session.commit()
|
||||
fav_charts.append(fav_star)
|
||||
yield charts
|
||||
|
||||
# rollback changes
|
||||
for chart in charts:
|
||||
db.session.delete(chart)
|
||||
for fav_chart in fav_charts:
|
||||
db.session.delete(fav_chart)
|
||||
db.session.commit()
|
||||
|
||||
def test_delete_chart(self):
|
||||
"""
|
||||
Chart API: Test delete
|
||||
|
|
@ -659,6 +686,53 @@ class TestChartApi(SupersetTestCase, ApiOwnersTestCaseMixin):
|
|||
db.session.delete(chart5)
|
||||
db.session.commit()
|
||||
|
||||
@pytest.mark.usefixtures("create_charts")
|
||||
def test_get_charts_favorite_filter(self):
|
||||
"""
|
||||
Chart API: Test get charts favorite filter
|
||||
"""
|
||||
admin = self.get_user("admin")
|
||||
users_favorite_query = db.session.query(FavStar.obj_id).filter(
|
||||
and_(FavStar.user_id == admin.id, FavStar.class_name == "slice")
|
||||
)
|
||||
expected_models = (
|
||||
db.session.query(Slice)
|
||||
.filter(and_(Slice.id.in_(users_favorite_query)))
|
||||
.order_by(Slice.slice_name.asc())
|
||||
.all()
|
||||
)
|
||||
|
||||
arguments = {
|
||||
"filters": [{"col": "id", "opr": "chart_is_fav", "value": True}],
|
||||
"order_column": "slice_name",
|
||||
"order_direction": "asc",
|
||||
"keys": ["none"],
|
||||
"columns": ["slice_name"],
|
||||
}
|
||||
self.login(username="admin")
|
||||
uri = f"api/v1/chart/?q={prison.dumps(arguments)}"
|
||||
rv = self.client.get(uri)
|
||||
data = json.loads(rv.data.decode("utf-8"))
|
||||
assert rv.status_code == 200
|
||||
assert len(expected_models) == data["count"]
|
||||
|
||||
for i, expected_model in enumerate(expected_models):
|
||||
assert expected_model.slice_name == data["result"][i]["slice_name"]
|
||||
|
||||
# Test not favorite charts
|
||||
expected_models = (
|
||||
db.session.query(Slice)
|
||||
.filter(and_(~Slice.id.in_(users_favorite_query)))
|
||||
.order_by(Slice.slice_name.asc())
|
||||
.all()
|
||||
)
|
||||
arguments["filters"][0]["value"] = False
|
||||
uri = f"api/v1/chart/?q={prison.dumps(arguments)}"
|
||||
rv = self.client.get(uri)
|
||||
data = json.loads(rv.data.decode("utf-8"))
|
||||
assert rv.status_code == 200
|
||||
assert len(expected_models) == data["count"]
|
||||
|
||||
def test_get_charts_page(self):
|
||||
"""
|
||||
Chart API: Test get charts filter
|
||||
|
|
|
|||
|
|
@ -18,15 +18,16 @@
|
|||
"""Unit tests for Superset"""
|
||||
import json
|
||||
from typing import List, Optional
|
||||
from datetime import datetime
|
||||
|
||||
import pytest
|
||||
import prison
|
||||
import humanize
|
||||
from sqlalchemy.sql import func
|
||||
|
||||
import tests.test_app
|
||||
from sqlalchemy import and_
|
||||
from superset import db, security_manager
|
||||
from superset.models.dashboard import Dashboard
|
||||
from superset.models.core import FavStar
|
||||
from superset.models.slice import Slice
|
||||
from superset.views.base import generate_download_headers
|
||||
|
||||
|
|
@ -34,6 +35,9 @@ from tests.base_api_tests import ApiOwnersTestCaseMixin
|
|||
from tests.base_tests import SupersetTestCase
|
||||
|
||||
|
||||
DASHBOARDS_FIXTURE_COUNT = 10
|
||||
|
||||
|
||||
class TestDashboardApi(SupersetTestCase, ApiOwnersTestCaseMixin):
|
||||
resource_name = "dashboard"
|
||||
|
||||
|
|
@ -78,6 +82,32 @@ class TestDashboardApi(SupersetTestCase, ApiOwnersTestCaseMixin):
|
|||
db.session.commit()
|
||||
return dashboard
|
||||
|
||||
@pytest.fixture()
|
||||
def create_dashboards(self):
|
||||
with self.create_app().app_context():
|
||||
dashboards = []
|
||||
admin = self.get_user("admin")
|
||||
for cx in range(DASHBOARDS_FIXTURE_COUNT - 1):
|
||||
dashboards.append(
|
||||
self.insert_dashboard(f"title{cx}", f"slug{cx}", [admin.id])
|
||||
)
|
||||
fav_dashboards = []
|
||||
for cx in range(round(DASHBOARDS_FIXTURE_COUNT / 2)):
|
||||
fav_star = FavStar(
|
||||
user_id=admin.id, class_name="Dashboard", obj_id=dashboards[cx].id
|
||||
)
|
||||
db.session.add(fav_star)
|
||||
db.session.commit()
|
||||
fav_dashboards.append(fav_star)
|
||||
yield dashboards
|
||||
|
||||
# rollback changes
|
||||
for dashboard in dashboards:
|
||||
db.session.delete(dashboard)
|
||||
for fav_dashboard in fav_dashboards:
|
||||
db.session.delete(fav_dashboard)
|
||||
db.session.commit()
|
||||
|
||||
def test_get_dashboard(self):
|
||||
"""
|
||||
Dashboard API: Test get dashboard
|
||||
|
|
@ -223,19 +253,15 @@ class TestDashboardApi(SupersetTestCase, ApiOwnersTestCaseMixin):
|
|||
db.session.delete(dashboard)
|
||||
db.session.commit()
|
||||
|
||||
def test_get_dashboards_custom_filter(self):
|
||||
@pytest.mark.usefixtures("create_dashboards")
|
||||
def test_get_dashboards_title_or_slug_filter(self):
|
||||
"""
|
||||
Dashboard API: Test get dashboards custom filter
|
||||
Dashboard API: Test get dashboards title or slug filter
|
||||
"""
|
||||
admin = self.get_user("admin")
|
||||
dashboard1 = self.insert_dashboard("foo_a", "ZY_bar", [admin.id])
|
||||
dashboard2 = self.insert_dashboard("zy_foo", "slug1", [admin.id])
|
||||
dashboard3 = self.insert_dashboard("foo_b", "slug1zy_", [admin.id])
|
||||
dashboard4 = self.insert_dashboard("bar", "foo", [admin.id])
|
||||
|
||||
# Test title filter with ilike
|
||||
arguments = {
|
||||
"filters": [
|
||||
{"col": "dashboard_title", "opr": "title_or_slug", "value": "zy_"}
|
||||
{"col": "dashboard_title", "opr": "title_or_slug", "value": "title1"}
|
||||
],
|
||||
"order_column": "dashboard_title",
|
||||
"order_direction": "asc",
|
||||
|
|
@ -247,18 +273,25 @@ class TestDashboardApi(SupersetTestCase, ApiOwnersTestCaseMixin):
|
|||
rv = self.client.get(uri)
|
||||
self.assertEqual(rv.status_code, 200)
|
||||
data = json.loads(rv.data.decode("utf-8"))
|
||||
self.assertEqual(data["count"], 3)
|
||||
self.assertEqual(data["count"], 1)
|
||||
|
||||
expected_response = [
|
||||
{"slug": "ZY_bar", "dashboard_title": "foo_a"},
|
||||
{"slug": "slug1zy_", "dashboard_title": "foo_b"},
|
||||
{"slug": "slug1", "dashboard_title": "zy_foo"},
|
||||
{"slug": "slug1", "dashboard_title": "title1"},
|
||||
]
|
||||
for index, item in enumerate(data["result"]):
|
||||
self.assertEqual(item["slug"], expected_response[index]["slug"])
|
||||
self.assertEqual(
|
||||
item["dashboard_title"], expected_response[index]["dashboard_title"]
|
||||
)
|
||||
assert data["result"] == expected_response
|
||||
|
||||
# Test slug filter with ilike
|
||||
arguments["filters"][0]["value"] = "slug2"
|
||||
uri = f"api/v1/dashboard/?q={prison.dumps(arguments)}"
|
||||
rv = self.client.get(uri)
|
||||
self.assertEqual(rv.status_code, 200)
|
||||
data = json.loads(rv.data.decode("utf-8"))
|
||||
self.assertEqual(data["count"], 1)
|
||||
|
||||
expected_response = [
|
||||
{"slug": "slug2", "dashboard_title": "title2"},
|
||||
]
|
||||
assert data["result"] == expected_response
|
||||
|
||||
self.logout()
|
||||
self.login(username="gamma")
|
||||
|
|
@ -268,12 +301,73 @@ class TestDashboardApi(SupersetTestCase, ApiOwnersTestCaseMixin):
|
|||
data = json.loads(rv.data.decode("utf-8"))
|
||||
self.assertEqual(data["count"], 0)
|
||||
|
||||
# rollback changes
|
||||
db.session.delete(dashboard1)
|
||||
db.session.delete(dashboard2)
|
||||
db.session.delete(dashboard3)
|
||||
db.session.delete(dashboard4)
|
||||
db.session.commit()
|
||||
@pytest.mark.usefixtures("create_dashboards")
|
||||
def test_get_dashboards_favorite_filter(self):
|
||||
"""
|
||||
Dashboard API: Test get dashboards favorite filter
|
||||
"""
|
||||
admin = self.get_user("admin")
|
||||
users_favorite_query = db.session.query(FavStar.obj_id).filter(
|
||||
and_(FavStar.user_id == admin.id, FavStar.class_name == "Dashboard")
|
||||
)
|
||||
expected_models = (
|
||||
db.session.query(Dashboard)
|
||||
.filter(and_(Dashboard.id.in_(users_favorite_query)))
|
||||
.order_by(Dashboard.dashboard_title.asc())
|
||||
.all()
|
||||
)
|
||||
|
||||
arguments = {
|
||||
"filters": [{"col": "id", "opr": "dashboard_is_fav", "value": True}],
|
||||
"order_column": "dashboard_title",
|
||||
"order_direction": "asc",
|
||||
"keys": ["none"],
|
||||
"columns": ["dashboard_title"],
|
||||
}
|
||||
self.login(username="admin")
|
||||
uri = f"api/v1/dashboard/?q={prison.dumps(arguments)}"
|
||||
rv = self.client.get(uri)
|
||||
assert rv.status_code == 200
|
||||
data = json.loads(rv.data.decode("utf-8"))
|
||||
assert len(expected_models) == data["count"]
|
||||
|
||||
for i, expected_model in enumerate(expected_models):
|
||||
assert (
|
||||
expected_model.dashboard_title == data["result"][i]["dashboard_title"]
|
||||
)
|
||||
|
||||
@pytest.mark.usefixtures("create_dashboards")
|
||||
def test_get_dashboards_not_favorite_filter(self):
|
||||
"""
|
||||
Dashboard API: Test get dashboards not favorite filter
|
||||
"""
|
||||
admin = self.get_user("admin")
|
||||
users_favorite_query = db.session.query(FavStar.obj_id).filter(
|
||||
and_(FavStar.user_id == admin.id, FavStar.class_name == "Dashboard")
|
||||
)
|
||||
expected_models = (
|
||||
db.session.query(Dashboard)
|
||||
.filter(and_(~Dashboard.id.in_(users_favorite_query)))
|
||||
.order_by(Dashboard.dashboard_title.asc())
|
||||
.all()
|
||||
)
|
||||
arguments = {
|
||||
"filters": [{"col": "id", "opr": "dashboard_is_fav", "value": False}],
|
||||
"order_column": "dashboard_title",
|
||||
"order_direction": "asc",
|
||||
"keys": ["none"],
|
||||
"columns": ["dashboard_title"],
|
||||
}
|
||||
uri = f"api/v1/dashboard/?q={prison.dumps(arguments)}"
|
||||
self.login(username="admin")
|
||||
rv = self.client.get(uri)
|
||||
data = json.loads(rv.data.decode("utf-8"))
|
||||
assert rv.status_code == 200
|
||||
assert len(expected_models) == data["count"]
|
||||
for i, expected_model in enumerate(expected_models):
|
||||
assert (
|
||||
expected_model.dashboard_title == data["result"][i]["dashboard_title"]
|
||||
)
|
||||
|
||||
def test_get_dashboards_no_data_access(self):
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -21,18 +21,19 @@ from typing import Optional
|
|||
|
||||
import pytest
|
||||
import prison
|
||||
from sqlalchemy.sql import func, asc
|
||||
from sqlalchemy.sql import func, and_
|
||||
|
||||
import tests.test_app
|
||||
from superset import db, security_manager
|
||||
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.utils.core import get_example_database
|
||||
|
||||
from tests.base_tests import SupersetTestCase
|
||||
|
||||
|
||||
SAVED_QUERIES_FIXTURE_COUNT = 5
|
||||
SAVED_QUERIES_FIXTURE_COUNT = 10
|
||||
|
||||
|
||||
class TestSavedQueryApi(SupersetTestCase):
|
||||
|
|
@ -78,6 +79,7 @@ class TestSavedQueryApi(SupersetTestCase):
|
|||
def create_saved_queries(self):
|
||||
with self.create_app().app_context():
|
||||
saved_queries = []
|
||||
admin = self.get_user("admin")
|
||||
for cx in range(SAVED_QUERIES_FIXTURE_COUNT - 1):
|
||||
saved_queries.append(
|
||||
self.insert_default_saved_query(
|
||||
|
|
@ -92,11 +94,22 @@ class TestSavedQueryApi(SupersetTestCase):
|
|||
)
|
||||
)
|
||||
|
||||
fav_saved_queries = []
|
||||
for cx in range(round(SAVED_QUERIES_FIXTURE_COUNT / 2)):
|
||||
fav_star = FavStar(
|
||||
user_id=admin.id, class_name="query", obj_id=saved_queries[cx].id
|
||||
)
|
||||
db.session.add(fav_star)
|
||||
db.session.commit()
|
||||
fav_saved_queries.append(fav_star)
|
||||
|
||||
yield saved_queries
|
||||
|
||||
# rollback changes
|
||||
for saved_query in saved_queries:
|
||||
db.session.delete(saved_query)
|
||||
for fav_saved_query in fav_saved_queries:
|
||||
db.session.delete(fav_saved_query)
|
||||
db.session.commit()
|
||||
|
||||
@pytest.mark.usefixtures("create_saved_queries")
|
||||
|
|
@ -290,6 +303,58 @@ class TestSavedQueryApi(SupersetTestCase):
|
|||
data = json.loads(rv.data.decode("utf-8"))
|
||||
assert data["count"] == len(all_queries)
|
||||
|
||||
@pytest.mark.usefixtures("create_saved_queries")
|
||||
def test_get_saved_query_favorite_filter(self):
|
||||
"""
|
||||
SavedQuery API: Test get saved queries favorite filter
|
||||
"""
|
||||
admin = self.get_user("admin")
|
||||
users_favorite_query = db.session.query(FavStar.obj_id).filter(
|
||||
and_(FavStar.user_id == admin.id, FavStar.class_name == "query")
|
||||
)
|
||||
expected_models = (
|
||||
db.session.query(SavedQuery)
|
||||
.filter(and_(SavedQuery.id.in_(users_favorite_query)))
|
||||
.order_by(SavedQuery.label.asc())
|
||||
.all()
|
||||
)
|
||||
|
||||
arguments = {
|
||||
"filters": [{"col": "id", "opr": "saved_query_is_fav", "value": True}],
|
||||
"order_column": "label",
|
||||
"order_direction": "asc",
|
||||
"keys": ["none"],
|
||||
"columns": ["label"],
|
||||
}
|
||||
self.login(username="admin")
|
||||
uri = f"api/v1/saved_query/?q={prison.dumps(arguments)}"
|
||||
rv = self.client.get(uri)
|
||||
data = json.loads(rv.data.decode("utf-8"))
|
||||
assert rv.status_code == 200
|
||||
assert len(expected_models) == data["count"]
|
||||
|
||||
for i, expected_model in enumerate(expected_models):
|
||||
assert expected_model.label == data["result"][i]["label"]
|
||||
|
||||
# Test not favorite saves queries
|
||||
expected_models = (
|
||||
db.session.query(SavedQuery)
|
||||
.filter(
|
||||
and_(
|
||||
~SavedQuery.id.in_(users_favorite_query),
|
||||
SavedQuery.created_by == admin,
|
||||
)
|
||||
)
|
||||
.order_by(SavedQuery.label.asc())
|
||||
.all()
|
||||
)
|
||||
arguments["filters"][0]["value"] = False
|
||||
uri = f"api/v1/saved_query/?q={prison.dumps(arguments)}"
|
||||
rv = self.client.get(uri)
|
||||
data = json.loads(rv.data.decode("utf-8"))
|
||||
assert rv.status_code == 200
|
||||
assert len(expected_models) == data["count"]
|
||||
|
||||
def test_info_saved_query(self):
|
||||
"""
|
||||
SavedQuery API: Test info
|
||||
|
|
|
|||
Loading…
Reference in New Issue