mirror of
https://github.com/apache/superset.git
synced 2026-04-19 08:04:53 +00:00
feat: create backend routes and API for importing saved queries (#13893)
* initial commit
* revisions
* started tests
* added unit tests
* revisions
* tests passing
* fixed api test
* Update superset/queries/saved_queries/commands/importers/v1/utils.py
Co-authored-by: Hugh A. Miles II <hughmil3s@gmail.com>
* Revert "Update superset/queries/saved_queries/commands/importers/v1/utils.py"
This reverts commit 18580aad1e.
Co-authored-by: Hugh A. Miles II <hughmil3s@gmail.com>
This commit is contained in:
@@ -197,7 +197,7 @@ class TestImportChartsCommand(SupersetTestCase):
|
||||
db.session.commit()
|
||||
|
||||
def test_import_v1_chart_multiple(self):
|
||||
"""Test that a dataset can be imported multiple times"""
|
||||
"""Test that a chart can be imported multiple times"""
|
||||
contents = {
|
||||
"metadata.yaml": yaml.safe_dump(chart_metadata_config),
|
||||
"databases/imported_database.yaml": yaml.safe_dump(database_config),
|
||||
|
||||
15
tests/fixtures/importexport.py
vendored
15
tests/fixtures/importexport.py
vendored
@@ -343,7 +343,11 @@ dashboard_metadata_config: Dict[str, Any] = {
|
||||
"type": "Dashboard",
|
||||
"timestamp": "2020-11-04T21:27:44.423819+00:00",
|
||||
}
|
||||
|
||||
saved_queries_metadata_config: Dict[str, Any] = {
|
||||
"version": "1.0.0",
|
||||
"type": "SavedQuery",
|
||||
"timestamp": "2021-03-30T20:37:54.791187+00:00",
|
||||
}
|
||||
database_config: Dict[str, Any] = {
|
||||
"allow_csv_upload": True,
|
||||
"allow_ctas": True,
|
||||
@@ -499,3 +503,12 @@ dashboard_config = {
|
||||
},
|
||||
"version": "1.0.0",
|
||||
}
|
||||
saved_queries_config = {
|
||||
"schema": "public",
|
||||
"label": "Test Saved Query",
|
||||
"description": None,
|
||||
"sql": "-- Note: Unless you save your query, these tabs will NOT persist if you clear\nyour cookies or change browsers.\n\n\nSELECT * from birth_names",
|
||||
"uuid": "05b679b5-8eaf-452c-b874-a7a774cfa4e9",
|
||||
"version": "1.0.0",
|
||||
"database_uuid": "b8a1ccd3-779d-4ab7-8ad8-9ab119d7fe89",
|
||||
}
|
||||
|
||||
@@ -19,8 +19,9 @@
|
||||
import json
|
||||
from io import BytesIO
|
||||
from typing import Optional
|
||||
from zipfile import is_zipfile
|
||||
from zipfile import is_zipfile, ZipFile
|
||||
|
||||
import yaml
|
||||
import pytest
|
||||
import prison
|
||||
from sqlalchemy.sql import func, and_
|
||||
@@ -33,6 +34,11 @@ from superset.models.sql_lab import SavedQuery
|
||||
from superset.utils.core import get_example_database
|
||||
|
||||
from tests.base_tests import SupersetTestCase
|
||||
from tests.fixtures.importexport import (
|
||||
database_config,
|
||||
saved_queries_config,
|
||||
saved_queries_metadata_config,
|
||||
)
|
||||
|
||||
|
||||
SAVED_QUERIES_FIXTURE_COUNT = 10
|
||||
@@ -745,3 +751,52 @@ class TestSavedQueryApi(SupersetTestCase):
|
||||
uri = f"api/v1/saved_query/export/?q={prison.dumps(argument)}"
|
||||
rv = self.client.get(uri)
|
||||
assert rv.status_code == 404
|
||||
|
||||
def create_saved_query_import(self):
|
||||
buf = BytesIO()
|
||||
with ZipFile(buf, "w") as bundle:
|
||||
with bundle.open("saved_query_export/metadata.yaml", "w") as fp:
|
||||
fp.write(yaml.safe_dump(saved_queries_metadata_config).encode())
|
||||
with bundle.open(
|
||||
"saved_query_export/databases/imported_database.yaml", "w"
|
||||
) as fp:
|
||||
fp.write(yaml.safe_dump(database_config).encode())
|
||||
with bundle.open(
|
||||
"saved_query_export/queries/imported_database/public/imported_saved_query.yaml",
|
||||
"w",
|
||||
) as fp:
|
||||
fp.write(yaml.safe_dump(saved_queries_config).encode())
|
||||
buf.seek(0)
|
||||
return buf
|
||||
|
||||
def test_import_saved_queries(self):
|
||||
"""
|
||||
Saved Query API: Test import
|
||||
"""
|
||||
self.login(username="admin")
|
||||
uri = "api/v1/saved_query/import/"
|
||||
|
||||
buf = self.create_saved_query_import()
|
||||
form_data = {
|
||||
"formData": (buf, "saved_query.zip"),
|
||||
}
|
||||
rv = self.client.post(uri, data=form_data, content_type="multipart/form-data")
|
||||
response = json.loads(rv.data.decode("utf-8"))
|
||||
|
||||
assert rv.status_code == 200
|
||||
assert response == {"message": "OK"}
|
||||
database = (
|
||||
db.session.query(Database).filter_by(uuid=database_config["uuid"]).one()
|
||||
)
|
||||
assert database.database_name == "imported_database"
|
||||
|
||||
saved_query = (
|
||||
db.session.query(SavedQuery)
|
||||
.filter_by(uuid=saved_queries_config["uuid"])
|
||||
.one()
|
||||
)
|
||||
assert saved_query.database == database
|
||||
|
||||
db.session.delete(saved_query)
|
||||
db.session.delete(database)
|
||||
db.session.commit()
|
||||
|
||||
@@ -17,14 +17,27 @@
|
||||
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
import yaml
|
||||
|
||||
from superset import db, security_manager
|
||||
from superset.commands.exceptions import CommandInvalidError
|
||||
from superset.commands.importers.exceptions import IncorrectVersionError
|
||||
from superset.models.core import Database
|
||||
from superset.models.sql_lab import SavedQuery
|
||||
from superset.queries.saved_queries.commands.exceptions import SavedQueryNotFoundError
|
||||
from superset.queries.saved_queries.commands.export import ExportSavedQueriesCommand
|
||||
from superset.queries.saved_queries.commands.importers.v1 import (
|
||||
ImportSavedQueriesCommand,
|
||||
)
|
||||
from superset.utils.core import get_example_database
|
||||
from tests.base_tests import SupersetTestCase
|
||||
from tests.fixtures.importexport import (
|
||||
database_config,
|
||||
database_metadata_config,
|
||||
saved_queries_config,
|
||||
saved_queries_metadata_config,
|
||||
)
|
||||
|
||||
|
||||
class TestExportSavedQueriesCommand(SupersetTestCase):
|
||||
@@ -108,3 +121,102 @@ class TestExportSavedQueriesCommand(SupersetTestCase):
|
||||
"version",
|
||||
"database_uuid",
|
||||
]
|
||||
|
||||
|
||||
class TestImportSavedQueriesCommand(SupersetTestCase):
|
||||
def test_import_v1_saved_queries(self):
|
||||
"""Test that we can import a saved query"""
|
||||
contents = {
|
||||
"metadata.yaml": yaml.safe_dump(saved_queries_metadata_config),
|
||||
"databases/imported_database.yaml": yaml.safe_dump(database_config),
|
||||
"queries/imported_query.yaml": yaml.safe_dump(saved_queries_config),
|
||||
}
|
||||
|
||||
command = ImportSavedQueriesCommand(contents)
|
||||
command.run()
|
||||
|
||||
saved_query = (
|
||||
db.session.query(SavedQuery)
|
||||
.filter_by(uuid=saved_queries_config["uuid"])
|
||||
.one()
|
||||
)
|
||||
|
||||
assert saved_query.schema == "public"
|
||||
|
||||
database = (
|
||||
db.session.query(Database).filter_by(uuid=database_config["uuid"]).one()
|
||||
)
|
||||
|
||||
db.session.delete(saved_query)
|
||||
db.session.delete(database)
|
||||
db.session.commit()
|
||||
|
||||
def test_import_v1_saved_queries_multiple(self):
|
||||
"""Test that a saved query can be imported multiple times"""
|
||||
contents = {
|
||||
"metadata.yaml": yaml.safe_dump(saved_queries_metadata_config),
|
||||
"databases/imported_database.yaml": yaml.safe_dump(database_config),
|
||||
"queries/imported_query.yaml": yaml.safe_dump(saved_queries_config),
|
||||
}
|
||||
command = ImportSavedQueriesCommand(contents, overwrite=True)
|
||||
command.run()
|
||||
command.run()
|
||||
database = (
|
||||
db.session.query(Database).filter_by(uuid=database_config["uuid"]).one()
|
||||
)
|
||||
saved_query = db.session.query(SavedQuery).filter_by(db_id=database.id).all()
|
||||
assert len(saved_query) == 1
|
||||
|
||||
db.session.delete(saved_query[0])
|
||||
db.session.delete(database)
|
||||
db.session.commit()
|
||||
|
||||
def test_import_v1_saved_queries_validation(self):
|
||||
"""Test different validations applied when importing a saved query"""
|
||||
# metadata.yaml must be present
|
||||
contents = {
|
||||
"databases/imported_database.yaml": yaml.safe_dump(database_config),
|
||||
"queries/imported_query.yaml": yaml.safe_dump(saved_queries_config),
|
||||
}
|
||||
command = ImportSavedQueriesCommand(contents)
|
||||
with pytest.raises(IncorrectVersionError) as excinfo:
|
||||
command.run()
|
||||
assert str(excinfo.value) == "Missing metadata.yaml"
|
||||
|
||||
# version should be 1.0.0
|
||||
contents["metadata.yaml"] = yaml.safe_dump(
|
||||
{
|
||||
"version": "2.0.0",
|
||||
"type": "SavedQuery",
|
||||
"timestamp": "2021-03-30T20:37:54.791187+00:00",
|
||||
}
|
||||
)
|
||||
command = ImportSavedQueriesCommand(contents)
|
||||
with pytest.raises(IncorrectVersionError) as excinfo:
|
||||
command.run()
|
||||
assert str(excinfo.value) == "Must be equal to 1.0.0."
|
||||
|
||||
# type should be a SavedQuery
|
||||
contents["metadata.yaml"] = yaml.safe_dump(database_metadata_config)
|
||||
command = ImportSavedQueriesCommand(contents)
|
||||
with pytest.raises(CommandInvalidError) as excinfo:
|
||||
command.run()
|
||||
assert str(excinfo.value) == "Error importing saved_queries"
|
||||
assert excinfo.value.normalized_messages() == {
|
||||
"metadata.yaml": {"type": ["Must be equal to SavedQuery."]}
|
||||
}
|
||||
|
||||
# must also validate databases
|
||||
broken_config = database_config.copy()
|
||||
del broken_config["database_name"]
|
||||
contents["metadata.yaml"] = yaml.safe_dump(saved_queries_metadata_config)
|
||||
contents["databases/imported_database.yaml"] = yaml.safe_dump(broken_config)
|
||||
command = ImportSavedQueriesCommand(contents)
|
||||
with pytest.raises(CommandInvalidError) as excinfo:
|
||||
command.run()
|
||||
assert str(excinfo.value) == "Error importing saved_queries"
|
||||
assert excinfo.value.normalized_messages() == {
|
||||
"databases/imported_database.yaml": {
|
||||
"database_name": ["Missing data for required field."],
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user