test(observability): add UI regression guard
Some checks failed
CD Pipeline / deploy (push) Failing after 1m39s
Some checks failed
CD Pipeline / deploy (push) Failing after 1m39s
This commit is contained in:
@@ -36,6 +36,24 @@ AI 觀測台是「AI 中樞控制室」,不是 Bootstrap 報表頁。任何新
|
||||
|
||||
## 驗收指令
|
||||
|
||||
### 1. Repo 靜態 UI guard
|
||||
|
||||
先跑本地 guard,避免把已知 UI/UX 回歸重新帶進 repo:
|
||||
|
||||
```bash
|
||||
python3 scripts/check_observability_ui.py
|
||||
```
|
||||
|
||||
Guard 會檢查:
|
||||
|
||||
- Times / Georgia 等非規範標題字型。
|
||||
- SQL/Jinja exception 文案外露。
|
||||
- 圖表 `style="height:..."` 與靜態 `style="width:..."`。
|
||||
- 過大標題 clamp 與純白 hover/card surface。
|
||||
- 觀測台 CSS 必要 token / utility class 是否仍存在。
|
||||
|
||||
### 2. Production 10 頁 HTTP 巡檢
|
||||
|
||||
```bash
|
||||
python3 - <<'PY'
|
||||
import urllib.request
|
||||
|
||||
151
scripts/check_observability_ui.py
Normal file
151
scripts/check_observability_ui.py
Normal file
@@ -0,0 +1,151 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Guardrail checks for AI observability UI templates.
|
||||
|
||||
This script is intentionally lightweight: it scans the observability templates
|
||||
and shared CSS for regression signals that previously hurt the war-room UI,
|
||||
including unreadable typography, raw SQL errors, oversized titles, and inline
|
||||
chart sizing.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
import sys
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
|
||||
TEMPLATE_PATHS = [
|
||||
"templates/admin/observability_overview.html",
|
||||
"templates/admin/agent_orchestration.html",
|
||||
"templates/admin/business_intel.html",
|
||||
"templates/admin/host_health.html",
|
||||
"templates/admin/ai_calls_dashboard.html",
|
||||
"templates/admin/budget.html",
|
||||
"templates/admin/promotion_review.html",
|
||||
"templates/admin/rag_queries.html",
|
||||
"templates/admin/quality_trend.html",
|
||||
"templates/admin/ppt_audit_history.html",
|
||||
]
|
||||
|
||||
CSS_PATH = Path("static/css/observability-system.css")
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Rule:
|
||||
code: str
|
||||
pattern: re.Pattern[str]
|
||||
message: str
|
||||
|
||||
|
||||
TEMPLATE_RULES = [
|
||||
Rule(
|
||||
"legacy_serif_font",
|
||||
re.compile(r"\b(Georgia|Times New Roman)\b", re.IGNORECASE),
|
||||
"觀測台標題不得回退到 Times/Georgia;使用 Noto Sans TC / Inter 與 CSS token。",
|
||||
),
|
||||
Rule(
|
||||
"raw_error_copy",
|
||||
re.compile(
|
||||
r"(查詢失敗:|ProgrammingError|UndefinedError|Traceback|Internal Server Error|relation\s+"|relation\s+\")"
|
||||
),
|
||||
"不得把 SQL/Jinja exception 或 raw failure 文案直接顯示給使用者。",
|
||||
),
|
||||
Rule(
|
||||
"inline_height",
|
||||
re.compile(r"style=\"[^\"]*height\s*:", re.IGNORECASE),
|
||||
"圖表/區塊高度請使用 .obs-chart-frame 系列或 CSS class,不要 inline height。",
|
||||
),
|
||||
Rule(
|
||||
"static_inline_width",
|
||||
re.compile(r"style=\"(?=[^\"]*width\s*:)(?![^\"]*\{\{)[^\"]*\"", re.IGNORECASE),
|
||||
"靜態寬度請移到 CSS class;僅允許 progress 類動態 width={{ ... }}。",
|
||||
),
|
||||
Rule(
|
||||
"oversized_title",
|
||||
re.compile(r"font-size:\s*clamp\([^;]*(3\.8rem|4\.45rem)", re.IGNORECASE),
|
||||
"頁面大標不得使用過大 clamp;使用 --obs-title-size token。",
|
||||
),
|
||||
Rule(
|
||||
"pure_white_surface",
|
||||
re.compile(r"background\s*:\s*#fff\s*;", re.IGNORECASE),
|
||||
"觀測台 hover/card surface 不用純白;使用暖色 paper/surface token。",
|
||||
),
|
||||
Rule(
|
||||
"wrong_business_active_page",
|
||||
re.compile(r"active_page\s*=\s*['\"]obs_business['\"]"),
|
||||
"商業戰情頁 active_page 必須是 obs_business_intel,否則觀測台 CSS 不會載入。",
|
||||
),
|
||||
]
|
||||
|
||||
REQUIRED_CSS_SNIPPETS = [
|
||||
"--obs-title-size",
|
||||
"--obs-value-size",
|
||||
".momo-observability-mode",
|
||||
".obs-chart-frame",
|
||||
".obs-chart-frame-tall",
|
||||
".obs-chart-frame-slim",
|
||||
".obs-modal-preview",
|
||||
".obs-progress-xs",
|
||||
".obs-table-shell",
|
||||
]
|
||||
|
||||
|
||||
def line_number(text: str, index: int) -> int:
|
||||
return text.count("\n", 0, index) + 1
|
||||
|
||||
|
||||
def scan_file(relative_path: Path) -> list[str]:
|
||||
path = ROOT / relative_path
|
||||
if not path.exists():
|
||||
return [f"{relative_path}: missing required observability file"]
|
||||
|
||||
text = path.read_text(encoding="utf-8")
|
||||
findings: list[str] = []
|
||||
for rule in TEMPLATE_RULES:
|
||||
for match in rule.pattern.finditer(text):
|
||||
line = line_number(text, match.start())
|
||||
snippet = match.group(0).replace("\n", " ")[:90]
|
||||
findings.append(
|
||||
f"{relative_path}:{line}: [{rule.code}] {rule.message} ({snippet})"
|
||||
)
|
||||
|
||||
return findings
|
||||
|
||||
|
||||
def scan_css() -> list[str]:
|
||||
path = ROOT / CSS_PATH
|
||||
if not path.exists():
|
||||
return [f"{CSS_PATH}: missing required observability CSS"]
|
||||
|
||||
text = path.read_text(encoding="utf-8")
|
||||
findings: list[str] = []
|
||||
for snippet in REQUIRED_CSS_SNIPPETS:
|
||||
if snippet not in text:
|
||||
findings.append(f"{CSS_PATH}: missing required token/class `{snippet}`")
|
||||
|
||||
return findings
|
||||
|
||||
|
||||
def main() -> int:
|
||||
findings: list[str] = []
|
||||
for template_path in TEMPLATE_PATHS:
|
||||
findings.extend(scan_file(Path(template_path)))
|
||||
findings.extend(scan_css())
|
||||
|
||||
if findings:
|
||||
print("Observability UI guard: FAIL")
|
||||
for finding in findings:
|
||||
print(f"- {finding}")
|
||||
return 1
|
||||
|
||||
print("Observability UI guard: PASS")
|
||||
print(f"- templates checked: {len(TEMPLATE_PATHS)}")
|
||||
print(f"- css guardrails checked: {len(REQUIRED_CSS_SNIPPETS)}")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -259,6 +259,7 @@
|
||||
.momo-observability-mode .rag-panel,
|
||||
.momo-observability-mode .quality-panel,
|
||||
.momo-observability-mode .ppt-panel,
|
||||
.momo-observability-mode .obs-table-shell,
|
||||
.momo-observability-mode [class$="-table-shell"] {
|
||||
position: relative;
|
||||
border: 1px solid var(--obs-line) !important;
|
||||
@@ -453,6 +454,7 @@
|
||||
}
|
||||
|
||||
/* Data surface polish: tables, matrices, lists */
|
||||
.momo-observability-mode .obs-table-shell,
|
||||
.momo-observability-mode [class$="-table-shell"],
|
||||
.momo-observability-mode .table-responsive {
|
||||
background:
|
||||
|
||||
Reference in New Issue
Block a user