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:
Daniel Vaz Gaspar 2020-10-31 10:18:34 +00:00 committed by GitHub
parent 55a3404b71
commit 34ae29e59b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 212 additions and 45 deletions

View File

@ -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.

View File

@ -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"}

View File

@ -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)