Skip to content
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

feat: add a config to enable retina quality images in screenshots #17409

Merged
merged 3 commits into from
Nov 15, 2021
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 16 additions & 4 deletions superset/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -848,6 +848,8 @@ class CeleryConfig: # pylint: disable=too-few-public-methods
CSV_TO_HIVE_UPLOAD_DIRECTORY = "EXTERNAL_HIVE_TABLES/"
# Function that creates upload directory dynamically based on the
# database used, user and schema provided.


def CSV_TO_HIVE_UPLOAD_DIRECTORY_FUNC( # pylint: disable=invalid-name
database: "Database",
user: "models.User", # pylint: disable=unused-argument
Expand Down Expand Up @@ -951,7 +953,9 @@ def CSV_TO_HIVE_UPLOAD_DIRECTORY_FUNC( # pylint: disable=invalid-name
# Provide a callable that receives a tracking_url and returns another
# URL. This is used to translate internal Hadoop job tracker URL
# into a proxied one
TRACKING_URL_TRANSFORMER = lambda x: x
def TRACKING_URL_TRANSFORMER(x): # pylint: disable=invalid-name
return x


# Interval between consecutive polls when using Hive Engine
HIVE_POLL_INTERVAL = int(timedelta(seconds=5).total_seconds())
Expand Down Expand Up @@ -1085,7 +1089,11 @@ def SQL_QUERY_MUTATOR( # pylint: disable=invalid-name,unused-argument
WEBDRIVER_TYPE = "firefox"

# Window size - this will impact the rendering of the data
WEBDRIVER_WINDOW = {"dashboard": (1600, 2000), "slice": (3000, 1200)}
WEBDRIVER_WINDOW = {
"dashboard": (1600, 2000),
"slice": (3000, 1200),
"pixel_density": 1,
}

# An optional override to the default auth hook used to provide auth to the
# offline webdriver
Expand Down Expand Up @@ -1215,7 +1223,9 @@ def SQL_QUERY_MUTATOR( # pylint: disable=invalid-name,unused-argument
# to allow mutating the object with this callback.
# This can be used to set any properties of the object based on naming
# conventions and such. You can find examples in the tests.
SQLA_TABLE_MUTATOR = lambda table: table
def SQLA_TABLE_MUTATOR(table): # pylint: disable=invalid-name
return table


# Global async query config options.
# Requires GLOBAL_ASYNC_QUERIES feature flag to be enabled.
Expand Down Expand Up @@ -1302,7 +1312,9 @@ def SQL_QUERY_MUTATOR( # pylint: disable=invalid-name,unused-argument
elif importlib.util.find_spec("superset_config") and not is_test():
try:
import superset_config # pylint: disable=import-error
from superset_config import * # type: ignore # pylint: disable=import-error,wildcard-import,unused-wildcard-import

# type: ignore # pylint: disable=import-error,wildcard-import,unused-wildcard-import
from superset_config import *

print(f"Loaded your LOCAL configuration at [{superset_config.__file__}]")
except Exception:
Expand Down
39 changes: 30 additions & 9 deletions superset/utils/webdriver.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
TimeoutException,
WebDriverException,
)
from selenium.webdriver import chrome, firefox
from selenium.webdriver import chrome, firefox, FirefoxProfile
from selenium.webdriver.common.by import By
from selenium.webdriver.remote.webdriver import WebDriver
from selenium.webdriver.support import expected_conditions as EC
Expand All @@ -52,30 +52,45 @@ class DashboardStandaloneMode(Enum):

class WebDriverProxy:
def __init__(
self, driver_type: str, window: Optional[WindowSize] = None,
self,
driver_type: str,
window: Optional[WindowSize] = None,
):
self._driver_type = driver_type
self._window: WindowSize = window or (800, 600)
self._screenshot_locate_wait = current_app.config["SCREENSHOT_LOCATE_WAIT"]
self._screenshot_load_wait = current_app.config["SCREENSHOT_LOAD_WAIT"]

def create(self) -> WebDriver:
pixel_density = current_app.config["WEBDRIVER_WINDOW"].get(
"pixel_density", 1)
if self._driver_type == "firefox":
driver_class = firefox.webdriver.WebDriver
options = firefox.options.Options()
profile = FirefoxProfile()
profile.set_preference(
"layout.css.devPixelsPerPx",
str(pixel_density),
)
kwargs: Dict[Any, Any] = dict(
options=options, firefox_profile=profile)
elif self._driver_type == "chrome":
driver_class = chrome.webdriver.WebDriver
options = chrome.options.Options()
options.add_argument(f"--window-size={self._window[0]},{self._window[1]}")
options.add_argument(
f"--force-device-scale-factor={pixel_density}")
options.add_argument(
f"--window-size={self._window[0]},{self._window[1]}")
kwargs = dict(options=options)
else:
raise Exception(f"Webdriver name ({self._driver_type}) not supported")
raise Exception(
f"Webdriver name ({self._driver_type}) not supported")
# Prepare args for the webdriver init

# Add additional configured options
for arg in current_app.config["WEBDRIVER_OPTION_ARGS"]:
options.add_argument(arg)

kwargs: Dict[Any, Any] = dict(options=options)
kwargs.update(current_app.config["WEBDRIVER_CONFIGURATION"])
logger.info("Init selenium driver")

Expand All @@ -102,7 +117,10 @@ def destroy(driver: WebDriver, tries: int = 2) -> None:
pass

def get_screenshot(
self, url: str, element_name: str, user: "User",
self,
url: str,
element_name: str,
user: "User",
) -> Optional[bytes]:
params = {"standalone": DashboardStandaloneMode.REPORT.value}
req = PreparedRequest()
Expand Down Expand Up @@ -135,12 +153,14 @@ def get_screenshot(
selenium_animation_wait = current_app.config[
"SCREENSHOT_SELENIUM_ANIMATION_WAIT"
]
logger.debug("Wait %i seconds for chart animation", selenium_animation_wait)
logger.debug("Wait %i seconds for chart animation",
selenium_animation_wait)
sleep(selenium_animation_wait)
logger.info("Taking a PNG screenshot of url %s", url)
img = element.screenshot_as_png
except TimeoutException:
logger.warning("Selenium timed out requesting url %s", url, exc_info=True)
logger.warning(
"Selenium timed out requesting url %s", url, exc_info=True)
img = element.screenshot_as_png
except StaleElementReferenceException:
logger.error(
Expand All @@ -151,5 +171,6 @@ def get_screenshot(
except WebDriverException as ex:
logger.error(ex, exc_info=True)
finally:
self.destroy(driver, current_app.config["SCREENSHOT_SELENIUM_RETRIES"])
self.destroy(
driver, current_app.config["SCREENSHOT_SELENIUM_RETRIES"])
return img