Skip to content

Commit

Permalink
feat: allow exporting all tabs to a single PDF in report (apache#30694)
Browse files Browse the repository at this point in the history
  • Loading branch information
US579 authored Nov 4, 2024
1 parent b02d18a commit 29e3f4b
Show file tree
Hide file tree
Showing 6 changed files with 995 additions and 26 deletions.
25 changes: 23 additions & 2 deletions superset-frontend/src/features/alerts/AlertReportModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -823,10 +823,31 @@ const AlertReportModal: FunctionComponent<AlertReportModalProps> = ({
})
.then(response => {
const { tab_tree: tabTree, all_tabs: allTabs } = response.json.result;
tabTree.push({
title: 'All Tabs',
// select tree only works with string value
value: JSON.stringify(Object.keys(allTabs)),
});
setTabOptions(tabTree);

const anchor = currentAlert?.extra?.dashboard?.anchor;
if (anchor && !(anchor in allTabs)) {
updateAnchorState(undefined);
if (anchor) {
try {
const parsedAnchor = JSON.parse(anchor);
if (Array.isArray(parsedAnchor)) {
// Check if all elements in parsedAnchor list are in allTabs
const isValidSubset = parsedAnchor.every(tab => tab in allTabs);
if (!isValidSubset) {
updateAnchorState(undefined);
}
} else {
throw new Error('Parsed value is not an array');
}
} catch (error) {
if (!(anchor in allTabs)) {
updateAnchorState(undefined);
}
}
}
})
.catch(() => {
Expand Down
13 changes: 10 additions & 3 deletions superset/commands/report/create.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,10 +143,17 @@ def _validate_report_extra(self, exceptions: list[ValidationError]) -> None:

position_data = json.loads(dashboard.position_json or "{}")
active_tabs = dashboard_state.get("activeTabs") or []
anchor = dashboard_state.get("anchor")
invalid_tab_ids = set(active_tabs) - set(position_data.keys())
if anchor and anchor not in position_data:
invalid_tab_ids.add(anchor)

if anchor := dashboard_state.get("anchor"):
try:
anchor_list: list[str] = json.loads(anchor)
if _invalid_tab_ids := set(anchor_list) - set(position_data.keys()):
invalid_tab_ids.update(_invalid_tab_ids)
except json.JSONDecodeError:
if anchor not in position_data:
invalid_tab_ids.add(anchor)

if invalid_tab_ids:
exceptions.append(
ValidationError(
Expand Down
108 changes: 87 additions & 21 deletions superset/commands/report/execute.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
REPORT_SCHEDULE_ERROR_NOTIFICATION_MARKER,
ReportScheduleDAO,
)
from superset.dashboards.permalink.types import DashboardPermalinkState
from superset.errors import ErrorLevel, SupersetError, SupersetErrorType
from superset.exceptions import SupersetErrorsException, SupersetException
from superset.extensions import feature_flag_manager, machine_auth_provider_factory
Expand Down Expand Up @@ -206,11 +207,8 @@ def _get_url(
if (
dashboard_state := self._report_schedule.extra.get("dashboard")
) and feature_flag_manager.is_feature_enabled("ALERT_REPORT_TABS"):
permalink_key = CreateDashboardPermalinkCommand(
dashboard_id=str(self._report_schedule.dashboard.uuid),
state=dashboard_state,
).run()
return get_url_path("Superset.dashboard_permalink", key=permalink_key)
return self._get_tab_url(dashboard_state)

dashboard = self._report_schedule.dashboard
dashboard_id_or_slug = (
dashboard.uuid if dashboard and dashboard.uuid else dashboard.id
Expand All @@ -223,54 +221,122 @@ def _get_url(
**kwargs,
)

def get_dashboard_urls(
self, user_friendly: bool = False, **kwargs: Any
) -> list[str]:
"""
Retrieve the URL for the dashboard tabs, or return the dashboard URL if no tabs are available.
"""
force = "true" if self._report_schedule.force_screenshot else "false"
if (
dashboard_state := self._report_schedule.extra.get("dashboard")
) and feature_flag_manager.is_feature_enabled("ALERT_REPORT_TABS"):
if anchor := dashboard_state.get("anchor"):
try:
anchor_list: list[str] = json.loads(anchor)
return self._get_tabs_urls(anchor_list)
except json.JSONDecodeError:
logger.debug("Anchor value is not a list, Fall back to single tab")
return [self._get_tab_url(dashboard_state)]

dashboard = self._report_schedule.dashboard
dashboard_id_or_slug = (
dashboard.uuid if dashboard and dashboard.uuid else dashboard.id
)

return [
get_url_path(
"Superset.dashboard",
user_friendly=user_friendly,
dashboard_id_or_slug=dashboard_id_or_slug,
force=force,
**kwargs,
)
]

def _get_tab_url(self, dashboard_state: DashboardPermalinkState) -> str:
"""
Get one tab url
"""
permalink_key = CreateDashboardPermalinkCommand(
dashboard_id=str(self._report_schedule.dashboard.uuid),
state=dashboard_state,
).run()
return get_url_path("Superset.dashboard_permalink", key=permalink_key)

def _get_tabs_urls(self, tab_anchors: list[str]) -> list[str]:
"""
Get multple tabs urls
"""
return [
self._get_tab_url(
{
"anchor": tab_anchor,
"dataMask": None,
"activeTabs": None,
"urlParams": None,
}
)
for tab_anchor in tab_anchors
]

def _get_screenshots(self) -> list[bytes]:
"""
Get chart or dashboard screenshots
:raises: ReportScheduleScreenshotFailedError
"""
url = self._get_url()
_, username = get_executor(
executor_types=app.config["ALERT_REPORTS_EXECUTE_AS"],
model=self._report_schedule,
)
user = security_manager.find_user(username)

if self._report_schedule.chart:
url = self._get_url()
window_width, window_height = app.config["WEBDRIVER_WINDOW"]["slice"]
window_size = (
self._report_schedule.custom_width or window_width,
self._report_schedule.custom_height or window_height,
)
screenshot: Union[ChartScreenshot, DashboardScreenshot] = ChartScreenshot(
url,
self._report_schedule.chart.digest,
window_size=window_size,
thumb_size=app.config["WEBDRIVER_WINDOW"]["slice"],
)
screenshots: list[Union[ChartScreenshot, DashboardScreenshot]] = [
ChartScreenshot(
url,
self._report_schedule.chart.digest,
window_size=window_size,
thumb_size=app.config["WEBDRIVER_WINDOW"]["slice"],
)
]
else:
urls = self.get_dashboard_urls()
window_width, window_height = app.config["WEBDRIVER_WINDOW"]["dashboard"]
window_size = (
self._report_schedule.custom_width or window_width,
self._report_schedule.custom_height or window_height,
)
screenshot = DashboardScreenshot(
url,
self._report_schedule.dashboard.digest,
window_size=window_size,
thumb_size=app.config["WEBDRIVER_WINDOW"]["dashboard"],
)
screenshots = [
DashboardScreenshot(
url,
self._report_schedule.dashboard.digest,
window_size=window_size,
thumb_size=app.config["WEBDRIVER_WINDOW"]["dashboard"],
)
for url in urls
]
try:
image = screenshot.get_screenshot(user=user)
imges = []
for screenshot in screenshots:
if imge := screenshot.get_screenshot(user=user):
imges.append(imge)
except SoftTimeLimitExceeded as ex:
logger.warning("A timeout occurred while taking a screenshot.")
raise ReportScheduleScreenshotTimeout() from ex
except Exception as ex:
raise ReportScheduleScreenshotFailedError(
f"Failed taking a screenshot {str(ex)}"
) from ex
if not image:
if not imges:
raise ReportScheduleScreenshotFailedError()
return [image]
return imges

def _get_pdf(self) -> bytes:
"""
Expand Down
Loading

0 comments on commit 29e3f4b

Please sign in to comment.