Files
wehub-resource-sync 5a558eb09e
TypeScript SDK Compatibility V1.x E2E Tests / Select Node version matrix (push) Has been cancelled
TypeScript SDK Compatibility V1.x E2E Tests / TypeScript SDK Compatibility V1.x E2E Tests Node ${{matrix.node_version}} (push) Has been cancelled
TypeScript SDK E2E Tests / TypeScript SDK E2E Tests Node ${{matrix.node_version}} (push) Has been cancelled
Opik Optimizer - E2E Tests / build-opik (push) Has been cancelled
TypeScript SDK Compatibility V1.x E2E Tests / build-opik (push) Has been cancelled
Python SDK E2E Tests / Select Python version matrix (push) Has been cancelled
Python SDK E2E Tests / Python SDK E2E Tests ${{matrix.python_version}} (push) Has been cancelled
Python SDK E2E Tests / build-opik (push) Has been cancelled
Python SDK Compatibility V1.x E2E Tests / Select Python version matrix (push) Has been cancelled
Python SDK Compatibility V1.x E2E Tests / Python SDK Compatibility V1.x E2E Tests ${{matrix.python_version}} (push) Has been cancelled
Python SDK Compatibility V1.x E2E Tests / build-opik (push) Has been cancelled
TypeScript SDK E2E Tests / Select Node version matrix (push) Has been cancelled
TypeScript SDK E2E Tests / build-opik (push) Has been cancelled
Opik Optimizer - E2E Tests / Opik Optimizer E2E Tests Python ${{matrix.python_version}} (push) Has been cancelled
Opik Optimizer - E2E Tests / Opik Optimizer Integration Smoke Tests (push) Has been cancelled
🐙 Code Quality / detect (push) Has been cancelled
🐙 Code Quality / lint (${{ matrix.leg.name }}) (push) Has been cancelled
🐙 Code Quality / summary (push) Has been cancelled
TypeScript SDK Library Integration Tests / Check Secrets (push) Has been cancelled
TypeScript SDK Library Integration Tests / opik-vercel (Vercel AI SDK / eve) (push) Has been cancelled
SDK Library Integration Tests Runner / Check Secrets (push) Has been cancelled
SDK Library Integration Tests Runner / Missed OpenAI API Key Warning (push) Has been cancelled
SDK Library Integration Tests Runner / Build (push) Has been cancelled
SDK Library Integration Tests Runner / openai_tests (push) Has been cancelled
SDK Library Integration Tests Runner / langchain_tests (push) Has been cancelled
SDK Library Integration Tests Runner / langchain_legacy_tests (push) Has been cancelled
SDK Library Integration Tests Runner / llama_index_tests (push) Has been cancelled
SDK Library Integration Tests Runner / anthropic_tests (push) Has been cancelled
SDK Library Integration Tests Runner / mistral_tests (push) Has been cancelled
SDK Library Integration Tests Runner / groq_tests (push) Has been cancelled
SDK Library Integration Tests Runner / aisuite_tests (push) Has been cancelled
SDK Library Integration Tests Runner / haystack_tests (push) Has been cancelled
SDK Library Integration Tests Runner / dspy_tests (push) Has been cancelled
SDK Library Integration Tests Runner / crewai_v0_tests (push) Has been cancelled
SDK Library Integration Tests Runner / crewai_v1_tests (push) Has been cancelled
SDK Library Integration Tests Runner / genai_tests (push) Has been cancelled
SDK Library Integration Tests Runner / adk_tests (push) Has been cancelled
SDK Library Integration Tests Runner / adk_legacy_1_3_0_tests (push) Has been cancelled
SDK Library Integration Tests Runner / evaluation_metrics_tests (push) Has been cancelled
SDK Library Integration Tests Runner / bedrock_tests (push) Has been cancelled
SDK Library Integration Tests Runner / litellm_tests (push) Has been cancelled
SDK Library Integration Tests Runner / harbor_tests (push) Has been cancelled
SDK Library Integration Tests Runner / Slack Notification (push) Has been cancelled
Lint Opik Helm Chart / render-equality (push) Has been cancelled
Opik Optimizer - Unit Tests / Opik Optimizer Unit Tests Python ${{matrix.python_version}} (push) Has been cancelled
Python BE E2E Tests / Python BE E2E (push) Has been cancelled
Python Backend Tests / run-python-backend-tests (push) Has been cancelled
Python SDK Unit Tests / Python SDK Unit Tests ${{matrix.python_version}} (push) Has been cancelled
Release Drafter / update_release_draft (push) Has been cancelled
SDK E2E Libraries Integration Tests / Check Secrets (push) Has been cancelled
SDK E2E Libraries Integration Tests / Missed OpenAI API Key Warning (push) Has been cancelled
SDK E2E Libraries Integration Tests / build-opik (push) Has been cancelled
SDK E2E Libraries Integration Tests / E2E Lib Integration Python ${{matrix.python_version}} (push) Has been cancelled
TypeScript SDK Integration Build & Publish / build-and-publish (opik-gemini) (push) Has been cancelled
TypeScript SDK Integration Build & Publish / build-and-publish (opik-langchain) (push) Has been cancelled
TypeScript SDK Integration Build & Publish / build-and-publish (opik-openai) (push) Has been cancelled
TypeScript SDK Integration Build & Publish / build-and-publish (opik-otel) (push) Has been cancelled
TypeScript SDK Integration Build & Publish / build-and-publish (opik-vercel) (push) Has been cancelled
TypeScript SDK Build & Publish / build-and-publish (push) Has been cancelled
TypeScript SDK Unit Tests / Test on Node ${{ matrix.node-version }} (push) Has been cancelled
Backend Tests / discover-tests (push) Has been cancelled
Backend Tests / ${{ matrix.name }} (push) Has been cancelled
Build and Publish SDK / build-and-publish (push) Has been cancelled
Build Opik Docker Images / set-version (push) Has been cancelled
Build Opik Docker Images / build-backend (push) Has been cancelled
Build Opik Docker Images / build-sandbox-executor-python (push) Has been cancelled
Build Opik Docker Images / build-python-backend (push) Has been cancelled
Build Opik Docker Images / build-frontend (push) Has been cancelled
Build Opik Docker Images / create-git-tag (push) Has been cancelled
ClickHouse Migration Cluster Check / validate-clickhouse-migrations (push) Has been cancelled
Docs - Publish / run (push) Has been cancelled
E2E Tests - Post Merge (v2) / 🧪 E2E v2 Tests (${{ github.event.inputs.tier || 't1' }}) (push) Has been cancelled
E2E Tests - Post Merge (v2) / 📢 Slack Notification (push) Has been cancelled
Frontend Unit Tests / Test on Node 20 (push) Has been cancelled
Guardrails E2E Tests / Select Python version matrix (push) Has been cancelled
Guardrails E2E Tests / Guardrails E2E Tests ${{matrix.python_version}} (push) Has been cancelled
Guardrails E2E Tests / 📢 Slack Notification (push) Has been cancelled
Guardrails Backend Unit Tests / Guardrails Backend Unit Tests (push) Has been cancelled
Guardrails Backend Unit Tests / 📢 Slack Notification (push) Has been cancelled
Lint Opik Helm Chart / lint-helm-chart (Helm v3.21.0) (push) Has been cancelled
Lint Opik Helm Chart / lint-helm-chart (Helm v4.2.0) (push) Has been cancelled
Lint Opik Helm Chart / unittest-helm-chart (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:25:44 +08:00

204 lines
6.7 KiB
Python

#!/usr/bin/env python3
"""
Pre-migration snapshot.
Captures all orphan alerts (project_id IS NULL) with their triggers and trigger
configs and writes raw DB state to a timestamped JSON file.
No migration logic — pure data capture.
Run BEFORE the AlertProjectMigrationJob processes anything.
Usage:
pip install pymysql
DB_HOST=<your-host> DB_PORT=3306 DB_NAME=<your-db> DB_USER=<your-user> DB_PASSWORD=<your-password> \
python snapshot.py
python snapshot.py --output my_snapshot.json # custom output path
"""
import argparse
import json
import os
import sys
from datetime import datetime, timezone
try:
import pymysql
import pymysql.cursors
except ImportError:
print("ERROR: pymysql not installed. Run: pip install pymysql")
sys.exit(1)
def connect():
missing = [v for v in ("DB_HOST", "DB_NAME", "DB_USER", "DB_PASSWORD") if not os.environ.get(v)]
if missing:
print(f"ERROR: Missing required environment variables: {', '.join(missing)}")
sys.exit(1)
return pymysql.connect(
host=os.environ["DB_HOST"],
port=int(os.environ.get("DB_PORT", "3306")),
database=os.environ["DB_NAME"],
user=os.environ["DB_USER"],
password=os.environ["DB_PASSWORD"],
cursorclass=pymysql.cursors.DictCursor,
charset="utf8mb4",
)
def fetch_orphan_alerts(cursor, excluded_workspace_ids=None):
if excluded_workspace_ids:
placeholders = ", ".join(["%s"] * len(excluded_workspace_ids))
cursor.execute(
f"""
SELECT id, name, enabled, workspace_id, webhook_id,
created_by, last_updated_by, created_at, last_updated_at
FROM alerts
WHERE project_id IS NULL
AND workspace_id NOT IN ({placeholders})
ORDER BY workspace_id, id
""",
excluded_workspace_ids,
)
else:
cursor.execute("""
SELECT id, name, enabled, workspace_id, webhook_id,
created_by, last_updated_by, created_at, last_updated_at
FROM alerts
WHERE project_id IS NULL
ORDER BY workspace_id, id
""")
return cursor.fetchall()
def fetch_triggers(cursor, alert_id):
cursor.execute("""
SELECT id, alert_id, event_type, created_by, created_at
FROM alert_triggers
WHERE alert_id = %s
ORDER BY id
""", (alert_id,))
return cursor.fetchall()
def fetch_trigger_configs(cursor, trigger_id):
cursor.execute("""
SELECT id, alert_trigger_id, config_type, config_value,
group_index, created_by, last_updated_by, created_at, last_updated_at
FROM alert_trigger_configs
WHERE alert_trigger_id = %s
ORDER BY id
""", (trigger_id,))
return cursor.fetchall()
def extract_scope_project_ids(configs):
"""Return all project UUIDs listed in scope:project trigger configs."""
ids = []
for cfg in configs:
if cfg.get("config_type") != "scope:project":
continue
raw = cfg.get("config_value")
if not raw:
continue
# pymysql returns JSON columns as Python objects; guard against raw strings too
parsed = raw if isinstance(raw, dict) else json.loads(raw)
project_ids_value = parsed.get("project_ids", "[]")
try:
project_ids = (
json.loads(project_ids_value)
if isinstance(project_ids_value, str)
else project_ids_value
)
ids.extend(project_ids)
except (json.JSONDecodeError, TypeError):
pass
return ids
def serialize(v):
return v.isoformat() if hasattr(v, "isoformat") else v
def row_to_dict(row):
return {k: serialize(v) for k, v in row.items()}
def main():
parser = argparse.ArgumentParser(description="Snapshot orphan alerts before migration.")
parser.add_argument("--output", help="Output file (default: auto-timestamped)")
args = parser.parse_args()
output_file = args.output or (
f"alert_migration_snapshot_{datetime.now(timezone.utc).strftime('%Y%m%d_%H%M%S')}.json"
)
excluded_raw = os.environ.get("MIGRATION_EXCLUDED_WORKSPACE_IDS", "")
excluded_workspace_ids = [w.strip() for w in excluded_raw.split(",") if w.strip()]
print("Connecting to database...")
conn = connect()
try:
with conn.cursor() as cursor:
cursor.execute("SELECT NOW(6) AS db_time")
db_captured_at = serialize(cursor.fetchone()["db_time"])
if excluded_workspace_ids:
print(f"Excluding {len(excluded_workspace_ids)} workspace(s) "
f"(MIGRATION_EXCLUDED_WORKSPACE_IDS)")
print("Fetching orphan alerts (project_id IS NULL)...")
alerts = fetch_orphan_alerts(cursor, excluded_workspace_ids)
print(f" Found {len(alerts)} orphan alert(s)")
workspace_counts = {}
alert_records = []
for alert in alerts:
alert_id = alert["id"]
ws_id = alert["workspace_id"]
workspace_counts[ws_id] = workspace_counts.get(ws_id, 0) + 1
triggers = fetch_triggers(cursor, alert_id)
trigger_records = []
for trigger in triggers:
configs = fetch_trigger_configs(cursor, trigger["id"])
scope_ids = extract_scope_project_ids(configs)
trigger_records.append({
**row_to_dict(trigger),
"configs": [row_to_dict(c) for c in configs],
# project UUIDs from scope:project configs for this trigger
"scope_project_ids": scope_ids,
})
alert_records.append({
**row_to_dict(alert),
"triggers": trigger_records,
})
snapshot = {
"captured_at": datetime.now(timezone.utc).isoformat(),
"db_captured_at": db_captured_at,
"total_orphan_alerts": len(alert_records),
"workspaces_affected": len(workspace_counts),
"workspace_summary": {ws: cnt for ws, cnt in sorted(workspace_counts.items())},
"alerts": alert_records,
}
with open(output_file, "w") as f:
json.dump(snapshot, f, indent=2)
print(f"\nWorkspaces affected: {len(workspace_counts)}")
for ws, cnt in sorted(workspace_counts.items()):
print(f" {ws}: {cnt} alert(s)")
print(f"\nSnapshot saved → {output_file}")
print(f"After migration, run: python verify.py {output_file}")
finally:
conn.close()
if __name__ == "__main__":
main()