-
Notifications
You must be signed in to change notification settings - Fork 14.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix(thumbnail cache): Enabling force parameter on screenshot/thumbnail cache #31757
Merged
Merged
Changes from 4 commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
1b4ca40
enabling force parameter on screenshot endpoints
fisjac 7bae9d4
test fixes
fisjac 896b029
test-fix
fisjac 7dc75c7
Implement error cache ttl check
kgabryje 33a3742
Move error ttl logic to cache class
kgabryje b4f001d
address comments
kgabryje 684d733
change comment
kgabryje feba297
more drying
kgabryje File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -30,7 +30,7 @@ | |||||
from werkzeug.wrappers import Response as WerkzeugResponse | ||||||
from werkzeug.wsgi import FileWrapper | ||||||
|
||||||
from superset import app, is_feature_enabled, thumbnail_cache | ||||||
from superset import app, is_feature_enabled | ||||||
from superset.charts.filters import ( | ||||||
ChartAllTextFilter, | ||||||
ChartCertifiedFilter, | ||||||
|
@@ -84,7 +84,12 @@ | |||||
from superset.tasks.thumbnails import cache_chart_thumbnail | ||||||
from superset.tasks.utils import get_current_user | ||||||
from superset.utils import json | ||||||
from superset.utils.screenshots import ChartScreenshot, DEFAULT_CHART_WINDOW_SIZE | ||||||
from superset.utils.screenshots import ( | ||||||
ChartScreenshot, | ||||||
DEFAULT_CHART_WINDOW_SIZE, | ||||||
ScreenshotCachePayload, | ||||||
StatusValues, | ||||||
) | ||||||
from superset.utils.urls import get_url_path | ||||||
from superset.views.base_api import ( | ||||||
BaseSupersetModelRestApi, | ||||||
|
@@ -564,8 +569,14 @@ | |||||
schema: | ||||||
$ref: '#/components/schemas/screenshot_query_schema' | ||||||
responses: | ||||||
200: | ||||||
description: Chart async result | ||||||
content: | ||||||
application/json: | ||||||
schema: | ||||||
$ref: "#/components/schemas/ChartCacheScreenshotResponseSchema" | ||||||
202: | ||||||
description: Chart async result | ||||||
description: Chart async task created | ||||||
content: | ||||||
application/json: | ||||||
schema: | ||||||
|
@@ -580,6 +591,7 @@ | |||||
$ref: '#/components/responses/500' | ||||||
""" | ||||||
rison_dict = kwargs["rison"] | ||||||
force = rison_dict.get("force") | ||||||
window_size = rison_dict.get("window_size") or DEFAULT_CHART_WINDOW_SIZE | ||||||
|
||||||
# Don't shrink the image if thumb_size is not specified | ||||||
|
@@ -591,25 +603,45 @@ | |||||
|
||||||
chart_url = get_url_path("Superset.slice", slice_id=chart.id) | ||||||
screenshot_obj = ChartScreenshot(chart_url, chart.digest) | ||||||
cache_key = screenshot_obj.cache_key(window_size, thumb_size) | ||||||
cache_key = screenshot_obj.get_cache_key(window_size, thumb_size) | ||||||
cache_payload = ( | ||||||
screenshot_obj.get_from_cache_key(cache_key) or ScreenshotCachePayload() | ||||||
) | ||||||
image_url = get_url_path( | ||||||
"ChartRestApi.screenshot", pk=chart.id, digest=cache_key | ||||||
) | ||||||
|
||||||
def trigger_celery() -> WerkzeugResponse: | ||||||
def build_response(status_code: int) -> WerkzeugResponse: | ||||||
return self.response( | ||||||
status_code, | ||||||
cache_key=cache_key, | ||||||
chart_url=chart_url, | ||||||
image_url=image_url, | ||||||
task_updated_at=cache_payload.get_timestamp(), | ||||||
task_status=cache_payload.get_status(), | ||||||
) | ||||||
|
||||||
error_cache_ttl = config["THUMBNAIL_ERROR_CACHE_TTL"] | ||||||
error_cache_expired = ( | ||||||
datetime.now() | ||||||
- datetime.strptime(cache_payload.get_timestamp(), "%Y/%m/%d-%H:%M:%S") | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same
Suggested change
|
||||||
).total_seconds() > error_cache_ttl | ||||||
if ( | ||||||
cache_payload.status == StatusValues.PENDING | ||||||
or (cache_payload.status == StatusValues.ERROR and error_cache_expired) | ||||||
or force | ||||||
): | ||||||
logger.info("Triggering screenshot ASYNC") | ||||||
screenshot_obj.cache.set(cache_key, ScreenshotCachePayload()) | ||||||
cache_chart_thumbnail.delay( | ||||||
current_user=get_current_user(), | ||||||
chart_id=chart.id, | ||||||
force=True, | ||||||
window_size=window_size, | ||||||
thumb_size=thumb_size, | ||||||
force=force, | ||||||
) | ||||||
return self.response( | ||||||
202, cache_key=cache_key, chart_url=chart_url, image_url=image_url | ||||||
) | ||||||
|
||||||
return trigger_celery() | ||||||
return build_response(202) | ||||||
return build_response(200) | ||||||
|
||||||
@expose("/<pk>/screenshot/<digest>/", methods=("GET",)) | ||||||
@protect() | ||||||
|
@@ -635,7 +667,7 @@ | |||||
name: digest | ||||||
responses: | ||||||
200: | ||||||
description: Chart thumbnail image | ||||||
description: Chart screenshot image | ||||||
content: | ||||||
image/*: | ||||||
schema: | ||||||
|
@@ -652,16 +684,16 @@ | |||||
""" | ||||||
chart = self.datamodel.get(pk, self._base_filters) | ||||||
|
||||||
# Making sure the chart still exists | ||||||
if not chart: | ||||||
return self.response_404() | ||||||
|
||||||
# fetch the chart screenshot using the current user and cache if set | ||||||
if img := ChartScreenshot.get_from_cache_key(thumbnail_cache, digest): | ||||||
return Response( | ||||||
FileWrapper(img), mimetype="image/png", direct_passthrough=True | ||||||
) | ||||||
# TODO: return an empty image | ||||||
if cache_payload := ChartScreenshot.get_from_cache_key(digest): | ||||||
if cache_payload.status == StatusValues.UPDATED: | ||||||
return Response( | ||||||
FileWrapper(cache_payload.get_image()), | ||||||
mimetype="image/png", | ||||||
direct_passthrough=True, | ||||||
) | ||||||
return self.response_404() | ||||||
|
||||||
@expose("/<pk>/thumbnail/<digest>/", methods=("GET",)) | ||||||
|
@@ -685,9 +717,10 @@ | |||||
type: integer | ||||||
name: pk | ||||||
- in: path | ||||||
name: digest | ||||||
description: A hex digest that makes this chart unique | ||||||
schema: | ||||||
type: string | ||||||
name: digest | ||||||
responses: | ||||||
200: | ||||||
description: Chart thumbnail image | ||||||
|
@@ -712,44 +745,41 @@ | |||||
return self.response_404() | ||||||
|
||||||
current_user = get_current_user() | ||||||
url = get_url_path("Superset.slice", slice_id=chart.id) | ||||||
if kwargs["rison"].get("force", False): | ||||||
logger.info( | ||||||
"Triggering thumbnail compute (chart id: %s) ASYNC", str(chart.id) | ||||||
) | ||||||
cache_chart_thumbnail.delay( | ||||||
current_user=current_user, | ||||||
chart_id=chart.id, | ||||||
force=True, | ||||||
if chart.digest != digest: | ||||||
self.incr_stats("redirect", self.thumbnail.__name__) | ||||||
return redirect( | ||||||
url_for( | ||||||
f"{self.__class__.__name__}.thumbnail", pk=pk, digest=chart.digest | ||||||
) | ||||||
) | ||||||
return self.response(202, message="OK Async") | ||||||
# fetch the chart screenshot using the current user and cache if set | ||||||
screenshot = ChartScreenshot(url, chart.digest).get_from_cache( | ||||||
cache=thumbnail_cache | ||||||
url = get_url_path("Superset.slice", slice_id=chart.id) | ||||||
screenshot_obj = ChartScreenshot(url, chart.digest) | ||||||
cache_key = screenshot_obj.get_cache_key() | ||||||
cache_payload = ( | ||||||
screenshot_obj.get_from_cache_key(cache_key) or ScreenshotCachePayload() | ||||||
) | ||||||
# If not screenshot then send request to compute thumb to celery | ||||||
if not screenshot: | ||||||
|
||||||
if cache_payload.status in [StatusValues.PENDING, StatusValues.ERROR]: | ||||||
villebro marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
self.incr_stats("async", self.thumbnail.__name__) | ||||||
logger.info( | ||||||
"Triggering thumbnail compute (chart id: %s) ASYNC", str(chart.id) | ||||||
) | ||||||
screenshot_obj.cache.set(cache_key, ScreenshotCachePayload()) | ||||||
cache_chart_thumbnail.delay( | ||||||
current_user=current_user, | ||||||
chart_id=chart.id, | ||||||
force=True, | ||||||
force=False, | ||||||
) | ||||||
return self.response(202, message="OK Async") | ||||||
# If digests | ||||||
if chart.digest != digest: | ||||||
self.incr_stats("redirect", self.thumbnail.__name__) | ||||||
return redirect( | ||||||
url_for( | ||||||
f"{self.__class__.__name__}.thumbnail", pk=pk, digest=chart.digest | ||||||
) | ||||||
return self.response( | ||||||
202, | ||||||
task_updated_at=cache_payload.get_timestamp(), | ||||||
task_status=cache_payload.get_status(), | ||||||
) | ||||||
self.incr_stats("from_cache", self.thumbnail.__name__) | ||||||
return Response( | ||||||
FileWrapper(screenshot), mimetype="image/png", direct_passthrough=True | ||||||
FileWrapper(cache_payload.get_image()), | ||||||
mimetype="image/png", | ||||||
direct_passthrough=True, | ||||||
) | ||||||
|
||||||
@expose("/export/", methods=("GET",)) | ||||||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry about the back and forth here, but this seems more appropriate