# #region MaintenanceChartManager [C:3] [TYPE Module] [SEMANTICS maintenance, chart, banner, lifecycle] # @BRIEF Chart operations for maintenance banners: create/get banner charts, process # per-dashboard state transitions for start and end orchestrators. # @LAYER Service # @RELATION DEPENDS_ON -> [SupersetDashboardsWriteMixin] # @RELATION DEPENDS_ON -> [MaintenanceModels] from typing import Any from sqlalchemy.orm import Session from ...core.logger import belief_scope, logger as app_logger from ...core.superset_client import SupersetClient from ...models.maintenance import ( MaintenanceDashboardBanner, MaintenanceDashboardBannerStatus, MaintenanceDashboardState, MaintenanceDashboardStateStatus, ) from ._banner_renderer import _build_banner_text_for_dashboard, rebuild_banner from ._dashboard_scanner import _resolve_dashboard_title # #region ensure_banner_chart [C:3] [TYPE Function] [SEMANTICS maintenance, banner, chart, creation] # @BRIEF Get or create a MaintenanceDashboardBanner for a dashboard in the target environment. # If an active banner exists in DB, return it. Otherwise, create a markdown chart in Superset, # update dashboard layout, and store the banner row. # @PRE dashboard_id exists in Superset. superset_client has write access. # @POST Returns MaintenanceDashboardBanner with chart_id set. Chart is placed at top of dashboard. # @SIDE_EFFECT Creates chart in Superset, modifies dashboard layout, writes DB row. # @RELATION DEPENDS_ON -> [SupersetDashboardsWriteMixin] def ensure_banner_chart( dashboard_id: int, environment_id: str, superset_client: SupersetClient, db_session: Session, ) -> MaintenanceDashboardBanner: with belief_scope( "ensure_banner_chart", f"dashboard_id={dashboard_id}", ): app_logger.reason( "Ensuring banner chart exists", extra={"dashboard_id": dashboard_id}, ) # Check for existing active banner existing = ( db_session.query(MaintenanceDashboardBanner) .filter( MaintenanceDashboardBanner.dashboard_id == dashboard_id, MaintenanceDashboardBanner.environment_id == environment_id, MaintenanceDashboardBanner.status == MaintenanceDashboardBannerStatus.ACTIVE, ) .first() ) if existing: app_logger.reason( "Found existing active banner, verifying chart in Superset", extra={"banner_id": existing.id, "chart_id": existing.chart_id}, ) # Verify the chart still exists in Superset try: superset_client.get_chart(existing.chart_id) app_logger.reflect( "Existing banner chart is alive, reusing", extra={"banner_id": existing.id, "chart_id": existing.chart_id}, ) return existing except Exception: app_logger.explore( "Existing banner chart is stale (not found in Superset), marking as REMOVED", extra={ "banner_id": existing.id, "chart_id": existing.chart_id, }, ) existing.status = MaintenanceDashboardBannerStatus.REMOVED db_session.flush() # Fall through to create a new chart # Create markdown chart in Superset try: chart_id = superset_client.create_markdown_chart( dashboard_id, "*Maintenance banner placeholder*" ) except Exception as e: app_logger.explore( "Failed to create markdown chart", extra={"dashboard_id": dashboard_id}, error=str(e), ) raise # Insert chart at top of dashboard layout try: superset_client.update_dashboard_layout(dashboard_id, chart_id) except Exception as e: app_logger.explore( "Failed to update dashboard layout, deleting chart", extra={"dashboard_id": dashboard_id, "chart_id": chart_id}, error=str(e), ) # Clean up the chart we just created try: superset_client.delete_chart(chart_id) except Exception: pass raise # Persist banner row banner = MaintenanceDashboardBanner( environment_id=environment_id, dashboard_id=dashboard_id, chart_id=chart_id, banner_text="", status=MaintenanceDashboardBannerStatus.ACTIVE, ) db_session.add(banner) db_session.flush() app_logger.reflect( "Banner chart created and persisted", extra={ "banner_id": banner.id, "chart_id": chart_id, "dashboard_id": dashboard_id, }, ) return banner # #endregion ensure_banner_chart # #region _process_dashboards_for_start [C:3] [TYPE Function] # @BRIEF Process each dashboard for a start maintenance event: ensure banner chart, # create dashboard state, build banner text, update chart markdown. # @SIDE_EFFECT Creates/updates Superset charts, writes DB rows. # @RELATION CALLS -> [ensure_banner_chart] # @RELATION CALLS -> [_build_banner_text_for_dashboard] # @RELATION CALLS -> [_resolve_dashboard_title] def _process_dashboards_for_start( dashboard_ids: list[int], event_id: str, environment_id: str, template: str, timezone_str: str, superset_client: SupersetClient, db_session: Session, ) -> tuple[list[dict[str, Any]], list[dict[str, Any]], list[MaintenanceDashboardBanner]]: successful_dashboards: list[dict[str, Any]] = [] failed_dashboards: list[dict[str, Any]] = [] banners_created: list[MaintenanceDashboardBanner] = [] for dash_id in dashboard_ids: app_logger.reason( "Processing dashboard", extra={"dashboard_id": dash_id, "event_id": event_id}, ) try: # Ensure banner chart exists banner = ensure_banner_chart( dash_id, environment_id, superset_client, db_session, ) banners_created.append(banner) # Create dashboard state state = MaintenanceDashboardState( event_id=event_id, dashboard_id=dash_id, banner_id=banner.id, status=MaintenanceDashboardStateStatus.ACTIVE, ) db_session.add(state) db_session.flush() # Ensure state is visible to subsequent queries # Build banner text for this dashboard's active events banner_text = _build_banner_text_for_dashboard( banner.id, db_session, template, timezone_str ) # Update banner content on dashboard (native MARKDOWN in layout) if banner.chart_id: try: superset_client.update_banner_on_dashboard( dash_id, banner.chart_id, banner_text ) except Exception as e: app_logger.explore( "Failed to update banner on dashboard", extra={ "chart_id": banner.chart_id, "dashboard_id": dash_id, }, error=str(e), ) state.status = MaintenanceDashboardStateStatus.APPLY_FAILED failed_dashboards.append( {"id": dash_id, "title": str(dash_id), "error": str(e)} ) continue # Update banner text in DB banner.banner_text = banner_text db_session.flush() # Resolve dashboard title title = _resolve_dashboard_title(dash_id, superset_client) successful_dashboards.append( { "id": dash_id, "title": title, "chart_id": banner.chart_id, } ) app_logger.reflect( "Dashboard processed successfully", extra={"dashboard_id": dash_id}, ) except Exception as e: app_logger.explore( "Failed to process dashboard", extra={"dashboard_id": dash_id}, error=str(e), ) failed_dashboards.append( {"id": dash_id, "title": str(dash_id), "error": str(e)} ) return successful_dashboards, failed_dashboards, banners_created # #endregion _process_dashboards_for_start # #region _process_states_for_end [C:3] [TYPE Function] # @BRIEF Process each dashboard state for an end maintenance event: if other events # still use same banner → rebuild; if no other events → remove banner (chart + layout). # @SIDE_EFFECT Modifies Superset dashboards (removes charts, updates layouts). Writes DB. # @RELATION CALLS -> [rebuild_banner] def _process_states_for_end( states: list[MaintenanceDashboardState], event_id: str, superset_client: SupersetClient, db_session: Session, ) -> tuple[int, list[dict[str, Any]]]: removed_count = 0 failed_removals: list[dict[str, Any]] = [] for state in states: dash_id = state.dashboard_id app_logger.reason( "Processing dashboard state for end", extra={"dashboard_id": dash_id, "state_id": state.id}, ) try: banner_id = state.banner_id if not banner_id: # No banner linked, just mark as removed state.status = MaintenanceDashboardStateStatus.REMOVED removed_count += 1 continue # Check if other active events still use this banner other_active = db_session.query(MaintenanceDashboardState).filter( MaintenanceDashboardState.banner_id == banner_id, MaintenanceDashboardState.status == MaintenanceDashboardStateStatus.ACTIVE, MaintenanceDashboardState.event_id != event_id, ).count() banner = db_session.query(MaintenanceDashboardBanner).filter( MaintenanceDashboardBanner.id == banner_id ).first() if other_active > 0 and banner: # Other events still active — rebuild banner text state.status = MaintenanceDashboardStateStatus.REMOVED db_session.flush() rebuild_banner(banner_id, db_session, superset_client) app_logger.reflect( "Banner rebuilt after removing event", extra={"banner_id": banner_id, "dashboard_id": dash_id}, ) elif banner: # No other events — remove banner entirely state.status = MaintenanceDashboardStateStatus.REMOVING db_session.flush() # Remove chart from layout if banner.chart_id: try: superset_client.remove_chart_from_layout( dash_id, banner.chart_id ) except Exception as e: app_logger.explore( "Failed to remove chart from layout", extra={ "dashboard_id": dash_id, "chart_id": banner.chart_id, }, error=str(e), ) state.status = MaintenanceDashboardStateStatus.REMOVAL_FAILED failed_removals.append( {"id": dash_id, "title": str(dash_id), "error": str(e)} ) continue # Delete chart try: superset_client.delete_chart(banner.chart_id) except Exception as e: app_logger.explore( "Failed to delete chart", extra={ "chart_id": banner.chart_id, "dashboard_id": dash_id, }, error=str(e), ) state.status = MaintenanceDashboardStateStatus.REMOVAL_FAILED failed_removals.append( {"id": dash_id, "title": str(dash_id), "error": str(e)} ) continue # Mark banner as removed banner.status = MaintenanceDashboardBannerStatus.REMOVED state.status = MaintenanceDashboardStateStatus.REMOVED app_logger.reflect( "Banner fully removed", extra={"banner_id": banner_id, "dashboard_id": dash_id}, ) else: state.status = MaintenanceDashboardStateStatus.REMOVED removed_count += 1 except Exception as e: app_logger.explore( "Failed to end maintenance for dashboard", extra={"dashboard_id": dash_id}, error=str(e), ) state.status = MaintenanceDashboardStateStatus.REMOVAL_FAILED failed_removals.append( {"id": dash_id, "title": str(dash_id), "error": str(e)} ) return removed_count, failed_removals # #endregion _process_states_for_end # #endregion MaintenanceChartManager