mirror of https://github.com/apache/superset.git
chore: remove deprecated apis stop_query, queries, search_queries (#24360)
This commit is contained in:
parent
ca478bd5c4
commit
c8e958fb7e
|
@ -64,7 +64,6 @@
|
|||
|can user slices on Superset|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
|
||||
|can favstar on Superset|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
|
||||
|can import dashboards on Superset|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
|
||||
|can search queries on Superset|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|
|
||||
|can sqllab viz on Superset|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|
|
||||
|can schemas on Superset|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
|
||||
|can sqllab history on Superset|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|
|
||||
|
@ -92,8 +91,6 @@
|
|||
|can sqllab table viz on Superset|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|
|
||||
|can profile on Superset|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
|
||||
|can available domains on Superset|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
|
||||
|can queries on Superset|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
|
||||
|can stop query on Superset|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|
|
||||
|can request access on Superset|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
|
||||
|can dashboard on Superset|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
|
||||
|can post on TableSchemaView|:heavy_check_mark:|:heavy_check_mark:|:heavy_check_mark:|O|
|
||||
|
|
|
@ -34,6 +34,7 @@ assists people when migrating to a new version.
|
|||
|
||||
### Breaking Changes
|
||||
|
||||
- [24360](https://github.com/apache/superset/pull/24360): Removed deprecated APIs `/superset/stop_query/...`, `/superset/queries/...`, `/superset/search_queries`
|
||||
- [24353](https://github.com/apache/superset/pull/24353): Removed deprecated APIs `/copy_dash/int:dashboard_id/`, `/save_dash/int:dashboard_id/`, `/add_slices/int:dashboard_id/`.
|
||||
- [24198](https://github.com/apache/superset/pull/24198) The FAB views `User Registrations` and `User's Statistics` have been changed to Admin only. To re-enable them for non-admin users, please add the following perms to your custom role: `menu access on User's Statistics` and `menu access on User Registrations`.
|
||||
- [24354](https://github.com/apache/superset/pull/24354): Removed deprecated APIs `/superset/testconn`, `/superset/validate_sql_json/`, `/superset/schemas_access_for_file_upload`, `/superset/extra_table_metadata`
|
||||
|
|
|
@ -241,9 +241,7 @@ class SupersetSecurityManager( # pylint: disable=too-many-public-methods
|
|||
("can_sqllab_viz", "Superset"),
|
||||
("can_sqllab_table_viz", "Superset"), # Deprecated permission remove on 3.0.0
|
||||
("can_sqllab", "Superset"),
|
||||
("can_stop_query", "Superset"), # Deprecated permission remove on 3.0.0
|
||||
("can_test_conn", "Superset"), # Deprecated permission remove on 3.0.0
|
||||
("can_search_queries", "Superset"), # Deprecated permission remove on 3.0.0
|
||||
("can_activate", "TabStateView"),
|
||||
("can_get", "TabStateView"),
|
||||
("can_delete_query", "TabStateView"),
|
||||
|
|
|
@ -22,7 +22,6 @@ from datetime import datetime
|
|||
from typing import Any, Callable, cast
|
||||
from urllib import parse
|
||||
|
||||
import backoff
|
||||
import simplejson as json
|
||||
from flask import abort, flash, g, redirect, render_template, request, Response
|
||||
from flask_appbuilder import expose
|
||||
|
@ -44,13 +43,11 @@ from superset import (
|
|||
event_logger,
|
||||
is_feature_enabled,
|
||||
security_manager,
|
||||
sql_lab,
|
||||
viz,
|
||||
)
|
||||
from superset.charts.commands.exceptions import ChartNotFoundError
|
||||
from superset.charts.dao import ChartDAO
|
||||
from superset.common.chart_data import ChartDataResultFormat, ChartDataResultType
|
||||
from superset.common.db_query_status import QueryStatus
|
||||
from superset.connectors.base.models import BaseDatasource
|
||||
from superset.connectors.sqla.models import (
|
||||
AnnotationDatasource,
|
||||
|
@ -58,7 +55,6 @@ from superset.connectors.sqla.models import (
|
|||
SqlMetric,
|
||||
TableColumn,
|
||||
)
|
||||
from superset.constants import QUERY_EARLY_CANCEL_KEY
|
||||
from superset.dashboards.commands.exceptions import DashboardAccessDeniedError
|
||||
from superset.dashboards.commands.importers.v0 import ImportDashboardsCommand
|
||||
from superset.dashboards.permalink.commands.get import GetDashboardPermalinkCommand
|
||||
|
@ -70,7 +66,6 @@ from superset.errors import ErrorLevel, SupersetError, SupersetErrorType
|
|||
from superset.exceptions import (
|
||||
CacheLoadError,
|
||||
DatabaseNotFound,
|
||||
SupersetCancelQueryException,
|
||||
SupersetErrorException,
|
||||
SupersetException,
|
||||
SupersetGenericErrorException,
|
||||
|
@ -94,7 +89,6 @@ from superset.utils import core as utils
|
|||
from superset.utils.async_query_manager import AsyncQueryTokenException
|
||||
from superset.utils.cache import etag_cache
|
||||
from superset.utils.core import DatasourceType, get_user_id, ReservedUrlParameters
|
||||
from superset.utils.dates import now_as_float
|
||||
from superset.views.base import (
|
||||
api,
|
||||
BaseSupersetView,
|
||||
|
@ -1433,44 +1427,6 @@ class Superset(BaseSupersetView): # pylint: disable=too-many-public-methods
|
|||
def theme(self) -> FlaskResponse:
|
||||
return self.render_template("superset/theme.html")
|
||||
|
||||
@has_access_api
|
||||
@handle_api_exception
|
||||
@expose("/stop_query/", methods=("POST",))
|
||||
@event_logger.log_this
|
||||
@backoff.on_exception(
|
||||
backoff.constant,
|
||||
Exception,
|
||||
interval=1,
|
||||
on_backoff=lambda details: db.session.rollback(),
|
||||
on_giveup=lambda details: db.session.rollback(),
|
||||
max_tries=5,
|
||||
)
|
||||
@deprecated(new_target="/api/v1/query/stop")
|
||||
def stop_query(self) -> FlaskResponse:
|
||||
client_id = request.form.get("client_id")
|
||||
query = db.session.query(Query).filter_by(client_id=client_id).one()
|
||||
if query.status in [
|
||||
QueryStatus.FAILED,
|
||||
QueryStatus.SUCCESS,
|
||||
QueryStatus.TIMED_OUT,
|
||||
]:
|
||||
logger.warning(
|
||||
"Query with client_id could not be stopped: query already complete",
|
||||
)
|
||||
return self.json_response("OK")
|
||||
|
||||
if not sql_lab.cancel_query(query):
|
||||
raise SupersetCancelQueryException("Could not cancel query")
|
||||
|
||||
query.status = QueryStatus.STOPPED
|
||||
# Add the stop identity attribute because the sqlalchemy thread is unsafe
|
||||
# because of multiple updates to the status in the query table
|
||||
query.set_extra_json_key(QUERY_EARLY_CANCEL_KEY, True)
|
||||
query.end_time = now_as_float()
|
||||
db.session.commit()
|
||||
|
||||
return self.json_response("OK")
|
||||
|
||||
@api
|
||||
@handle_api_exception
|
||||
@has_access
|
||||
|
@ -1495,102 +1451,6 @@ class Superset(BaseSupersetView): # pylint: disable=too-many-public-methods
|
|||
datasource.raise_for_access()
|
||||
return json_success(json.dumps(sanitize_datasource_data(datasource.data)))
|
||||
|
||||
@has_access_api
|
||||
@event_logger.log_this
|
||||
@expose("/queries/<float:last_updated_ms>")
|
||||
@expose("/queries/<int:last_updated_ms>")
|
||||
@deprecated(new_target="api/v1/query/updated_since")
|
||||
def queries(self, last_updated_ms: float | int) -> FlaskResponse:
|
||||
"""
|
||||
Get the updated queries.
|
||||
|
||||
:param last_updated_ms: Unix time (milliseconds)
|
||||
"""
|
||||
|
||||
return self.queries_exec(last_updated_ms)
|
||||
|
||||
@staticmethod
|
||||
def queries_exec(last_updated_ms: float | int) -> FlaskResponse:
|
||||
stats_logger.incr("queries")
|
||||
if not get_user_id():
|
||||
return json_error_response(
|
||||
"Please login to access the queries.", status=403
|
||||
)
|
||||
|
||||
# UTC date time, same that is stored in the DB.
|
||||
last_updated_dt = datetime.utcfromtimestamp(last_updated_ms / 1000)
|
||||
|
||||
sql_queries = (
|
||||
db.session.query(Query)
|
||||
.filter(Query.user_id == get_user_id(), Query.changed_on >= last_updated_dt)
|
||||
.all()
|
||||
)
|
||||
dict_queries = {q.client_id: q.to_dict() for q in sql_queries}
|
||||
return json_success(json.dumps(dict_queries, default=utils.json_int_dttm_ser))
|
||||
|
||||
@has_access
|
||||
@event_logger.log_this
|
||||
@expose("/search_queries")
|
||||
@deprecated(new_target="api/v1/query/")
|
||||
def search_queries(self) -> FlaskResponse: # pylint: disable=no-self-use
|
||||
"""
|
||||
Search for previously run sqllab queries. Used for Sqllab Query Search
|
||||
page /superset/sqllab#search.
|
||||
|
||||
Custom permission can_only_search_queries_owned restricts queries
|
||||
to only queries run by current user.
|
||||
|
||||
:returns: Response with list of sql query dicts
|
||||
"""
|
||||
if security_manager.can_access_all_queries():
|
||||
search_user_id = request.args.get("user_id")
|
||||
elif request.args.get("user_id") is not None:
|
||||
try:
|
||||
search_user_id = int(cast(int, request.args.get("user_id")))
|
||||
except ValueError:
|
||||
return Response(status=400, mimetype="application/json")
|
||||
if search_user_id != get_user_id():
|
||||
return Response(status=403, mimetype="application/json")
|
||||
else:
|
||||
search_user_id = get_user_id()
|
||||
database_id = request.args.get("database_id")
|
||||
search_text = request.args.get("search_text")
|
||||
# From and To time stamp should be Epoch timestamp in seconds
|
||||
|
||||
query = db.session.query(Query)
|
||||
if search_user_id:
|
||||
# Filter on user_id
|
||||
query = query.filter(Query.user_id == search_user_id)
|
||||
|
||||
if database_id:
|
||||
# Filter on db Id
|
||||
query = query.filter(Query.database_id == database_id)
|
||||
|
||||
if status := request.args.get("status"):
|
||||
# Filter on status
|
||||
query = query.filter(Query.status == status)
|
||||
|
||||
if search_text:
|
||||
# Filter on search text
|
||||
query = query.filter(Query.sql.like(f"%{search_text}%"))
|
||||
|
||||
if from_time := request.args.get("from"):
|
||||
query = query.filter(Query.start_time > int(from_time))
|
||||
|
||||
if to_time := request.args.get("to"):
|
||||
query = query.filter(Query.start_time < int(to_time))
|
||||
|
||||
query_limit = config["QUERY_SEARCH_LIMIT"]
|
||||
sql_queries = query.order_by(Query.start_time.asc()).limit(query_limit).all()
|
||||
|
||||
dict_queries = [q.to_dict() for q in sql_queries]
|
||||
|
||||
return Response(
|
||||
json.dumps(dict_queries, default=utils.json_int_dttm_ser),
|
||||
status=200,
|
||||
mimetype="application/json",
|
||||
)
|
||||
|
||||
@app.errorhandler(500)
|
||||
def show_traceback(self) -> FlaskResponse: # pylint: disable=no-self-use
|
||||
return (
|
||||
|
|
|
@ -1243,29 +1243,6 @@ class TestCore(SupersetTestCase):
|
|||
data = self.get_resp(url)
|
||||
self.assertIn("Error message", data)
|
||||
|
||||
@mock.patch("superset.sql_lab.cancel_query")
|
||||
@mock.patch("superset.views.core.db.session")
|
||||
def test_stop_query_not_implemented(
|
||||
self, mock_superset_db_session, mock_sql_lab_cancel_query
|
||||
):
|
||||
"""
|
||||
Handles stop query when the DB engine spec does not
|
||||
have a cancel query method.
|
||||
"""
|
||||
form_data = {"client_id": "foo"}
|
||||
query_mock = mock.Mock()
|
||||
query_mock.client_id = "foo"
|
||||
query_mock.status = QueryStatus.RUNNING
|
||||
self.login(username="admin")
|
||||
mock_superset_db_session.query().filter_by().one().return_value = query_mock
|
||||
mock_sql_lab_cancel_query.return_value = False
|
||||
rv = self.client.post(
|
||||
"/superset/stop_query/",
|
||||
data={"form_data": json.dumps(form_data)},
|
||||
)
|
||||
|
||||
assert rv.status_code == 422
|
||||
|
||||
@pytest.mark.usefixtures("load_energy_table_with_slice")
|
||||
@mock.patch("superset.explore.form_data.commands.create.CreateFormDataCommand.run")
|
||||
def test_explore_redirect(self, mock_command: mock.Mock):
|
||||
|
|
|
@ -17,8 +17,7 @@
|
|||
# isort:skip_file
|
||||
"""Unit tests for Sql Lab"""
|
||||
import json
|
||||
from datetime import datetime, timedelta
|
||||
from math import ceil, floor
|
||||
from datetime import datetime
|
||||
|
||||
import pytest
|
||||
from celery.exceptions import SoftTimeLimitExceeded
|
||||
|
@ -327,132 +326,6 @@ class TestSqlLab(SupersetTestCase):
|
|||
engine.execute(f"DROP TABLE IF EXISTS {CTAS_SCHEMA_NAME}.test_table")
|
||||
db.session.commit()
|
||||
|
||||
def test_queries_endpoint(self):
|
||||
self.run_some_queries()
|
||||
|
||||
# Not logged in, should error out
|
||||
resp = self.client.get("/superset/queries/0")
|
||||
# Redirects to the login page
|
||||
self.assertEqual(401, resp.status_code)
|
||||
|
||||
# Admin sees queries
|
||||
self.login("admin")
|
||||
data = self.get_json_resp("/superset/queries/0")
|
||||
self.assertEqual(2, len(data))
|
||||
data = self.get_json_resp("/superset/queries/0.0")
|
||||
self.assertEqual(2, len(data))
|
||||
|
||||
# Run 2 more queries
|
||||
self.run_sql("SELECT * FROM birth_names LIMIT 1", client_id="client_id_4")
|
||||
self.run_sql("SELECT * FROM birth_names LIMIT 2", client_id="client_id_5")
|
||||
self.login("admin")
|
||||
data = self.get_json_resp("/superset/queries/0")
|
||||
self.assertEqual(4, len(data))
|
||||
|
||||
now = datetime.now() + timedelta(days=1)
|
||||
query = (
|
||||
db.session.query(Query)
|
||||
.filter_by(sql="SELECT * FROM birth_names LIMIT 1")
|
||||
.first()
|
||||
)
|
||||
query.changed_on = now
|
||||
db.session.commit()
|
||||
|
||||
data = self.get_json_resp(
|
||||
f"/superset/queries/{float(datetime_to_epoch(now)) - 1000}"
|
||||
)
|
||||
self.assertEqual(1, len(data))
|
||||
|
||||
self.logout()
|
||||
resp = self.client.get("/superset/queries/0")
|
||||
# Redirects to the login page
|
||||
self.assertEqual(401, resp.status_code)
|
||||
|
||||
def test_search_query_on_db_id(self):
|
||||
self.run_some_queries()
|
||||
self.login("admin")
|
||||
examples_dbid = get_example_database().id
|
||||
|
||||
# Test search queries on database Id
|
||||
data = self.get_json_resp(
|
||||
f"/superset/search_queries?database_id={examples_dbid}"
|
||||
)
|
||||
self.assertEqual(3, len(data))
|
||||
db_ids = [k["dbId"] for k in data]
|
||||
self.assertEqual([examples_dbid for i in range(3)], db_ids)
|
||||
|
||||
resp = self.get_resp("/superset/search_queries?database_id=-1")
|
||||
data = json.loads(resp)
|
||||
self.assertEqual(0, len(data))
|
||||
|
||||
def test_search_query_on_user(self):
|
||||
self.run_some_queries()
|
||||
self.login("admin")
|
||||
|
||||
# Test search queries on user Id
|
||||
user_id = security_manager.find_user("admin").id
|
||||
data = self.get_json_resp(f"/superset/search_queries?user_id={user_id}")
|
||||
self.assertEqual(2, len(data))
|
||||
user_ids = {k["userId"] for k in data}
|
||||
self.assertEqual({user_id}, user_ids)
|
||||
|
||||
user_id = security_manager.find_user("gamma_sqllab").id
|
||||
resp = self.get_resp(f"/superset/search_queries?user_id={user_id}")
|
||||
data = json.loads(resp)
|
||||
self.assertEqual(1, len(data))
|
||||
self.assertEqual(data[0]["userId"], user_id)
|
||||
|
||||
@pytest.mark.usefixtures("load_birth_names_dashboard_with_slices")
|
||||
def test_search_query_on_status(self):
|
||||
self.run_some_queries()
|
||||
self.login("admin")
|
||||
# Test search queries on status
|
||||
resp = self.get_resp("/superset/search_queries?status=success")
|
||||
data = json.loads(resp)
|
||||
self.assertEqual(2, len(data))
|
||||
states = [k["state"] for k in data]
|
||||
self.assertEqual(["success", "success"], states)
|
||||
|
||||
resp = self.get_resp("/superset/search_queries?status=failed")
|
||||
data = json.loads(resp)
|
||||
self.assertEqual(1, len(data))
|
||||
self.assertEqual(data[0]["state"], "failed")
|
||||
|
||||
def test_search_query_on_text(self):
|
||||
self.run_some_queries()
|
||||
self.login("admin")
|
||||
url = "/superset/search_queries?search_text=birth"
|
||||
data = self.get_json_resp(url)
|
||||
self.assertEqual(2, len(data))
|
||||
self.assertIn("birth", data[0]["sql"])
|
||||
|
||||
def test_search_query_filter_by_time(self):
|
||||
self.run_some_queries()
|
||||
self.login("admin")
|
||||
from_time = floor(
|
||||
(db.session.query(Query).filter_by(sql=QUERY_1).one()).start_time
|
||||
)
|
||||
to_time = ceil(
|
||||
(db.session.query(Query).filter_by(sql=QUERY_2).one()).start_time
|
||||
)
|
||||
url = f"/superset/search_queries?from={from_time}&to={to_time}"
|
||||
assert len(self.client.get(url).json) == 2
|
||||
|
||||
def test_search_query_only_owned(self) -> None:
|
||||
"""
|
||||
Test a search query with a user that does not have can_access_all_queries.
|
||||
"""
|
||||
# Test search_queries for Alpha user
|
||||
self.run_some_queries()
|
||||
self.login("gamma_sqllab")
|
||||
|
||||
user_id = security_manager.find_user("gamma_sqllab").id
|
||||
data = self.get_json_resp("/superset/search_queries")
|
||||
|
||||
self.assertEqual(1, len(data))
|
||||
user_ids = {k["userId"] for k in data}
|
||||
self.assertEqual({user_id}, user_ids)
|
||||
|
||||
def test_alias_duplicate(self):
|
||||
self.run_sql(
|
||||
"SELECT name as col, gender as col FROM birth_names LIMIT 10",
|
||||
|
|
Loading…
Reference in New Issue