Skip to content

Handle PermisssionDenied exception so that a new dashboard is created. #4209

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

Merged
merged 2 commits into from
Jul 11, 2025
Merged
Changes from all 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
55 changes: 36 additions & 19 deletions src/databricks/labs/ucx/install.py
Original file line number Diff line number Diff line change
Expand Up @@ -619,31 +619,48 @@ def _handle_existing_dashboard(self, dashboard_id: str, display_name: str, paren
str | None :
The dashboard id. If None, the dashboard will be recreated.
"""
if "-" in dashboard_id:
logger.info(f"Upgrading dashboard to Lakeview: {display_name} ({dashboard_id})")
try:
self._ws.dashboards.delete(dashboard_id=dashboard_id)
except BadRequest:
logger.warning(f"Cannot delete dashboard: {display_name} ({dashboard_id})")
return None # Recreate the dashboard if upgrading from Redash
if self._is_redash_dashboard(dashboard_id):
self._upgrade_redash_dashboard(dashboard_id, display_name)
try:
dashboard = self._ws.lakeview.get(dashboard_id)
if dashboard.lifecycle_state is None:
raise NotFound(f"Dashboard life cycle state: {display_name} ({dashboard_id})")
if dashboard.lifecycle_state == LifecycleState.TRASHED:
logger.info(f"Recreating trashed dashboard: {display_name} ({dashboard_id})")
if self._is_trashed_dashboard(dashboard, display_name, dashboard_id):
return None # Recreate the dashboard if it is trashed (manually)
except (NotFound, InvalidParameterValue):
logger.info(f"Recovering invalid dashboard: {display_name} ({dashboard_id})")
try:
dashboard_path = f"{parent_path}/{display_name}.lvdash.json"
self._ws.workspace.delete(dashboard_path) # Cannot recreate dashboard if file still exists
logger.debug(f"Deleted dangling dashboard {display_name} ({dashboard_id}): {dashboard_path}")
except NotFound:
pass
return None # Recreate the dashboard if it's reference is corrupted (manually)
self._recover_invalid_dashboard(dashboard_id, display_name, parent_path)
return None
except PermissionDenied:
logger.warning(f"Cannot access dashboard {display_name} ({dashboard_id}), permission denied")
return None # Create a new dashboard if permission is denied.
return dashboard_id # Update the existing dashboard

def _is_redash_dashboard(self, dashboard_id: str) -> bool:
"""Check if the dashboard is a Redash dashboard"""
return "-" in dashboard_id

def _upgrade_redash_dashboard(self, dashboard_id: str, display_name: str) -> None:
logger.info(f"Upgrading dashboard to Lakeview: {display_name} ({dashboard_id})")
try:
self._ws.dashboards.delete(dashboard_id=dashboard_id)
except BadRequest:
logger.warning(f"Cannot delete dashboard: {display_name} ({dashboard_id})")

def _is_trashed_dashboard(self, dashboard, display_name: str, dashboard_id: str) -> bool:
if dashboard.lifecycle_state is None:
raise NotFound(f"Dashboard life cycle state: {display_name} ({dashboard_id})")
if dashboard.lifecycle_state == LifecycleState.TRASHED:
logger.info(f"Recreating trashed dashboard: {display_name} ({dashboard_id})")
return True
return False

def _recover_invalid_dashboard(self, dashboard_id: str, display_name: str, parent_path: str) -> None:
logger.info(f"Recovering invalid dashboard: {display_name} ({dashboard_id})")
try:
dashboard_path = f"{parent_path}/{display_name}.lvdash.json"
self._ws.workspace.delete(dashboard_path) # Cannot recreate dashboard if file still exists
logger.debug(f"Deleted dangling dashboard {display_name} ({dashboard_id}): {dashboard_path}")
except NotFound:
pass

# InternalError and DeadlineExceeded are retried because of Lakeview internal issues
# These issues have been reported to and are resolved by the Lakeview team
# Keeping the retry for resilience
Expand Down