- Guard maintenance Alembic operations for create_all-only tables on clean DBs - Add guarded verification_runs.fanout_plan_id backfill migration - Improve maintenance banner rendering, chart management, orchestration, and API routes - Expand assistant maintenance tool and edge-case coverage Tests: cd backend && source .venv/bin/activate && python -m pytest -q tests/test_maintenance_api.py tests/test_maintenance_service.py tests/api/test_assistant_tool_maintenance.py tests/api/test_maintenance_routes_edge.py (77 passed)
74 lines
3.4 KiB
Python
74 lines
3.4 KiB
Python
# #region Alembic.MaintenanceSettingsAndBannerSnapshot [C:3] [TYPE Module] [SEMANTICS alembic,maintenance,banner-height,timezone,snapshot]
|
|
# @ingroup Alembic
|
|
# @BRIEF Add maintenance_settings.banner_height and maintenance_dashboard_banners.original_position_json;
|
|
# migrate default timezone 'UTC' -> 'Europe/Moscow' for the untouched default settings row.
|
|
# @LAYER Database
|
|
# @RELATION DEPENDS_ON -> [Models.Maintenance.MaintenanceModels]
|
|
# @INVARIANT banner_height is nullable — NULL/0 means auto-estimate (unchanged behavior).
|
|
# @INVARIANT original_position_json is nullable — legacy banners (pre-release) have no snapshot
|
|
# and keep the surgical removal path.
|
|
# @INVARIANT The timezone UPDATE touches only rows still equal to 'UTC' — a deliberate user choice
|
|
# of 'UTC' is indistinguishable from the old default and therefore preserved as-is;
|
|
# rows already customized to another zone are never overwritten.
|
|
# @RATIONALE The banner is removed by restoring the pre-mutation position_json verbatim instead of
|
|
# rebuilding the layout structure, which broke dashboards (y-shift drift, unreverted
|
|
# ROOT->TABS -> ROOT->GRID normalization). The snapshot column stores that JSON.
|
|
# @REJECTED Restoring only the banner keys (surgical removal) was rejected as the primary path —
|
|
# it cannot revert structural normalization and drifts y-coordinates. Dropping the
|
|
# snapshot after restore was rejected — keeping it is harmless and aids debugging.
|
|
|
|
"""add banner_height + original_position_json, migrate timezone default
|
|
|
|
Revision ID: k5l6m7n8o9p0
|
|
Revises: j1k2l3m4n5o6
|
|
Create Date: 2026-08-04 10:30:00.000000
|
|
"""
|
|
|
|
from collections.abc import Sequence
|
|
|
|
import sqlalchemy as sa
|
|
from sqlalchemy import inspect
|
|
|
|
from alembic import op
|
|
|
|
# revision identifiers, used by Alembic.
|
|
revision: str = "k5l6m7n8o9p0"
|
|
down_revision: str | Sequence[str] | None = "j1k2l3m4n5o6"
|
|
branch_labels: str | Sequence[str] | None = None
|
|
depends_on: str | Sequence[str] | None = None
|
|
|
|
|
|
def _table_exists(table: str) -> bool:
|
|
"""Check if a table exists in the current database connection."""
|
|
return inspect(op.get_bind()).has_table(table)
|
|
|
|
|
|
def upgrade() -> None:
|
|
"""Add the two columns and migrate the untouched timezone default."""
|
|
# maintenance_settings / maintenance_dashboard_banners are create_all()-only tables on a
|
|
# fresh install (no base migration creates them); guard so clean-DB upgrade is a no-op and
|
|
# the ORM schema initialization creates them with the final shape.
|
|
if _table_exists("maintenance_settings"):
|
|
op.add_column(
|
|
"maintenance_settings",
|
|
sa.Column("banner_height", sa.Integer(), nullable=True),
|
|
)
|
|
op.execute(
|
|
"UPDATE maintenance_settings SET display_timezone = 'Europe/Moscow' "
|
|
"WHERE id = 'default' AND display_timezone = 'UTC'"
|
|
)
|
|
if _table_exists("maintenance_dashboard_banners"):
|
|
op.add_column(
|
|
"maintenance_dashboard_banners",
|
|
sa.Column("original_position_json", sa.Text(), nullable=True),
|
|
)
|
|
|
|
|
|
def downgrade() -> None:
|
|
"""Drop the two columns (timezone values are left as-is)."""
|
|
if _table_exists("maintenance_dashboard_banners"):
|
|
op.drop_column("maintenance_dashboard_banners", "original_position_json")
|
|
if _table_exists("maintenance_settings"):
|
|
op.drop_column("maintenance_settings", "banner_height")
|
|
# #endregion Alembic.MaintenanceSettingsAndBannerSnapshot
|