mirror of
https://github.com/apache/superset.git
synced 2024-09-12 00:29:39 -04:00
feat: query REST API more fields and tests (#11482)
* feat: query REST API more fields and tests * fix missing field
This commit is contained in:
parent
55a3404b71
commit
34ae29e59b
@ -156,6 +156,10 @@ class Query(Model, ExtraJSONMixin):
|
|||||||
def username(self) -> str:
|
def username(self) -> str:
|
||||||
return self.user.username
|
return self.user.username
|
||||||
|
|
||||||
|
@property
|
||||||
|
def sql_tables(self) -> List[Table]:
|
||||||
|
return list(ParsedQuery(self.sql).tables)
|
||||||
|
|
||||||
def raise_for_access(self) -> None:
|
def raise_for_access(self) -> None:
|
||||||
"""
|
"""
|
||||||
Raise an exception if the user cannot access the resource.
|
Raise an exception if the user cannot access the resource.
|
||||||
|
@ -19,6 +19,7 @@ import logging
|
|||||||
from flask_appbuilder.models.sqla.interface import SQLAInterface
|
from flask_appbuilder.models.sqla.interface import SQLAInterface
|
||||||
|
|
||||||
from superset.constants import RouteMethod
|
from superset.constants import RouteMethod
|
||||||
|
from superset.databases.filters import DatabaseFilter
|
||||||
from superset.models.sql_lab import Query
|
from superset.models.sql_lab import Query
|
||||||
from superset.queries.filters import QueryFilter
|
from superset.queries.filters import QueryFilter
|
||||||
from superset.queries.schemas import openapi_spec_methods_override
|
from superset.queries.schemas import openapi_spec_methods_override
|
||||||
@ -37,9 +38,18 @@ class QueryRestApi(BaseSupersetModelRestApi):
|
|||||||
|
|
||||||
class_permission_name = "QueryView"
|
class_permission_name = "QueryView"
|
||||||
list_columns = [
|
list_columns = [
|
||||||
"user.username",
|
"changed_on",
|
||||||
"database.database_name",
|
"database.database_name",
|
||||||
|
"rows",
|
||||||
|
"schema",
|
||||||
|
"sql",
|
||||||
|
"sql_tables",
|
||||||
"status",
|
"status",
|
||||||
|
"tab_name",
|
||||||
|
"user.first_name",
|
||||||
|
"user.id",
|
||||||
|
"user.last_name",
|
||||||
|
"user.username",
|
||||||
"start_time",
|
"start_time",
|
||||||
"end_time",
|
"end_time",
|
||||||
"rows",
|
"rows",
|
||||||
@ -47,30 +57,30 @@ class QueryRestApi(BaseSupersetModelRestApi):
|
|||||||
"tracking_url",
|
"tracking_url",
|
||||||
]
|
]
|
||||||
show_columns = [
|
show_columns = [
|
||||||
|
"changed_on",
|
||||||
"client_id",
|
"client_id",
|
||||||
"tmp_table_name",
|
|
||||||
"tmp_schema_name",
|
|
||||||
"status",
|
|
||||||
"tab_name",
|
|
||||||
"sql_editor_id",
|
|
||||||
"database.id",
|
"database.id",
|
||||||
"schema",
|
"end_result_backend_time",
|
||||||
"sql",
|
"end_time",
|
||||||
"select_sql",
|
"error_message",
|
||||||
"executed_sql",
|
"executed_sql",
|
||||||
"limit",
|
"limit",
|
||||||
|
"progress",
|
||||||
|
"results_key",
|
||||||
|
"rows",
|
||||||
|
"schema",
|
||||||
"select_as_cta",
|
"select_as_cta",
|
||||||
"select_as_cta_used",
|
"select_as_cta_used",
|
||||||
"progress",
|
"select_sql",
|
||||||
"rows",
|
"sql",
|
||||||
"error_message",
|
"sql_editor_id",
|
||||||
"results_key",
|
|
||||||
"start_time",
|
|
||||||
"start_running_time",
|
"start_running_time",
|
||||||
"end_time",
|
"start_time",
|
||||||
"end_result_backend_time",
|
"status",
|
||||||
|
"tab_name",
|
||||||
|
"tmp_schema_name",
|
||||||
|
"tmp_table_name",
|
||||||
"tracking_url",
|
"tracking_url",
|
||||||
"changed_on",
|
|
||||||
]
|
]
|
||||||
base_filters = [["id", QueryFilter, lambda: []]]
|
base_filters = [["id", QueryFilter, lambda: []]]
|
||||||
base_order = ("changed_on", "desc")
|
base_order = ("changed_on", "desc")
|
||||||
@ -78,7 +88,21 @@ class QueryRestApi(BaseSupersetModelRestApi):
|
|||||||
openapi_spec_tag = "Queries"
|
openapi_spec_tag = "Queries"
|
||||||
openapi_spec_methods = openapi_spec_methods_override
|
openapi_spec_methods = openapi_spec_methods_override
|
||||||
|
|
||||||
|
order_columns = [
|
||||||
|
"changed_on",
|
||||||
|
"database.database_name",
|
||||||
|
"rows",
|
||||||
|
"schema",
|
||||||
|
"sql",
|
||||||
|
"tab_name",
|
||||||
|
"user.first_name",
|
||||||
|
]
|
||||||
|
|
||||||
related_field_filters = {
|
related_field_filters = {
|
||||||
"created_by": RelatedFieldFilter("first_name", FilterRelatedOwners),
|
"created_by": RelatedFieldFilter("first_name", FilterRelatedOwners),
|
||||||
}
|
}
|
||||||
allowed_rel_fields = {"user"}
|
|
||||||
|
search_columns = ["changed_on", "database", "sql", "status", "user"]
|
||||||
|
|
||||||
|
filter_rel_fields = {"database": [["id", DatabaseFilter, lambda: []]]}
|
||||||
|
allowed_rel_fields = {"database", "user"}
|
||||||
|
@ -16,22 +16,25 @@
|
|||||||
# under the License.
|
# under the License.
|
||||||
# isort:skip_file
|
# isort:skip_file
|
||||||
"""Unit tests for Superset"""
|
"""Unit tests for Superset"""
|
||||||
|
from datetime import datetime, timedelta
|
||||||
import json
|
import json
|
||||||
import random
|
import random
|
||||||
import string
|
import string
|
||||||
from typing import Dict, Any
|
|
||||||
|
|
||||||
|
import pytest
|
||||||
import prison
|
import prison
|
||||||
from sqlalchemy.sql import func
|
from sqlalchemy.sql import func
|
||||||
|
|
||||||
import tests.test_app
|
import tests.test_app
|
||||||
from superset import db, security_manager
|
from superset import db, security_manager
|
||||||
from superset.models.core import Database
|
from superset.models.core import Database
|
||||||
from superset.utils.core import get_example_database
|
from superset.utils.core import get_example_database, get_main_database, QueryStatus
|
||||||
from superset.models.sql_lab import Query
|
from superset.models.sql_lab import Query
|
||||||
|
|
||||||
from tests.base_tests import SupersetTestCase
|
from tests.base_tests import SupersetTestCase
|
||||||
|
|
||||||
|
QUERIES_FIXTURE_COUNT = 10
|
||||||
|
|
||||||
|
|
||||||
class TestQueryApi(SupersetTestCase):
|
class TestQueryApi(SupersetTestCase):
|
||||||
def insert_query(
|
def insert_query(
|
||||||
@ -67,6 +70,45 @@ class TestQueryApi(SupersetTestCase):
|
|||||||
db.session.commit()
|
db.session.commit()
|
||||||
return query
|
return query
|
||||||
|
|
||||||
|
@pytest.fixture()
|
||||||
|
def create_queries(self):
|
||||||
|
with self.create_app().app_context():
|
||||||
|
queries = []
|
||||||
|
admin_id = self.get_user("admin").id
|
||||||
|
alpha_id = self.get_user("alpha").id
|
||||||
|
example_database_id = get_example_database().id
|
||||||
|
main_database_id = get_main_database().id
|
||||||
|
for cx in range(QUERIES_FIXTURE_COUNT - 1):
|
||||||
|
queries.append(
|
||||||
|
self.insert_query(
|
||||||
|
example_database_id,
|
||||||
|
admin_id,
|
||||||
|
self.get_random_string(),
|
||||||
|
sql=f"SELECT col1, col2 from table{cx}",
|
||||||
|
rows=cx,
|
||||||
|
status=QueryStatus.SUCCESS
|
||||||
|
if (cx % 2) == 0
|
||||||
|
else QueryStatus.RUNNING,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
queries.append(
|
||||||
|
self.insert_query(
|
||||||
|
main_database_id,
|
||||||
|
alpha_id,
|
||||||
|
self.get_random_string(),
|
||||||
|
sql=f"SELECT col1, col2 from table{QUERIES_FIXTURE_COUNT}",
|
||||||
|
rows=QUERIES_FIXTURE_COUNT,
|
||||||
|
status=QueryStatus.SUCCESS,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
yield queries
|
||||||
|
|
||||||
|
# rollback changes
|
||||||
|
for query in queries:
|
||||||
|
db.session.delete(query)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_random_string(length: int = 10):
|
def get_random_string(length: int = 10):
|
||||||
letters = string.ascii_letters
|
letters = string.ascii_letters
|
||||||
@ -74,7 +116,7 @@ class TestQueryApi(SupersetTestCase):
|
|||||||
|
|
||||||
def test_get_query(self):
|
def test_get_query(self):
|
||||||
"""
|
"""
|
||||||
Query API: Test get query
|
Query API: Test get query
|
||||||
"""
|
"""
|
||||||
admin = self.get_user("admin")
|
admin = self.get_user("admin")
|
||||||
client_id = self.get_random_string()
|
client_id = self.get_random_string()
|
||||||
@ -131,7 +173,7 @@ class TestQueryApi(SupersetTestCase):
|
|||||||
|
|
||||||
def test_get_query_not_found(self):
|
def test_get_query_not_found(self):
|
||||||
"""
|
"""
|
||||||
Query API: Test get query not found
|
Query API: Test get query not found
|
||||||
"""
|
"""
|
||||||
admin = self.get_user("admin")
|
admin = self.get_user("admin")
|
||||||
client_id = self.get_random_string()
|
client_id = self.get_random_string()
|
||||||
@ -144,7 +186,7 @@ class TestQueryApi(SupersetTestCase):
|
|||||||
|
|
||||||
def test_get_query_no_data_access(self):
|
def test_get_query_no_data_access(self):
|
||||||
"""
|
"""
|
||||||
Query API: Test get dashboard without data access
|
Query API: Test get query without data access
|
||||||
"""
|
"""
|
||||||
gamma1 = self.create_user(
|
gamma1 = self.create_user(
|
||||||
"gamma_1", "password", "Gamma", email="gamma1@superset.org"
|
"gamma_1", "password", "Gamma", email="gamma1@superset.org"
|
||||||
@ -198,34 +240,131 @@ class TestQueryApi(SupersetTestCase):
|
|||||||
db.session.delete(gamma2)
|
db.session.delete(gamma2)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
def test_get_query_filter(self):
|
@pytest.mark.usefixtures("create_queries")
|
||||||
|
def test_get_list_query(self):
|
||||||
"""
|
"""
|
||||||
Query API: Test get queries filter
|
Query API: Test get list query
|
||||||
"""
|
"""
|
||||||
admin = self.get_user("admin")
|
|
||||||
client_id = self.get_random_string()
|
|
||||||
query = self.insert_query(
|
|
||||||
get_example_database().id,
|
|
||||||
admin.id,
|
|
||||||
client_id,
|
|
||||||
sql="SELECT col1, col2 from table1",
|
|
||||||
)
|
|
||||||
|
|
||||||
self.login(username="admin")
|
self.login(username="admin")
|
||||||
arguments = {"filters": [{"col": "sql", "opr": "sw", "value": "SELECT col1"}]}
|
uri = "api/v1/query/"
|
||||||
uri = f"api/v1/query/?q={prison.dumps(arguments)}"
|
|
||||||
rv = self.client.get(uri)
|
rv = self.client.get(uri)
|
||||||
self.assertEqual(rv.status_code, 200)
|
self.assertEqual(rv.status_code, 200)
|
||||||
data = json.loads(rv.data.decode("utf-8"))
|
data = json.loads(rv.data.decode("utf-8"))
|
||||||
self.assertEqual(data["count"], 1)
|
assert data["count"] == QUERIES_FIXTURE_COUNT
|
||||||
|
# check expected columns
|
||||||
|
assert sorted(list(data["result"][0].keys())) == [
|
||||||
|
"changed_on",
|
||||||
|
"database",
|
||||||
|
"end_time",
|
||||||
|
"rows",
|
||||||
|
"schema",
|
||||||
|
"sql",
|
||||||
|
"sql_tables",
|
||||||
|
"start_time",
|
||||||
|
"status",
|
||||||
|
"tab_name",
|
||||||
|
"tmp_table_name",
|
||||||
|
"tracking_url",
|
||||||
|
"user",
|
||||||
|
]
|
||||||
|
assert sorted(list(data["result"][0]["user"].keys())) == [
|
||||||
|
"first_name",
|
||||||
|
"id",
|
||||||
|
"last_name",
|
||||||
|
"username",
|
||||||
|
]
|
||||||
|
assert list(data["result"][0]["database"].keys()) == [
|
||||||
|
"database_name",
|
||||||
|
]
|
||||||
|
|
||||||
# rollback changes
|
@pytest.mark.usefixtures("create_queries")
|
||||||
db.session.delete(query)
|
def test_get_list_query_filter_sql(self):
|
||||||
db.session.commit()
|
|
||||||
|
|
||||||
def test_get_queries_no_data_access(self):
|
|
||||||
"""
|
"""
|
||||||
Query API: Test get queries no data access
|
Query API: Test get list query filter
|
||||||
|
"""
|
||||||
|
self.login(username="admin")
|
||||||
|
arguments = {"filters": [{"col": "sql", "opr": "ct", "value": "table2"}]}
|
||||||
|
uri = f"api/v1/query/?q={prison.dumps(arguments)}"
|
||||||
|
rv = self.client.get(uri)
|
||||||
|
assert rv.status_code == 200
|
||||||
|
data = json.loads(rv.data.decode("utf-8"))
|
||||||
|
assert data["count"] == 1
|
||||||
|
|
||||||
|
@pytest.mark.usefixtures("create_queries")
|
||||||
|
def test_get_list_query_filter_database(self):
|
||||||
|
"""
|
||||||
|
Query API: Test get list query filter database
|
||||||
|
"""
|
||||||
|
self.login(username="admin")
|
||||||
|
database_id = get_main_database().id
|
||||||
|
arguments = {
|
||||||
|
"filters": [{"col": "database", "opr": "rel_o_m", "value": database_id}]
|
||||||
|
}
|
||||||
|
uri = f"api/v1/query/?q={prison.dumps(arguments)}"
|
||||||
|
rv = self.client.get(uri)
|
||||||
|
assert rv.status_code == 200
|
||||||
|
data = json.loads(rv.data.decode("utf-8"))
|
||||||
|
assert data["count"] == 1
|
||||||
|
|
||||||
|
@pytest.mark.usefixtures("create_queries")
|
||||||
|
def test_get_list_query_filter_user(self):
|
||||||
|
"""
|
||||||
|
Query API: Test get list query filter user
|
||||||
|
"""
|
||||||
|
self.login(username="admin")
|
||||||
|
alpha_id = self.get_user("alpha").id
|
||||||
|
arguments = {"filters": [{"col": "user", "opr": "rel_o_m", "value": alpha_id}]}
|
||||||
|
uri = f"api/v1/query/?q={prison.dumps(arguments)}"
|
||||||
|
rv = self.client.get(uri)
|
||||||
|
assert rv.status_code == 200
|
||||||
|
data = json.loads(rv.data.decode("utf-8"))
|
||||||
|
assert data["count"] == 1
|
||||||
|
|
||||||
|
@pytest.mark.usefixtures("create_queries")
|
||||||
|
def test_get_list_query_filter_changed_on(self):
|
||||||
|
"""
|
||||||
|
Query API: Test get list query filter changed_on
|
||||||
|
"""
|
||||||
|
self.login(username="admin")
|
||||||
|
now_time = datetime.now()
|
||||||
|
yesterday_time = now_time - timedelta(days=1)
|
||||||
|
arguments = {
|
||||||
|
"filters": [
|
||||||
|
{"col": "changed_on", "opr": "lt", "value": str(now_time)},
|
||||||
|
{"col": "changed_on", "opr": "gt", "value": str(yesterday_time)},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
uri = f"api/v1/query/?q={prison.dumps(arguments)}"
|
||||||
|
rv = self.client.get(uri)
|
||||||
|
assert rv.status_code == 200
|
||||||
|
data = json.loads(rv.data.decode("utf-8"))
|
||||||
|
assert data["count"] == QUERIES_FIXTURE_COUNT
|
||||||
|
|
||||||
|
@pytest.mark.usefixtures("create_queries")
|
||||||
|
def test_get_list_query_order(self):
|
||||||
|
"""
|
||||||
|
Query API: Test get list query filter changed_on
|
||||||
|
"""
|
||||||
|
self.login(username="admin")
|
||||||
|
order_columns = [
|
||||||
|
"changed_on",
|
||||||
|
"database.database_name",
|
||||||
|
"rows",
|
||||||
|
"schema",
|
||||||
|
"sql",
|
||||||
|
"tab_name",
|
||||||
|
"user.first_name",
|
||||||
|
]
|
||||||
|
|
||||||
|
for order_column in order_columns:
|
||||||
|
arguments = {"order_column": order_column, "order_direction": "asc"}
|
||||||
|
uri = f"api/v1/query/?q={prison.dumps(arguments)}"
|
||||||
|
rv = self.client.get(uri)
|
||||||
|
assert rv.status_code == 200
|
||||||
|
|
||||||
|
def test_get_list_query_no_data_access(self):
|
||||||
|
"""
|
||||||
|
Query API: Test get queries no data access
|
||||||
"""
|
"""
|
||||||
admin = self.get_user("admin")
|
admin = self.get_user("admin")
|
||||||
client_id = self.get_random_string()
|
client_id = self.get_random_string()
|
||||||
@ -240,9 +379,9 @@ class TestQueryApi(SupersetTestCase):
|
|||||||
arguments = {"filters": [{"col": "sql", "opr": "sw", "value": "SELECT col1"}]}
|
arguments = {"filters": [{"col": "sql", "opr": "sw", "value": "SELECT col1"}]}
|
||||||
uri = f"api/v1/query/?q={prison.dumps(arguments)}"
|
uri = f"api/v1/query/?q={prison.dumps(arguments)}"
|
||||||
rv = self.client.get(uri)
|
rv = self.client.get(uri)
|
||||||
self.assertEqual(rv.status_code, 200)
|
assert rv.status_code == 200
|
||||||
data = json.loads(rv.data.decode("utf-8"))
|
data = json.loads(rv.data.decode("utf-8"))
|
||||||
self.assertEqual(data["count"], 0)
|
assert data["count"] == 0
|
||||||
|
|
||||||
# rollback changes
|
# rollback changes
|
||||||
db.session.delete(query)
|
db.session.delete(query)
|
||||||
|
Loading…
Reference in New Issue
Block a user