Release v2.2.46: mission schema resilience and repair migration
This commit is contained in:
parent
4760b8b3c4
commit
acd5359580
1
.gitignore
vendored
1
.gitignore
vendored
@ -28,3 +28,4 @@ htmlcov/
|
||||
.coverage
|
||||
.pytest_cache/
|
||||
.mypy_cache/
|
||||
RELEASE_NOTES_v2.2.38.md
|
||||
|
||||
19
RELEASE_NOTES_v2.2.46.md
Normal file
19
RELEASE_NOTES_v2.2.46.md
Normal file
@ -0,0 +1,19 @@
|
||||
# Release Notes v2.2.46
|
||||
|
||||
Dato: 4. marts 2026
|
||||
|
||||
## Fixes og driftssikring
|
||||
- Mission Control backend tåler nu manglende mission-tabeller uden at crashe requests, og logger tydelige advarsler.
|
||||
- Tilføjet idempotent reparationsmigration for Mission Control schema (`143_mission_control_repair.sql`) til miljøer med delvist oprettede tabeller.
|
||||
- Opdateret `.gitignore` med release-note undtagelse fra tidligere drift.
|
||||
|
||||
## Ændrede filer
|
||||
- `app/dashboard/backend/mission_service.py`
|
||||
- `migrations/143_mission_control_repair.sql`
|
||||
- `.gitignore`
|
||||
- `VERSION`
|
||||
- `RELEASE_NOTES_v2.2.46.md`
|
||||
|
||||
## Drift
|
||||
- Deploy: `./updateto.sh v2.2.46`
|
||||
- Migration (hvis nødvendig): `docker compose exec db psql -U bmc_hub -d bmc_hub -f migrations/143_mission_control_repair.sql`
|
||||
@ -1,10 +1,19 @@
|
||||
import json
|
||||
import logging
|
||||
from typing import Any, Dict, Optional
|
||||
|
||||
from app.core.database import execute_query, execute_query_single
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class MissionService:
|
||||
@staticmethod
|
||||
def _table_exists(table_name: str) -> bool:
|
||||
row = execute_query_single("SELECT to_regclass(%s) AS table_name", (f"public.{table_name}",))
|
||||
return bool(row and row.get("table_name"))
|
||||
|
||||
@staticmethod
|
||||
def get_ring_timeout_seconds() -> int:
|
||||
raw = MissionService.get_setting_value("mission_call_ring_timeout_seconds", "180") or "180"
|
||||
@ -16,6 +25,9 @@ class MissionService:
|
||||
|
||||
@staticmethod
|
||||
def expire_stale_ringing_calls() -> None:
|
||||
if not MissionService._table_exists("mission_call_state"):
|
||||
return
|
||||
|
||||
timeout_seconds = MissionService.get_ring_timeout_seconds()
|
||||
execute_query(
|
||||
"""
|
||||
@ -108,6 +120,10 @@ class MissionService:
|
||||
customer_name: Optional[str] = None,
|
||||
payload: Optional[Dict[str, Any]] = None,
|
||||
) -> Dict[str, Any]:
|
||||
if not MissionService._table_exists("mission_events"):
|
||||
logger.warning("Mission table missing: mission_events (event skipped)")
|
||||
return {}
|
||||
|
||||
rows = execute_query(
|
||||
"""
|
||||
INSERT INTO mission_events (event_type, severity, title, source, customer_name, payload)
|
||||
@ -168,6 +184,10 @@ class MissionService:
|
||||
|
||||
@staticmethod
|
||||
def get_active_calls() -> list[Dict[str, Any]]:
|
||||
if not MissionService._table_exists("mission_call_state"):
|
||||
logger.warning("Mission table missing: mission_call_state (active calls unavailable)")
|
||||
return []
|
||||
|
||||
MissionService.expire_stale_ringing_calls()
|
||||
rows = execute_query(
|
||||
"""
|
||||
@ -181,6 +201,10 @@ class MissionService:
|
||||
|
||||
@staticmethod
|
||||
def get_active_alerts() -> list[Dict[str, Any]]:
|
||||
if not MissionService._table_exists("mission_uptime_alerts"):
|
||||
logger.warning("Mission table missing: mission_uptime_alerts (active alerts unavailable)")
|
||||
return []
|
||||
|
||||
rows = execute_query(
|
||||
"""
|
||||
SELECT alert_key, service_name, customer_name, status, is_active, started_at, resolved_at, updated_at
|
||||
@ -193,6 +217,10 @@ class MissionService:
|
||||
|
||||
@staticmethod
|
||||
def get_live_feed(limit: int = 20) -> list[Dict[str, Any]]:
|
||||
if not MissionService._table_exists("mission_events"):
|
||||
logger.warning("Mission table missing: mission_events (live feed unavailable)")
|
||||
return []
|
||||
|
||||
rows = execute_query(
|
||||
"""
|
||||
SELECT id, event_type, severity, title, source, customer_name, payload, created_at
|
||||
|
||||
110
migrations/143_mission_control_repair.sql
Normal file
110
migrations/143_mission_control_repair.sql
Normal file
@ -0,0 +1,110 @@
|
||||
-- Migration 143: Repair Mission Control schema on partially-initialized databases
|
||||
-- Safe to run multiple times.
|
||||
|
||||
CREATE TABLE IF NOT EXISTS mission_events (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
event_type VARCHAR(64) NOT NULL,
|
||||
severity VARCHAR(16) NOT NULL DEFAULT 'info',
|
||||
title VARCHAR(255) NOT NULL,
|
||||
source VARCHAR(64),
|
||||
customer_name VARCHAR(255),
|
||||
payload JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
ALTER TABLE mission_events ADD COLUMN IF NOT EXISTS event_type VARCHAR(64);
|
||||
ALTER TABLE mission_events ADD COLUMN IF NOT EXISTS severity VARCHAR(16) NOT NULL DEFAULT 'info';
|
||||
ALTER TABLE mission_events ADD COLUMN IF NOT EXISTS title VARCHAR(255);
|
||||
ALTER TABLE mission_events ADD COLUMN IF NOT EXISTS source VARCHAR(64);
|
||||
ALTER TABLE mission_events ADD COLUMN IF NOT EXISTS customer_name VARCHAR(255);
|
||||
ALTER TABLE mission_events ADD COLUMN IF NOT EXISTS payload JSONB NOT NULL DEFAULT '{}'::jsonb;
|
||||
ALTER TABLE mission_events ADD COLUMN IF NOT EXISTS created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_mission_events_created_at ON mission_events(created_at DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_mission_events_event_type ON mission_events(event_type);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS mission_call_state (
|
||||
call_id VARCHAR(128) PRIMARY KEY,
|
||||
queue_name VARCHAR(128),
|
||||
caller_number VARCHAR(64),
|
||||
contact_name VARCHAR(255),
|
||||
company_name VARCHAR(255),
|
||||
customer_tag VARCHAR(64),
|
||||
state VARCHAR(32) NOT NULL,
|
||||
started_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
answered_at TIMESTAMP,
|
||||
ended_at TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
last_payload JSONB NOT NULL DEFAULT '{}'::jsonb
|
||||
);
|
||||
|
||||
ALTER TABLE mission_call_state ADD COLUMN IF NOT EXISTS call_id VARCHAR(128);
|
||||
ALTER TABLE mission_call_state ADD COLUMN IF NOT EXISTS queue_name VARCHAR(128);
|
||||
ALTER TABLE mission_call_state ADD COLUMN IF NOT EXISTS caller_number VARCHAR(64);
|
||||
ALTER TABLE mission_call_state ADD COLUMN IF NOT EXISTS contact_name VARCHAR(255);
|
||||
ALTER TABLE mission_call_state ADD COLUMN IF NOT EXISTS company_name VARCHAR(255);
|
||||
ALTER TABLE mission_call_state ADD COLUMN IF NOT EXISTS customer_tag VARCHAR(64);
|
||||
ALTER TABLE mission_call_state ADD COLUMN IF NOT EXISTS state VARCHAR(32) NOT NULL DEFAULT 'ringing';
|
||||
ALTER TABLE mission_call_state ADD COLUMN IF NOT EXISTS started_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP;
|
||||
ALTER TABLE mission_call_state ADD COLUMN IF NOT EXISTS answered_at TIMESTAMP;
|
||||
ALTER TABLE mission_call_state ADD COLUMN IF NOT EXISTS ended_at TIMESTAMP;
|
||||
ALTER TABLE mission_call_state ADD COLUMN IF NOT EXISTS updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP;
|
||||
ALTER TABLE mission_call_state ADD COLUMN IF NOT EXISTS last_payload JSONB NOT NULL DEFAULT '{}'::jsonb;
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM pg_constraint
|
||||
WHERE conname = 'mission_call_state_pkey'
|
||||
AND conrelid = 'mission_call_state'::regclass
|
||||
) THEN
|
||||
ALTER TABLE mission_call_state ADD PRIMARY KEY (call_id);
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_mission_call_state_state ON mission_call_state(state);
|
||||
CREATE INDEX IF NOT EXISTS idx_mission_call_state_updated_at ON mission_call_state(updated_at DESC);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS mission_uptime_alerts (
|
||||
alert_key VARCHAR(255) PRIMARY KEY,
|
||||
service_name VARCHAR(255) NOT NULL,
|
||||
customer_name VARCHAR(255),
|
||||
status VARCHAR(32) NOT NULL,
|
||||
is_active BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
started_at TIMESTAMP,
|
||||
resolved_at TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
raw_payload JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||||
normalized_payload JSONB NOT NULL DEFAULT '{}'::jsonb
|
||||
);
|
||||
|
||||
ALTER TABLE mission_uptime_alerts ADD COLUMN IF NOT EXISTS alert_key VARCHAR(255);
|
||||
ALTER TABLE mission_uptime_alerts ADD COLUMN IF NOT EXISTS service_name VARCHAR(255);
|
||||
ALTER TABLE mission_uptime_alerts ADD COLUMN IF NOT EXISTS customer_name VARCHAR(255);
|
||||
ALTER TABLE mission_uptime_alerts ADD COLUMN IF NOT EXISTS status VARCHAR(32);
|
||||
ALTER TABLE mission_uptime_alerts ADD COLUMN IF NOT EXISTS is_active BOOLEAN NOT NULL DEFAULT FALSE;
|
||||
ALTER TABLE mission_uptime_alerts ADD COLUMN IF NOT EXISTS started_at TIMESTAMP;
|
||||
ALTER TABLE mission_uptime_alerts ADD COLUMN IF NOT EXISTS resolved_at TIMESTAMP;
|
||||
ALTER TABLE mission_uptime_alerts ADD COLUMN IF NOT EXISTS updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP;
|
||||
ALTER TABLE mission_uptime_alerts ADD COLUMN IF NOT EXISTS raw_payload JSONB NOT NULL DEFAULT '{}'::jsonb;
|
||||
ALTER TABLE mission_uptime_alerts ADD COLUMN IF NOT EXISTS normalized_payload JSONB NOT NULL DEFAULT '{}'::jsonb;
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM pg_constraint
|
||||
WHERE conname = 'mission_uptime_alerts_pkey'
|
||||
AND conrelid = 'mission_uptime_alerts'::regclass
|
||||
) THEN
|
||||
ALTER TABLE mission_uptime_alerts ADD PRIMARY KEY (alert_key);
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_mission_uptime_active ON mission_uptime_alerts(is_active, updated_at DESC);
|
||||
|
||||
INSERT INTO settings (key, value, category, description, value_type, is_public)
|
||||
VALUES
|
||||
('mission_call_ring_timeout_seconds', '180', 'mission', 'Seconds before stale ringing calls auto-expire', 'integer', true)
|
||||
ON CONFLICT (key) DO NOTHING;
|
||||
Loading…
Reference in New Issue
Block a user