mirror of
https://github.com/getredash/redash.git
synced 2025-12-19 17:37:19 -05:00
Merge pull request #1091 from whummer/feature/cache_embeds
Add caching for queries used in embeds
This commit is contained in:
@@ -1,14 +1,16 @@
|
|||||||
import json
|
import json
|
||||||
import pystache
|
import pystache
|
||||||
|
import time
|
||||||
|
import logging
|
||||||
|
|
||||||
from funcy import project
|
from funcy import project
|
||||||
from flask import render_template, request
|
from flask import render_template, request
|
||||||
from flask_login import login_required, current_user
|
from flask_login import login_required, current_user
|
||||||
from flask_restful import abort
|
from flask_restful import abort
|
||||||
|
|
||||||
from redash import models, settings
|
from redash import models, settings, utils
|
||||||
from redash import serializers
|
from redash import serializers
|
||||||
from redash.utils import json_dumps, collect_parameters_from_request
|
from redash.utils import json_dumps, collect_parameters_from_request, gen_query_hash
|
||||||
from redash.handlers import routes
|
from redash.handlers import routes
|
||||||
from redash.handlers.base import org_scoped_rule, record_event
|
from redash.handlers.base import org_scoped_rule, record_event
|
||||||
from redash.handlers.query_results import collect_query_parameters
|
from redash.handlers.query_results import collect_query_parameters
|
||||||
@@ -21,7 +23,7 @@ from authentication import current_org
|
|||||||
# removed once we refactor the query results API endpoints and handling
|
# removed once we refactor the query results API endpoints and handling
|
||||||
# on the client side. Please don't reuse in other API handlers.
|
# on the client side. Please don't reuse in other API handlers.
|
||||||
#
|
#
|
||||||
def run_query_sync(data_source, parameter_values, query_text):
|
def run_query_sync(data_source, parameter_values, query_text, max_age=0):
|
||||||
query_parameters = set(collect_query_parameters(query_text))
|
query_parameters = set(collect_query_parameters(query_text))
|
||||||
missing_params = set(query_parameters) - set(parameter_values.keys())
|
missing_params = set(query_parameters) - set(parameter_values.keys())
|
||||||
if missing_params:
|
if missing_params:
|
||||||
@@ -30,12 +32,35 @@ def run_query_sync(data_source, parameter_values, query_text):
|
|||||||
if query_parameters:
|
if query_parameters:
|
||||||
query_text = pystache.render(query_text, parameter_values)
|
query_text = pystache.render(query_text, parameter_values)
|
||||||
|
|
||||||
|
if max_age <= 0:
|
||||||
|
query_result = None
|
||||||
|
else:
|
||||||
|
query_result = models.QueryResult.get_latest(data_source, query_text, max_age)
|
||||||
|
|
||||||
|
query_hash = gen_query_hash(query_text)
|
||||||
|
|
||||||
|
if query_result:
|
||||||
|
logging.info("Returning cached result for query %s" % query_hash)
|
||||||
|
return query_result.data
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
started_at = time.time()
|
||||||
data, error = data_source.query_runner.run_query(query_text)
|
data, error = data_source.query_runner.run_query(query_text)
|
||||||
|
|
||||||
if error:
|
if error:
|
||||||
return None
|
return None
|
||||||
|
# update cache
|
||||||
|
if max_age > 0:
|
||||||
|
run_time = time.time() - started_at
|
||||||
|
query_result, updated_query_ids = models.QueryResult.store_result(data_source.org_id, data_source.id,
|
||||||
|
query_hash, query_text, data,
|
||||||
|
run_time, utils.utcnow())
|
||||||
|
|
||||||
return data
|
return data
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
|
if max_age > 0:
|
||||||
|
abort(404, message="Unable to get result from the database, and no cached query result found.")
|
||||||
|
else:
|
||||||
abort(503, message="Unable to get result from the database.")
|
abort(503, message="Unable to get result from the database.")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@@ -59,7 +84,8 @@ def embed(query_id, visualization_id, org_slug=None):
|
|||||||
# WARNING: Note that the external query parameters
|
# WARNING: Note that the external query parameters
|
||||||
# are a potential risk of SQL injections.
|
# are a potential risk of SQL injections.
|
||||||
#
|
#
|
||||||
results = run_query_sync(query.data_source, parameter_values, query.query)
|
max_age = int(request.args.get('maxAge', 0))
|
||||||
|
results = run_query_sync(query.data_source, parameter_values, query.query, max_age=max_age)
|
||||||
if results is None:
|
if results is None:
|
||||||
abort(400, message="Unable to get results for this query")
|
abort(400, message="Unable to get results for this query")
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -16,21 +16,29 @@ class TestEmbedVisualization(BaseTestCase):
|
|||||||
# set configuration
|
# set configuration
|
||||||
settings.ALLOW_PARAMETERS_IN_EMBEDS = True
|
settings.ALLOW_PARAMETERS_IN_EMBEDS = True
|
||||||
|
|
||||||
|
try:
|
||||||
vis = self.factory.create_visualization_with_params()
|
vis = self.factory.create_visualization_with_params()
|
||||||
param1_name = "param1"
|
param1_name = "param1"
|
||||||
param1_value = "12345"
|
param1_value = "12345"
|
||||||
|
|
||||||
res = self.make_request("get", "/embed/query/{}/visualization/{}?p_{}={}".format(vis.query.id, vis.id, param1_name, param1_value), is_json=False)
|
res = self.make_request("get", "/embed/query/{}/visualization/{}?p_{}={}".format(vis.query.id, vis.id, param1_name, param1_value), is_json=False)
|
||||||
|
|
||||||
# reset configuration
|
|
||||||
settings.ALLOW_PARAMETERS_IN_EMBEDS = previous
|
|
||||||
|
|
||||||
# Currently we are expecting a 503 error which indicates that
|
# Currently we are expecting a 503 error which indicates that
|
||||||
# the database is unavailable. This ensures that the code in embed.py
|
# the database is unavailable. This ensures that the code in embed.py
|
||||||
# reaches the point where a DB query is made, where we then fail
|
# reaches the point where a DB query is made, where we then fail
|
||||||
# intentionally (because DB connection is not available in the tests).
|
# intentionally (because DB connection is not available in the tests).
|
||||||
self.assertEqual(res.status_code, 503)
|
self.assertEqual(res.status_code, 503)
|
||||||
|
|
||||||
|
# run embed query with maxAge to test caching
|
||||||
|
res = self.make_request("get", "/embed/query/{}/visualization/{}?p_{}={}&maxAge=60".format(vis.query.id, vis.id, param1_name, param1_value), is_json=False)
|
||||||
|
# If the 'maxAge' parameter is set and the query fails (because DB connection
|
||||||
|
# is not available in the tests), we're expecting a 404 error here.
|
||||||
|
self.assertEqual(res.status_code, 404)
|
||||||
|
|
||||||
|
finally:
|
||||||
|
# reset configuration
|
||||||
|
settings.ALLOW_PARAMETERS_IN_EMBEDS = previous
|
||||||
|
|
||||||
|
|
||||||
class TestPublicDashboard(BaseTestCase):
|
class TestPublicDashboard(BaseTestCase):
|
||||||
def test_success(self):
|
def test_success(self):
|
||||||
|
|||||||
Reference in New Issue
Block a user