mirror of
https://github.com/apache/superset.git
synced 2026-04-21 09:04:38 +00:00
feat: Update Tags CRUD API (#24839)
This commit is contained in:
@@ -53,6 +53,7 @@ TAGS_LIST_COLUMNS = [
|
||||
"id",
|
||||
"name",
|
||||
"type",
|
||||
"description",
|
||||
"changed_by.first_name",
|
||||
"changed_by.last_name",
|
||||
"changed_on_delta_humanized",
|
||||
@@ -457,3 +458,46 @@ class TestTagApi(SupersetTestCase):
|
||||
rv = self.client.delete(uri, follow_redirects=True)
|
||||
|
||||
self.assertEqual(rv.status_code, 422)
|
||||
|
||||
@pytest.mark.usefixtures("load_world_bank_dashboard_with_slices")
|
||||
def test_post_tag(self):
|
||||
self.login(username="admin")
|
||||
uri = f"api/v1/tag/"
|
||||
dashboard = (
|
||||
db.session.query(Dashboard)
|
||||
.filter(Dashboard.dashboard_title == "World Bank's Data")
|
||||
.first()
|
||||
)
|
||||
rv = self.client.post(
|
||||
uri,
|
||||
json={"name": "my_tag", "objects_to_tag": [["dashboard", dashboard.id]]},
|
||||
)
|
||||
|
||||
self.assertEqual(rv.status_code, 201)
|
||||
user_id = self.get_user(username="admin").get_id()
|
||||
tag = (
|
||||
db.session.query(Tag)
|
||||
.filter(Tag.name == "my_tag", Tag.type == TagTypes.custom)
|
||||
.one_or_none()
|
||||
)
|
||||
assert tag is not None
|
||||
|
||||
@pytest.mark.usefixtures("load_world_bank_dashboard_with_slices")
|
||||
@pytest.mark.usefixtures("create_tags")
|
||||
def test_put_tag(self):
|
||||
self.login(username="admin")
|
||||
|
||||
tag_to_update = db.session.query(Tag).first()
|
||||
uri = f"api/v1/tag/{tag_to_update.id}"
|
||||
rv = self.client.put(
|
||||
uri, json={"name": "new_name", "description": "new description"}
|
||||
)
|
||||
|
||||
self.assertEqual(rv.status_code, 200)
|
||||
|
||||
tag = (
|
||||
db.session.query(Tag)
|
||||
.filter(Tag.name == "new_name", Tag.description == "new description")
|
||||
.one_or_none()
|
||||
)
|
||||
assert tag is not None
|
||||
|
||||
@@ -144,3 +144,31 @@ def test_user_favorite_tag_exc_raise(mocker):
|
||||
mock_session.commit.side_effect = Exception("DB Error")
|
||||
with pytest.raises(Exception):
|
||||
TagDAO.remove_user_favorite_tag(1)
|
||||
|
||||
|
||||
def test_create_tag_relationship(mocker):
|
||||
from superset.daos.tag import TagDAO
|
||||
from superset.tags.models import ( # Assuming these are defined in the same module
|
||||
ObjectTypes,
|
||||
TaggedObject,
|
||||
)
|
||||
|
||||
mock_session = mocker.patch("superset.daos.tag.db.session")
|
||||
|
||||
# Define a list of objects to tag
|
||||
objects_to_tag = [
|
||||
(ObjectTypes.query, 1),
|
||||
(ObjectTypes.chart, 2),
|
||||
(ObjectTypes.dashboard, 3),
|
||||
]
|
||||
|
||||
# Call the function
|
||||
tag = TagDAO.get_by_name("test_tag")
|
||||
TagDAO.create_tag_relationship(objects_to_tag, tag)
|
||||
|
||||
# Verify that the correct number of TaggedObjects are added to the session
|
||||
assert mock_session.add_all.call_count == 1
|
||||
assert len(mock_session.add_all.call_args[0][0]) == len(objects_to_tag)
|
||||
|
||||
# Verify that commit is called
|
||||
mock_session.commit.assert_called_once()
|
||||
|
||||
0
tests/unit_tests/tags/__init__.py
Normal file
0
tests/unit_tests/tags/__init__.py
Normal file
110
tests/unit_tests/tags/commands/create_test.py
Normal file
110
tests/unit_tests/tags/commands/create_test.py
Normal file
@@ -0,0 +1,110 @@
|
||||
import pytest
|
||||
from sqlalchemy.orm.session import Session
|
||||
|
||||
from superset.utils.core import DatasourceType
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def session_with_data(session: Session):
|
||||
from superset.connectors.sqla.models import SqlaTable, TableColumn
|
||||
from superset.models.core import Database
|
||||
from superset.models.dashboard import Dashboard
|
||||
from superset.models.slice import Slice
|
||||
from superset.models.sql_lab import Query, SavedQuery
|
||||
|
||||
engine = session.get_bind()
|
||||
SqlaTable.metadata.create_all(engine) # pylint: disable=no-member
|
||||
|
||||
slice_obj = Slice(
|
||||
id=1,
|
||||
datasource_id=1,
|
||||
datasource_type=DatasourceType.TABLE,
|
||||
datasource_name="tmp_perm_table",
|
||||
slice_name="slice_name",
|
||||
)
|
||||
|
||||
db = Database(database_name="my_database", sqlalchemy_uri="postgresql://")
|
||||
|
||||
columns = [
|
||||
TableColumn(column_name="a", type="INTEGER"),
|
||||
]
|
||||
|
||||
saved_query = SavedQuery(label="test_query", database=db, sql="select * from foo")
|
||||
|
||||
dashboard_obj = Dashboard(
|
||||
id=100,
|
||||
dashboard_title="test_dashboard",
|
||||
slug="test_slug",
|
||||
slices=[],
|
||||
published=True,
|
||||
)
|
||||
|
||||
session.add(slice_obj)
|
||||
session.add(db)
|
||||
session.add(saved_query)
|
||||
session.add(dashboard_obj)
|
||||
session.commit()
|
||||
yield session
|
||||
|
||||
|
||||
def test_create_command_success(session_with_data: Session):
|
||||
from superset.connectors.sqla.models import SqlaTable
|
||||
from superset.daos.tag import TagDAO
|
||||
from superset.models.dashboard import Dashboard
|
||||
from superset.models.slice import Slice
|
||||
from superset.models.sql_lab import Query, SavedQuery
|
||||
from superset.tags.commands.create import CreateCustomTagWithRelationshipsCommand
|
||||
from superset.tags.models import ObjectTypes, TaggedObject
|
||||
|
||||
# Define a list of objects to tag
|
||||
query = session_with_data.query(SavedQuery).first()
|
||||
chart = session_with_data.query(Slice).first()
|
||||
dashboard = session_with_data.query(Dashboard).first()
|
||||
|
||||
objects_to_tag = [
|
||||
(ObjectTypes.query, query.id),
|
||||
(ObjectTypes.chart, chart.id),
|
||||
(ObjectTypes.dashboard, dashboard.id),
|
||||
]
|
||||
|
||||
CreateCustomTagWithRelationshipsCommand(
|
||||
data={"name": "test_tag", "objects_to_tag": objects_to_tag}
|
||||
).run()
|
||||
|
||||
assert len(session_with_data.query(TaggedObject).all()) == len(objects_to_tag)
|
||||
for object_type, object_id in objects_to_tag:
|
||||
assert (
|
||||
session_with_data.query(TaggedObject)
|
||||
.filter(
|
||||
TaggedObject.object_type == object_type,
|
||||
TaggedObject.object_id == object_id,
|
||||
)
|
||||
.one_or_none()
|
||||
is not None
|
||||
)
|
||||
|
||||
|
||||
def test_create_command_failed_validate(session_with_data: Session):
|
||||
from superset.connectors.sqla.models import SqlaTable
|
||||
from superset.daos.tag import TagDAO
|
||||
from superset.models.dashboard import Dashboard
|
||||
from superset.models.slice import Slice
|
||||
from superset.models.sql_lab import Query, SavedQuery
|
||||
from superset.tags.commands.create import CreateCustomTagWithRelationshipsCommand
|
||||
from superset.tags.commands.exceptions import TagInvalidError
|
||||
from superset.tags.models import ObjectTypes, TaggedObject
|
||||
|
||||
query = session_with_data.query(SavedQuery).first()
|
||||
chart = session_with_data.query(Slice).first()
|
||||
dashboard = session_with_data.query(Dashboard).first()
|
||||
|
||||
objects_to_tag = [
|
||||
(ObjectTypes.query, query.id),
|
||||
(ObjectTypes.chart, chart.id),
|
||||
(ObjectTypes.dashboard, 0),
|
||||
]
|
||||
|
||||
with pytest.raises(TagInvalidError):
|
||||
CreateCustomTagWithRelationshipsCommand(
|
||||
data={"name": "test_tag", "objects_to_tag": objects_to_tag}
|
||||
).run()
|
||||
160
tests/unit_tests/tags/commands/update_test.py
Normal file
160
tests/unit_tests/tags/commands/update_test.py
Normal file
@@ -0,0 +1,160 @@
|
||||
import pytest
|
||||
from sqlalchemy.orm.session import Session
|
||||
|
||||
from superset.utils.core import DatasourceType
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def session_with_data(session: Session):
|
||||
from superset.connectors.sqla.models import SqlaTable, TableColumn
|
||||
from superset.models.core import Database
|
||||
from superset.models.dashboard import Dashboard
|
||||
from superset.models.slice import Slice
|
||||
from superset.models.sql_lab import Query, SavedQuery
|
||||
from superset.tags.models import Tag
|
||||
|
||||
engine = session.get_bind()
|
||||
Tag.metadata.create_all(engine) # pylint: disable=no-member
|
||||
|
||||
slice_obj = Slice(
|
||||
id=1,
|
||||
datasource_id=1,
|
||||
datasource_type=DatasourceType.TABLE,
|
||||
datasource_name="tmp_perm_table",
|
||||
slice_name="slice_name",
|
||||
)
|
||||
|
||||
db = Database(database_name="my_database", sqlalchemy_uri="postgresql://")
|
||||
|
||||
columns = [
|
||||
TableColumn(column_name="a", type="INTEGER"),
|
||||
]
|
||||
|
||||
sqla_table = SqlaTable(
|
||||
table_name="my_sqla_table",
|
||||
columns=columns,
|
||||
metrics=[],
|
||||
database=db,
|
||||
)
|
||||
|
||||
dashboard_obj = Dashboard(
|
||||
id=100,
|
||||
dashboard_title="test_dashboard",
|
||||
slug="test_slug",
|
||||
slices=[],
|
||||
published=True,
|
||||
)
|
||||
|
||||
saved_query = SavedQuery(label="test_query", database=db, sql="select * from foo")
|
||||
|
||||
tag = Tag(name="test_name", description="test_description")
|
||||
|
||||
session.add(slice_obj)
|
||||
session.add(dashboard_obj)
|
||||
session.add(tag)
|
||||
session.commit()
|
||||
yield session
|
||||
|
||||
|
||||
def test_update_command_success(session_with_data: Session):
|
||||
from superset.daos.tag import TagDAO
|
||||
from superset.models.dashboard import Dashboard
|
||||
from superset.tags.commands.update import UpdateTagCommand
|
||||
from superset.tags.models import ObjectTypes, TaggedObject
|
||||
|
||||
dashboard = session_with_data.query(Dashboard).first()
|
||||
|
||||
objects_to_tag = [
|
||||
(ObjectTypes.dashboard, dashboard.id),
|
||||
]
|
||||
|
||||
tag_to_update = TagDAO.find_by_name("test_name")
|
||||
changed_model = UpdateTagCommand(
|
||||
tag_to_update.id,
|
||||
{
|
||||
"name": "new_name",
|
||||
"description": "new_description",
|
||||
"objects_to_tag": objects_to_tag,
|
||||
},
|
||||
).run()
|
||||
|
||||
updated_tag = TagDAO.find_by_name("new_name")
|
||||
assert updated_tag is not None
|
||||
assert updated_tag.description == "new_description"
|
||||
assert len(session_with_data.query(TaggedObject).all()) == len(objects_to_tag)
|
||||
|
||||
|
||||
def test_update_command_success_duplicates(session_with_data: Session):
|
||||
from superset.daos.tag import TagDAO
|
||||
from superset.models.dashboard import Dashboard
|
||||
from superset.models.slice import Slice
|
||||
from superset.tags.commands.create import CreateCustomTagWithRelationshipsCommand
|
||||
from superset.tags.commands.update import UpdateTagCommand
|
||||
from superset.tags.models import ObjectTypes, TaggedObject
|
||||
|
||||
dashboard = session_with_data.query(Dashboard).first()
|
||||
chart = session_with_data.query(Slice).first()
|
||||
|
||||
objects_to_tag = [
|
||||
(ObjectTypes.dashboard, dashboard.id),
|
||||
]
|
||||
|
||||
CreateCustomTagWithRelationshipsCommand(
|
||||
data={"name": "test_tag", "objects_to_tag": objects_to_tag}
|
||||
).run()
|
||||
|
||||
tag_to_update = TagDAO.find_by_name("test_tag")
|
||||
|
||||
objects_to_tag = [
|
||||
(ObjectTypes.chart, chart.id),
|
||||
]
|
||||
changed_model = UpdateTagCommand(
|
||||
tag_to_update.id,
|
||||
{
|
||||
"name": "new_name",
|
||||
"description": "new_description",
|
||||
"objects_to_tag": objects_to_tag,
|
||||
},
|
||||
).run()
|
||||
|
||||
updated_tag = TagDAO.find_by_name("new_name")
|
||||
assert updated_tag is not None
|
||||
assert updated_tag.description == "new_description"
|
||||
assert len(session_with_data.query(TaggedObject).all()) == len(objects_to_tag)
|
||||
assert changed_model.objects[0].object_id == chart.id
|
||||
|
||||
|
||||
def test_update_command_failed_validation(session_with_data: Session):
|
||||
from superset.daos.tag import TagDAO
|
||||
from superset.models.dashboard import Dashboard
|
||||
from superset.models.slice import Slice
|
||||
from superset.tags.commands.create import CreateCustomTagWithRelationshipsCommand
|
||||
from superset.tags.commands.exceptions import TagInvalidError
|
||||
from superset.tags.commands.update import UpdateTagCommand
|
||||
from superset.tags.models import ObjectTypes, TaggedObject
|
||||
|
||||
dashboard = session_with_data.query(Dashboard).first()
|
||||
chart = session_with_data.query(Slice).first()
|
||||
objects_to_tag = [
|
||||
(ObjectTypes.chart, chart.id),
|
||||
]
|
||||
|
||||
CreateCustomTagWithRelationshipsCommand(
|
||||
data={"name": "test_tag", "objects_to_tag": objects_to_tag}
|
||||
).run()
|
||||
|
||||
tag_to_update = TagDAO.find_by_name("test_tag")
|
||||
|
||||
objects_to_tag = [
|
||||
(0, dashboard.id), # type: ignore
|
||||
]
|
||||
|
||||
with pytest.raises(TagInvalidError):
|
||||
UpdateTagCommand(
|
||||
tag_to_update.id,
|
||||
{
|
||||
"name": "new_name",
|
||||
"description": "new_description",
|
||||
"objects_to_tag": objects_to_tag,
|
||||
},
|
||||
).run()
|
||||
Reference in New Issue
Block a user