feat(thumbnails): add support for user specific thumbs (#22328)

This commit is contained in:
Ville Brofeldt
2022-12-14 15:02:31 +02:00
committed by GitHub
parent 1014a327f5
commit aa0cae9b49
26 changed files with 1182 additions and 413 deletions

View File

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