mirror of
https://github.com/apache/superset.git
synced 2026-04-19 08:04:53 +00:00
feat(thumbnails): add support for user specific thumbs (#22328)
This commit is contained in:
@@ -16,8 +16,11 @@
|
||||
# under the License.
|
||||
# from superset import db
|
||||
# from superset.models.dashboard import Dashboard
|
||||
|
||||
import json
|
||||
import urllib.request
|
||||
from io import BytesIO
|
||||
from typing import Tuple
|
||||
from unittest import skipUnless
|
||||
from unittest.mock import ANY, call, MagicMock, patch
|
||||
|
||||
@@ -29,14 +32,22 @@ from superset import db, is_feature_enabled, security_manager
|
||||
from superset.extensions import machine_auth_provider_factory
|
||||
from superset.models.dashboard import Dashboard
|
||||
from superset.models.slice import Slice
|
||||
from superset.tasks.types import ExecutorType
|
||||
from superset.utils.screenshots import ChartScreenshot, DashboardScreenshot
|
||||
from superset.utils.urls import get_url_host, get_url_path
|
||||
from superset.utils.urls import get_url_path
|
||||
from superset.utils.webdriver import find_unexpected_errors, WebDriverProxy
|
||||
from tests.integration_tests.conftest import with_feature_flags
|
||||
from tests.integration_tests.fixtures.birth_names_dashboard import (
|
||||
load_birth_names_dashboard_with_slices,
|
||||
load_birth_names_data,
|
||||
)
|
||||
from tests.integration_tests.test_app import app
|
||||
|
||||
from .base_tests import SupersetTestCase
|
||||
|
||||
CHART_URL = "/api/v1/chart/"
|
||||
DASHBOARD_URL = "/api/v1/dashboard/"
|
||||
|
||||
|
||||
class TestThumbnailsSeleniumLive(LiveServerTestCase):
|
||||
def create_app(self):
|
||||
@@ -54,11 +65,14 @@ class TestThumbnailsSeleniumLive(LiveServerTestCase):
|
||||
"""
|
||||
Thumbnails: Simple get async dashboard screenshot
|
||||
"""
|
||||
dashboard = db.session.query(Dashboard).all()[0]
|
||||
with patch("superset.dashboards.api.DashboardRestApi.get") as mock_get:
|
||||
rv = self.client.get(DASHBOARD_URL)
|
||||
resp = json.loads(rv.data.decode("utf-8"))
|
||||
thumbnail_url = resp["result"][0]["thumbnail_url"]
|
||||
|
||||
response = self.url_open_auth(
|
||||
"admin",
|
||||
f"api/v1/dashboard/{dashboard.id}/thumbnail/{dashboard.digest}/",
|
||||
thumbnail_url,
|
||||
)
|
||||
self.assertEqual(response.getcode(), 202)
|
||||
|
||||
@@ -187,50 +201,82 @@ class TestWebDriverProxy(SupersetTestCase):
|
||||
class TestThumbnails(SupersetTestCase):
|
||||
|
||||
mock_image = b"bytes mock image"
|
||||
digest_return_value = "foo_bar"
|
||||
digest_hash = "5c7d96a3dd7a87850a2ef34087565a6e"
|
||||
|
||||
def _get_id_and_thumbnail_url(self, url: str) -> Tuple[int, str]:
|
||||
rv = self.client.get(url)
|
||||
resp = json.loads(rv.data.decode("utf-8"))
|
||||
obj = resp["result"][0]
|
||||
return obj["id"], obj["thumbnail_url"]
|
||||
|
||||
@pytest.mark.usefixtures("load_birth_names_dashboard_with_slices")
|
||||
@with_feature_flags(THUMBNAILS=False)
|
||||
def test_dashboard_thumbnail_disabled(self):
|
||||
"""
|
||||
Thumbnails: Dashboard thumbnail disabled
|
||||
"""
|
||||
dashboard = db.session.query(Dashboard).all()[0]
|
||||
self.login(username="admin")
|
||||
uri = f"api/v1/dashboard/{dashboard.id}/thumbnail/{dashboard.digest}/"
|
||||
rv = self.client.get(uri)
|
||||
_, thumbnail_url = self._get_id_and_thumbnail_url(DASHBOARD_URL)
|
||||
rv = self.client.get(thumbnail_url)
|
||||
self.assertEqual(rv.status_code, 404)
|
||||
|
||||
@pytest.mark.usefixtures("load_birth_names_dashboard_with_slices")
|
||||
@with_feature_flags(THUMBNAILS=False)
|
||||
def test_chart_thumbnail_disabled(self):
|
||||
"""
|
||||
Thumbnails: Chart thumbnail disabled
|
||||
"""
|
||||
chart = db.session.query(Slice).all()[0]
|
||||
self.login(username="admin")
|
||||
uri = f"api/v1/chart/{chart}/thumbnail/{chart.digest}/"
|
||||
rv = self.client.get(uri)
|
||||
_, thumbnail_url = self._get_id_and_thumbnail_url(CHART_URL)
|
||||
rv = self.client.get(thumbnail_url)
|
||||
self.assertEqual(rv.status_code, 404)
|
||||
|
||||
@pytest.mark.usefixtures("load_birth_names_dashboard_with_slices")
|
||||
@with_feature_flags(THUMBNAILS=True)
|
||||
def test_get_async_dashboard_screenshot(self):
|
||||
def test_get_async_dashboard_screenshot_as_selenium(self):
|
||||
"""
|
||||
Thumbnails: Simple get async dashboard screenshot
|
||||
Thumbnails: Simple get async dashboard screenshot as selenium user
|
||||
"""
|
||||
dashboard = db.session.query(Dashboard).all()[0]
|
||||
self.login(username="admin")
|
||||
uri = f"api/v1/dashboard/{dashboard.id}/thumbnail/{dashboard.digest}/"
|
||||
self.login(username="alpha")
|
||||
with patch(
|
||||
"superset.tasks.thumbnails.cache_dashboard_thumbnail.delay"
|
||||
) as mock_task:
|
||||
rv = self.client.get(uri)
|
||||
"superset.thumbnails.digest._adjust_string_for_executor"
|
||||
) as mock_adjust_string:
|
||||
mock_adjust_string.return_value = self.digest_return_value
|
||||
_, thumbnail_url = self._get_id_and_thumbnail_url(DASHBOARD_URL)
|
||||
assert self.digest_hash in thumbnail_url
|
||||
assert mock_adjust_string.call_args[0][1] == ExecutorType.SELENIUM
|
||||
assert mock_adjust_string.call_args[0][2] == "admin"
|
||||
|
||||
rv = self.client.get(thumbnail_url)
|
||||
self.assertEqual(rv.status_code, 202)
|
||||
|
||||
expected_uri = f"{get_url_host()}superset/dashboard/{dashboard.id}/"
|
||||
expected_digest = dashboard.digest
|
||||
expected_kwargs = {"force": True}
|
||||
mock_task.assert_called_with(
|
||||
expected_uri, expected_digest, **expected_kwargs
|
||||
)
|
||||
@pytest.mark.usefixtures("load_birth_names_dashboard_with_slices")
|
||||
@with_feature_flags(THUMBNAILS=True)
|
||||
def test_get_async_dashboard_screenshot_as_current_user(self):
|
||||
"""
|
||||
Thumbnails: Simple get async dashboard screenshot as current user
|
||||
"""
|
||||
username = "alpha"
|
||||
self.login(username=username)
|
||||
with patch.dict(
|
||||
"superset.thumbnails.digest.current_app.config",
|
||||
{
|
||||
"THUMBNAIL_EXECUTE_AS": [ExecutorType.CURRENT_USER],
|
||||
},
|
||||
), patch(
|
||||
"superset.thumbnails.digest._adjust_string_for_executor"
|
||||
) as mock_adjust_string:
|
||||
mock_adjust_string.return_value = self.digest_return_value
|
||||
_, thumbnail_url = self._get_id_and_thumbnail_url(DASHBOARD_URL)
|
||||
assert self.digest_hash in thumbnail_url
|
||||
assert mock_adjust_string.call_args[0][1] == ExecutorType.CURRENT_USER
|
||||
assert mock_adjust_string.call_args[0][2] == username
|
||||
|
||||
rv = self.client.get(thumbnail_url)
|
||||
self.assertEqual(rv.status_code, 202)
|
||||
|
||||
@pytest.mark.usefixtures("load_birth_names_dashboard_with_slices")
|
||||
@with_feature_flags(THUMBNAILS=True)
|
||||
def test_get_async_dashboard_notfound(self):
|
||||
"""
|
||||
@@ -242,37 +288,62 @@ class TestThumbnails(SupersetTestCase):
|
||||
rv = self.client.get(uri)
|
||||
self.assertEqual(rv.status_code, 404)
|
||||
|
||||
@pytest.mark.usefixtures("load_birth_names_dashboard_with_slices")
|
||||
@skipUnless((is_feature_enabled("THUMBNAILS")), "Thumbnails feature")
|
||||
def test_get_async_dashboard_not_allowed(self):
|
||||
"""
|
||||
Thumbnails: Simple get async dashboard not allowed
|
||||
"""
|
||||
dashboard = db.session.query(Dashboard).all()[0]
|
||||
self.login(username="gamma")
|
||||
uri = f"api/v1/dashboard/{dashboard.id}/thumbnail/{dashboard.digest}/"
|
||||
rv = self.client.get(uri)
|
||||
_, thumbnail_url = self._get_id_and_thumbnail_url(DASHBOARD_URL)
|
||||
rv = self.client.get(thumbnail_url)
|
||||
self.assertEqual(rv.status_code, 404)
|
||||
|
||||
@pytest.mark.usefixtures("load_birth_names_dashboard_with_slices")
|
||||
@with_feature_flags(THUMBNAILS=True)
|
||||
def test_get_async_chart_screenshot(self):
|
||||
def test_get_async_chart_screenshot_as_selenium(self):
|
||||
"""
|
||||
Thumbnails: Simple get async chart screenshot
|
||||
Thumbnails: Simple get async chart screenshot as selenium user
|
||||
"""
|
||||
chart = db.session.query(Slice).all()[0]
|
||||
self.login(username="admin")
|
||||
uri = f"api/v1/chart/{chart.id}/thumbnail/{chart.digest}/"
|
||||
self.login(username="alpha")
|
||||
with patch(
|
||||
"superset.tasks.thumbnails.cache_chart_thumbnail.delay"
|
||||
) as mock_task:
|
||||
rv = self.client.get(uri)
|
||||
self.assertEqual(rv.status_code, 202)
|
||||
expected_uri = f"{get_url_host()}superset/slice/{chart.id}/?standalone=true"
|
||||
expected_digest = chart.digest
|
||||
expected_kwargs = {"force": True}
|
||||
mock_task.assert_called_with(
|
||||
expected_uri, expected_digest, **expected_kwargs
|
||||
)
|
||||
"superset.thumbnails.digest._adjust_string_for_executor"
|
||||
) as mock_adjust_string:
|
||||
mock_adjust_string.return_value = self.digest_return_value
|
||||
_, thumbnail_url = self._get_id_and_thumbnail_url(CHART_URL)
|
||||
assert self.digest_hash in thumbnail_url
|
||||
assert mock_adjust_string.call_args[0][1] == ExecutorType.SELENIUM
|
||||
assert mock_adjust_string.call_args[0][2] == "admin"
|
||||
|
||||
rv = self.client.get(thumbnail_url)
|
||||
self.assertEqual(rv.status_code, 202)
|
||||
|
||||
@pytest.mark.usefixtures("load_birth_names_dashboard_with_slices")
|
||||
@with_feature_flags(THUMBNAILS=True)
|
||||
def test_get_async_chart_screenshot_as_current_user(self):
|
||||
"""
|
||||
Thumbnails: Simple get async chart screenshot as current user
|
||||
"""
|
||||
username = "alpha"
|
||||
self.login(username=username)
|
||||
with patch.dict(
|
||||
"superset.thumbnails.digest.current_app.config",
|
||||
{
|
||||
"THUMBNAIL_EXECUTE_AS": [ExecutorType.CURRENT_USER],
|
||||
},
|
||||
), patch(
|
||||
"superset.thumbnails.digest._adjust_string_for_executor"
|
||||
) as mock_adjust_string:
|
||||
mock_adjust_string.return_value = self.digest_return_value
|
||||
_, thumbnail_url = self._get_id_and_thumbnail_url(CHART_URL)
|
||||
assert self.digest_hash in thumbnail_url
|
||||
assert mock_adjust_string.call_args[0][1] == ExecutorType.CURRENT_USER
|
||||
assert mock_adjust_string.call_args[0][2] == username
|
||||
|
||||
rv = self.client.get(thumbnail_url)
|
||||
self.assertEqual(rv.status_code, 202)
|
||||
|
||||
@pytest.mark.usefixtures("load_birth_names_dashboard_with_slices")
|
||||
@with_feature_flags(THUMBNAILS=True)
|
||||
def test_get_async_chart_notfound(self):
|
||||
"""
|
||||
@@ -284,66 +355,62 @@ class TestThumbnails(SupersetTestCase):
|
||||
rv = self.client.get(uri)
|
||||
self.assertEqual(rv.status_code, 404)
|
||||
|
||||
@pytest.mark.usefixtures("load_birth_names_dashboard_with_slices")
|
||||
@with_feature_flags(THUMBNAILS=True)
|
||||
def test_get_cached_chart_wrong_digest(self):
|
||||
"""
|
||||
Thumbnails: Simple get chart with wrong digest
|
||||
"""
|
||||
chart = db.session.query(Slice).all()[0]
|
||||
with patch.object(
|
||||
ChartScreenshot, "get_from_cache", return_value=BytesIO(self.mock_image)
|
||||
):
|
||||
self.login(username="admin")
|
||||
uri = f"api/v1/chart/{chart.id}/thumbnail/1234/"
|
||||
rv = self.client.get(uri)
|
||||
id_, thumbnail_url = self._get_id_and_thumbnail_url(CHART_URL)
|
||||
rv = self.client.get(f"api/v1/chart/{id_}/thumbnail/1234/")
|
||||
self.assertEqual(rv.status_code, 302)
|
||||
self.assertRedirects(
|
||||
rv, f"api/v1/chart/{chart.id}/thumbnail/{chart.digest}/"
|
||||
)
|
||||
self.assertRedirects(rv, thumbnail_url)
|
||||
|
||||
@pytest.mark.usefixtures("load_birth_names_dashboard_with_slices")
|
||||
@with_feature_flags(THUMBNAILS=True)
|
||||
def test_get_cached_dashboard_screenshot(self):
|
||||
"""
|
||||
Thumbnails: Simple get cached dashboard screenshot
|
||||
"""
|
||||
dashboard = db.session.query(Dashboard).all()[0]
|
||||
with patch.object(
|
||||
DashboardScreenshot, "get_from_cache", return_value=BytesIO(self.mock_image)
|
||||
):
|
||||
self.login(username="admin")
|
||||
uri = f"api/v1/dashboard/{dashboard.id}/thumbnail/{dashboard.digest}/"
|
||||
rv = self.client.get(uri)
|
||||
_, thumbnail_url = self._get_id_and_thumbnail_url(DASHBOARD_URL)
|
||||
rv = self.client.get(thumbnail_url)
|
||||
self.assertEqual(rv.status_code, 200)
|
||||
self.assertEqual(rv.data, self.mock_image)
|
||||
|
||||
@pytest.mark.usefixtures("load_birth_names_dashboard_with_slices")
|
||||
@with_feature_flags(THUMBNAILS=True)
|
||||
def test_get_cached_chart_screenshot(self):
|
||||
"""
|
||||
Thumbnails: Simple get cached chart screenshot
|
||||
"""
|
||||
chart = db.session.query(Slice).all()[0]
|
||||
with patch.object(
|
||||
ChartScreenshot, "get_from_cache", return_value=BytesIO(self.mock_image)
|
||||
):
|
||||
self.login(username="admin")
|
||||
uri = f"api/v1/chart/{chart.id}/thumbnail/{chart.digest}/"
|
||||
rv = self.client.get(uri)
|
||||
id_, thumbnail_url = self._get_id_and_thumbnail_url(CHART_URL)
|
||||
rv = self.client.get(thumbnail_url)
|
||||
self.assertEqual(rv.status_code, 200)
|
||||
self.assertEqual(rv.data, self.mock_image)
|
||||
|
||||
@pytest.mark.usefixtures("load_birth_names_dashboard_with_slices")
|
||||
@with_feature_flags(THUMBNAILS=True)
|
||||
def test_get_cached_dashboard_wrong_digest(self):
|
||||
"""
|
||||
Thumbnails: Simple get dashboard with wrong digest
|
||||
"""
|
||||
dashboard = db.session.query(Dashboard).all()[0]
|
||||
with patch.object(
|
||||
DashboardScreenshot, "get_from_cache", return_value=BytesIO(self.mock_image)
|
||||
):
|
||||
self.login(username="admin")
|
||||
uri = f"api/v1/dashboard/{dashboard.id}/thumbnail/1234/"
|
||||
rv = self.client.get(uri)
|
||||
id_, thumbnail_url = self._get_id_and_thumbnail_url(DASHBOARD_URL)
|
||||
rv = self.client.get(f"api/v1/dashboard/{id_}/thumbnail/1234/")
|
||||
self.assertEqual(rv.status_code, 302)
|
||||
self.assertRedirects(
|
||||
rv, f"api/v1/dashboard/{dashboard.id}/thumbnail/{dashboard.digest}/"
|
||||
)
|
||||
self.assertRedirects(rv, thumbnail_url)
|
||||
|
||||
Reference in New Issue
Block a user