def _get_url( self, user_friendly: bool = False, csv: bool = False, **kwargs: Any ) -> str: """ Get the url for this report schedule: chart or dashboard """ if self._report_schedule.chart: if csv: return get_url_path( "Superset.explore_json", csv="true", form_data=json.dumps({"slice_id": self._report_schedule.chart_id}), ) return get_url_path( "Superset.slice", user_friendly=user_friendly, slice_id=self._report_schedule.chart_id, **kwargs, ) return get_url_path( "Superset.dashboard", user_friendly=user_friendly, dashboard_id_or_slug=self._report_schedule.dashboard_id, **kwargs, )
def _get_url(self, user_friendly: bool = False, csv: bool = False, **kwargs: Any) -> str: """ Get the url for this report schedule: chart or dashboard """ if self._report_schedule.chart: if csv: return get_url_path( "ChartRestApi.get_data", pk=self._report_schedule.chart_id, format=ChartDataResultFormat.CSV.value, type=ChartDataResultType.POST_PROCESSED.value, ) return get_url_path( "Superset.slice", user_friendly=user_friendly, slice_id=self._report_schedule.chart_id, **kwargs, ) return get_url_path( "Superset.dashboard", user_friendly=user_friendly, dashboard_id_or_slug=self._report_schedule.dashboard_id, **kwargs, )
def compute_generic_thumbnail( friendly_type: str, model_cls: Union[Type[Dashboard], Type[Slice]], model_id: int, compute_func: CallableTask, ) -> None: query = db.session.query(model_cls) if model_id: query = query.filter(model_cls.id.in_(model_id)) dashboards = query.all() count = len(dashboards) for i, model in enumerate(dashboards): if asynchronous: func = compute_func.delay action = "Triggering" else: func = compute_func action = "Processing" msg = f'{action} {friendly_type} "{model}" ({i+1}/{count})' click.secho(msg, fg="green") if friendly_type == "chart": url = get_url_path("Superset.slice", slice_id=model.id, standalone="true") else: url = get_url_path("Superset.dashboard", dashboard_id_or_slug=model.id) func(url, model.digest, force=force)
def _get_url( self, user_friendly: bool = False, result_format: Optional[ChartDataResultFormat] = None, **kwargs: Any, ) -> str: """ Get the url for this report schedule: chart or dashboard """ force = "true" if self._report_schedule.force_screenshot else "false" if self._report_schedule.chart: if result_format in { ChartDataResultFormat.CSV, ChartDataResultFormat.JSON, }: return get_url_path( "ChartDataRestApi.get_data", pk=self._report_schedule.chart_id, format=result_format.value, type=ChartDataResultType.POST_PROCESSED.value, force=force, ) return get_url_path( "ExploreView.root", user_friendly=user_friendly, form_data=json.dumps( {"slice_id": self._report_schedule.chart_id}), standalone="true", force=force, **kwargs, ) # If we need to render dashboard in a specific sate, use stateful permalink dashboard_state = self._report_schedule.extra.get("dashboard") if dashboard_state: permalink_key = CreateDashboardPermalinkCommand( dashboard_id=self._report_schedule.dashboard_id, state=dashboard_state, ).run() return get_url_path("Superset.dashboard_permalink", key=permalink_key) return get_url_path( "Superset.dashboard", user_friendly=user_friendly, dashboard_id_or_slug=self._report_schedule.dashboard_id, standalone=DashboardStandaloneMode.REPORT.value, force=force, **kwargs, )
def _get_url(report_schedule: ReportSchedule, user_friendly: bool = False) -> str: """ Get the url for this report schedule: chart or dashboard """ if report_schedule.chart: return get_url_path( "Superset.slice", user_friendly=user_friendly, slice_id=report_schedule.chart_id, standalone="true", ) return get_url_path( "Superset.dashboard", user_friendly=user_friendly, dashboard_id_or_slug=report_schedule.dashboard_id, )
def _get_url(self, user_friendly: bool = False, **kwargs: Any) -> str: """ Get the url for this report schedule: chart or dashboard """ if self._report_schedule.chart: return get_url_path( "Superset.slice", user_friendly=user_friendly, slice_id=self._report_schedule.chart_id, **kwargs, ) return get_url_path( "Superset.dashboard", user_friendly=user_friendly, dashboard_id_or_slug=self._report_schedule.dashboard_id, **kwargs, )
def test_screenshot_selenium_load_wait(self, mock_webdriver, mock_webdriver_wait): app.config["SCREENSHOT_LOAD_WAIT"] = 15 webdriver = WebDriverProxy("firefox") user = security_manager.get_user_by_username( app.config["THUMBNAIL_SELENIUM_USER"]) url = get_url_path("Superset.slice", slice_id=1, standalone="true") webdriver.get_screenshot(url, "chart-container", user=user) assert mock_webdriver_wait.call_args_list[1] == call(ANY, 15)
def test_screenshot_selenium_headstart(self, mock_sleep, mock_webdriver, mock_webdriver_wait): webdriver = WebDriverProxy("firefox") user = security_manager.get_user_by_username( app.config["THUMBNAIL_SELENIUM_USER"]) url = get_url_path("Superset.slice", slice_id=1, standalone="true") app.config["SCREENSHOT_SELENIUM_HEADSTART"] = 5 webdriver.get_screenshot(url, "chart-container", user=user) assert mock_sleep.call_args_list[0] == call(5)
def deliver_alert(alert: Alert, recipients: Optional[str] = None) -> None: logging.info("Triggering alert: %s", alert) img_data = None images = {} recipients = recipients or alert.recipients if alert.slice: chart_url = get_url_path("Superset.slice", slice_id=alert.slice.id, standalone="true") screenshot = ChartScreenshot(chart_url, alert.slice.digest) cache_key = screenshot.cache_key() image_url = get_url_path("ChartRestApi.screenshot", pk=alert.slice.id, digest=cache_key) user = security_manager.find_user( current_app.config["THUMBNAIL_SELENIUM_USER"]) img_data = screenshot.compute_and_cache( user=user, cache=thumbnail_cache, force=True, ) else: # TODO: dashboard delivery! image_url = "https://media.giphy.com/media/dzaUX7CAG0Ihi/giphy.gif" # generate the email subject = f"[Superset] Triggered alert: {alert.label}" deliver_as_group = False data = None if img_data: images = {"screenshot": img_data} body = __( textwrap.dedent("""\ <h2>Alert: %(label)s</h2> <img src="cid:screenshot" alt="%(label)s" /> """), label=alert.label, image_url=image_url, ) _deliver_email(recipients, deliver_as_group, subject, body, data, images)
def deliver_alert(alert_id: int, recipients: Optional[str] = None) -> None: alert = db.session.query(Alert).get(alert_id) logging.info("Triggering alert: %s", alert) img_data = None images = {} recipients = recipients or alert.recipients if alert.slice: chart_url = get_url_path("Superset.slice", slice_id=alert.slice.id, standalone="true") screenshot = ChartScreenshot(chart_url, alert.slice.digest) image_url = _get_url_path( "Superset.slice", user_friendly=True, slice_id=alert.slice.id, standalone="true", ) standalone_index = image_url.find("/?standalone=true") if standalone_index != -1: image_url = image_url[:standalone_index] user = security_manager.find_user( current_app.config["THUMBNAIL_SELENIUM_USER"]) img_data = screenshot.compute_and_cache( user=user, cache=thumbnail_cache, force=True, ) else: # TODO: dashboard delivery! image_url = "https://media.giphy.com/media/dzaUX7CAG0Ihi/giphy.gif" # generate the email # TODO add sql query results to email subject = f"[Superset] Triggered alert: {alert.label}" deliver_as_group = False data = None if img_data: images = {"screenshot": img_data} body = render_template( "email/alert.txt", alert_url=_get_url_path("AlertModelView.show", user_friendly=True, pk=alert.id), label=alert.label, sql=alert.sql, image_url=image_url, ) _deliver_email(recipients, deliver_as_group, subject, body, data, images)
def _get_url( self, user_friendly: bool = False, result_format: Optional[ChartDataResultFormat] = None, **kwargs: Any, ) -> str: """ Get the url for this report schedule: chart or dashboard """ force = "true" if self._report_schedule.force_screenshot else "false" if self._report_schedule.chart: if result_format in { ChartDataResultFormat.CSV, ChartDataResultFormat.JSON, }: return get_url_path( "ChartDataRestApi.get_data", pk=self._report_schedule.chart_id, format=result_format.value, type=ChartDataResultType.POST_PROCESSED.value, force=force, ) return get_url_path( "Superset.explore", user_friendly=user_friendly, form_data=json.dumps( {"slice_id": self._report_schedule.chart_id}), standalone="true", force=force, **kwargs, ) return get_url_path( "Superset.dashboard", user_friendly=user_friendly, dashboard_id_or_slug=self._report_schedule.dashboard_id, standalone=DashboardStandaloneMode.REPORT.value, # force=force, TODO (betodealmeida) implement this **kwargs, )
def test_get_cached_chart_screenshot(self): """ Thumbnails: Simple get cached chart screenshot """ chart = db.session.query(Slice).all()[0] chart_url = get_url_path("Superset.slice", slice_id=chart.id, standalone="true") # Cache a test "image" screenshot = ChartScreenshot(chart_url, chart.digest) thumbnail_cache.set(screenshot.cache_key, self.mock_image) self.login(username="******") uri = f"api/v1/chart/{chart.id}/thumbnail/{chart.digest}/" rv = self.client.get(uri) self.assertEqual(rv.status_code, 200) self.assertEqual(rv.data, self.mock_image)
def test_get_cached_dashboard_screenshot(self): """ Thumbnails: Simple get cached dashboard screenshot """ dashboard = db.session.query(Dashboard).all()[0] dashboard_url = get_url_path("Superset.dashboard", dashboard_id=dashboard.id) # Cache a test "image" screenshot = DashboardScreenshot(dashboard_url, dashboard.digest) thumbnail_cache.set(screenshot.cache_key, self.mock_image) self.login(username="******") uri = f"api/v1/dashboard/{dashboard.id}/thumbnail/{dashboard.digest}/" rv = self.client.get(uri) self.assertEqual(rv.status_code, 200) self.assertEqual(rv.data, self.mock_image)
def _get_slice_screenshot(slice_id: int, session: Session) -> ScreenshotData: slice_obj = session.query(Slice).get(slice_id) chart_url = get_url_path("Superset.slice", slice_id=slice_obj.id, standalone="true") screenshot = ChartScreenshot(chart_url, slice_obj.digest) image_url = _get_url_path( "Superset.slice", user_friendly=True, slice_id=slice_obj.id, ) user = security_manager.find_user(current_app.config["THUMBNAIL_SELENIUM_USER"]) image_data = screenshot.compute_and_cache( user=user, cache=thumbnail_cache, force=True, ) session.commit() return ScreenshotData(image_url, image_data)
def cache_screenshot(self, pk: int, **kwargs: Dict[str, bool]) -> WerkzeugResponse: """ --- get: description: Compute and cache a screenshot. parameters: - in: path schema: type: integer name: pk - in: query name: q content: application/json: schema: $ref: '#/components/schemas/screenshot_query_schema' responses: 200: description: Chart async result content: application/json: schema: $ref: "#/components/schemas/ChartCacheScreenshotResponseSchema" 302: description: Redirects to the current digest 400: $ref: '#/components/responses/400' 401: $ref: '#/components/responses/401' 404: $ref: '#/components/responses/404' 500: $ref: '#/components/responses/500' """ rison_dict = kwargs["rison"] window_size = rison_dict.get("window_size") or (800, 600) # Don't shrink the image if thumb_size is not specified thumb_size = rison_dict.get("thumb_size") or window_size chart = self.datamodel.get(pk, self._base_filters) if not chart: return self.response_404() chart_url = get_url_path("Superset.slice", slice_id=chart.id, standalone="true") screenshot_obj = ChartScreenshot(chart_url, chart.digest) cache_key = screenshot_obj.cache_key(window_size, thumb_size) image_url = get_url_path("ChartRestApi.screenshot", pk=chart.id, digest=cache_key) def trigger_celery() -> WerkzeugResponse: logger.info("Triggering screenshot ASYNC") kwargs = { "url": chart_url, "digest": chart.digest, "force": True, "window_size": window_size, "thumb_size": thumb_size, } cache_chart_thumbnail.delay(**kwargs) return self.response( 202, cache_key=cache_key, chart_url=chart_url, image_url=image_url, ) return trigger_celery()
def thumbnail(self, pk: int, digest: str, **kwargs: Any) -> WerkzeugResponse: """Get Dashboard thumbnail --- get: description: >- Compute async or get already computed dashboard thumbnail from cache. parameters: - in: path schema: type: integer name: pk - in: path name: digest description: A hex digest that makes this dashboard unique schema: type: string - in: query name: q content: application/json: schema: $ref: '#/components/schemas/thumbnail_query_schema' responses: 200: description: Dashboard thumbnail image content: image/*: schema: type: string format: binary 202: description: Thumbnail does not exist on cache, fired async to compute content: application/json: schema: type: object properties: message: type: string 302: description: Redirects to the current digest 401: $ref: '#/components/responses/401' 404: $ref: '#/components/responses/404' 422: $ref: '#/components/responses/422' 500: $ref: '#/components/responses/500' """ dashboard = self.datamodel.get(pk, self._base_filters) if not dashboard: return self.response_404() dashboard_url = get_url_path("Superset.dashboard", dashboard_id_or_slug=dashboard.id) # If force, request a screenshot from the workers if kwargs["rison"].get("force", False): cache_dashboard_thumbnail.delay(dashboard_url, dashboard.digest, force=True) return self.response(202, message="OK Async") # fetch the dashboard screenshot using the current user and cache if set screenshot = DashboardScreenshot( dashboard_url, dashboard.digest).get_from_cache(cache=thumbnail_cache) # If the screenshot does not exist, request one from the workers if not screenshot: self.incr_stats("async", self.thumbnail.__name__) cache_dashboard_thumbnail.delay(dashboard_url, dashboard.digest, force=True) return self.response(202, message="OK Async") # If digests if dashboard.digest != digest: self.incr_stats("redirect", self.thumbnail.__name__) return redirect( url_for( f"{self.__class__.__name__}.thumbnail", pk=pk, digest=dashboard.digest, )) self.incr_stats("from_cache", self.thumbnail.__name__) return Response(FileWrapper(screenshot), mimetype="image/png", direct_passthrough=True)
def event_after_dashboard_changed( # pylint: disable=unused-argument mapper: Mapper, connection: Connection, target: Dashboard ) -> None: url = get_url_path("Superset.dashboard", dashboard_id=target.id) cache_dashboard_thumbnail.delay(url, target.digest, force=True)
def thumbnail(self, pk: int, digest: str, **kwargs: Dict[str, bool]) -> WerkzeugResponse: """Get Chart thumbnail --- get: description: Compute or get already computed chart thumbnail from cache. parameters: - in: path schema: type: integer name: pk - in: path schema: type: string name: digest responses: 200: description: Chart thumbnail image content: image/*: schema: type: string format: binary 302: description: Redirects to the current digest 400: $ref: '#/components/responses/400' 401: $ref: '#/components/responses/401' 404: $ref: '#/components/responses/404' 500: $ref: '#/components/responses/500' """ chart = self.datamodel.get(pk, self._base_filters) if not chart: return self.response_404() url = get_url_path("Superset.slice", slice_id=chart.id, standalone="true") if kwargs["rison"].get("force", False): logger.info("Triggering thumbnail compute (chart id: %s) ASYNC", str(chart.id)) cache_chart_thumbnail.delay(url, chart.digest, force=True) 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) # If not screenshot then send request to compute thumb to celery if not screenshot: logger.info("Triggering thumbnail compute (chart id: %s) ASYNC", str(chart.id)) cache_chart_thumbnail.delay(url, chart.digest, force=True) return self.response(202, message="OK Async") # If digests if chart.digest != digest: return redirect( url_for(f"{self.__class__.__name__}.thumbnail", pk=pk, digest=chart.digest)) return Response(FileWrapper(screenshot), mimetype="image/png", direct_passthrough=True)
def event_after_dashboard_changed( _mapper: Mapper, _connection: Connection, target: Dashboard ) -> None: url = get_url_path("Superset.dashboard", dashboard_id_or_slug=target.id) cache_dashboard_thumbnail.delay(url, target.digest, force=True)
def event_after_chart_changed(_mapper: Mapper, _connection: Connection, target: Slice) -> None: url = get_url_path("Superset.slice", slice_id=target.id, standalone="true") cache_chart_thumbnail.delay(url, target.digest, force=True)
def update_thumbnail(self) -> None: url = get_url_path("Superset.dashboard", dashboard_id_or_slug=self.id) cache_dashboard_thumbnail.delay(url, self.digest, force=True)