From e6f06b077b5cf6a508954f2dd83010fda4261c98 Mon Sep 17 00:00:00 2001 From: pritishpai Date: Fri, 11 Jul 2025 10:39:11 -0400 Subject: [PATCH 1/2] Handle PermisssionDenied exception so that a new dashboard is created. --- src/databricks/labs/ucx/install.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/databricks/labs/ucx/install.py b/src/databricks/labs/ucx/install.py index 5255acb86a..a8fd14dffc 100644 --- a/src/databricks/labs/ucx/install.py +++ b/src/databricks/labs/ucx/install.py @@ -642,6 +642,10 @@ def _handle_existing_dashboard(self, dashboard_id: str, display_name: str, paren except NotFound: pass return None # Recreate the dashboard if it's reference is corrupted (manually) + except PermissionDenied: + logger.warning(f"Cannot access dashboard {display_name} ({dashboard_id}), permission denied") + pass + return None # Create a new dashboard if permission is denied. return dashboard_id # Update the existing dashboard # InternalError and DeadlineExceeded are retried because of Lakeview internal issues From d59ba21f66bda838a029759e0a49bcb0d4452453 Mon Sep 17 00:00:00 2001 From: pritishpai Date: Fri, 11 Jul 2025 11:07:36 -0400 Subject: [PATCH 2/2] Reorganized to reduce McCabe complexity and improve readability --- src/databricks/labs/ucx/install.py | 57 ++++++++++++++++++------------ 1 file changed, 35 insertions(+), 22 deletions(-) diff --git a/src/databricks/labs/ucx/install.py b/src/databricks/labs/ucx/install.py index a8fd14dffc..ced120fdde 100644 --- a/src/databricks/labs/ucx/install.py +++ b/src/databricks/labs/ucx/install.py @@ -619,35 +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") - pass - return None # Create a new dashboard if permission is denied. + 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