v1.0.95: full test data — op_uid, user_email, app_version, params saved to runs

This commit is contained in:
2026-07-28 10:12:16 +04:00
parent 163dd02419
commit 99b0a79cbd
4 changed files with 43 additions and 22 deletions
+1 -1
View File
@@ -18,7 +18,7 @@ from routes.api import bp as api_bp
from routes.api_test import bp as api_test_bp from routes.api_test import bp as api_test_bp
# Версия — меняется при КАЖДОМ изменении кода. Показывается в топбаре UI. # Версия — меняется при КАЖДОМ изменении кода. Показывается в топбаре UI.
VERSION = "1.0.93" VERSION = "1.0.95"
app = Flask(__name__, template_folder="templates", static_folder="static") app = Flask(__name__, template_folder="templates", static_folder="static")
app.config["NUBES_API_ENDPOINT"] = os.getenv("NUBES_API_ENDPOINT", "https://lk-api-gateway-test.ngcloud.ru/api/v1/svc") app.config["NUBES_API_ENDPOINT"] = os.getenv("NUBES_API_ENDPOINT", "https://lk-api-gateway-test.ngcloud.ru/api/v1/svc")
+16 -3
View File
@@ -8,10 +8,12 @@
Таблица runs — история запусков операций: Таблица runs — история запусков операций:
- id, created_at - id, created_at
- client_id, stand (изоляция по пользователю и стенду) - client_id, stand (изоляция по пользователю и стенду)
- user_email (кто запустил)
- svc_id, svc_name, op_name, svc_op_id - svc_id, svc_name, op_name, svc_op_id
- instance_uid, display_name - instance_uid, display_name, op_uid
- status (OK/FAIL/TIMEOUT), duration_sec, error_log - status (OK/FAIL/TIMEOUT), duration_sec, error_log
- params (JSONB), stages (JSONB) - params (JSONB), stages (JSONB)
- app_version (версия приложения)
""" """
import os import os
@@ -23,17 +25,20 @@ CREATE TABLE IF NOT EXISTS runs (
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
client_id VARCHAR(64) NOT NULL, client_id VARCHAR(64) NOT NULL,
stand VARCHAR(16) NOT NULL, stand VARCHAR(16) NOT NULL,
user_email VARCHAR(128),
svc_id INTEGER NOT NULL, svc_id INTEGER NOT NULL,
svc_name VARCHAR(128), svc_name VARCHAR(128),
op_name VARCHAR(32) NOT NULL, op_name VARCHAR(32) NOT NULL,
svc_op_id INTEGER, svc_op_id INTEGER,
op_uid VARCHAR(64),
instance_uid VARCHAR(64), instance_uid VARCHAR(64),
display_name VARCHAR(255), display_name VARCHAR(255),
status VARCHAR(16) NOT NULL, status VARCHAR(16) NOT NULL,
duration_sec DOUBLE PRECISION, duration_sec DOUBLE PRECISION,
error_log TEXT, error_log TEXT,
params JSONB, params JSONB,
stages JSONB stages JSONB,
app_version VARCHAR(16)
); );
CREATE INDEX IF NOT EXISTS idx_runs_client_stand CREATE INDEX IF NOT EXISTS idx_runs_client_stand
@@ -46,9 +51,16 @@ CREATE INDEX IF NOT EXISTS idx_runs_created
ON runs (created_at); ON runs (created_at);
""" """
# Миграция для существующих таблиц (добавляем колонки если их нет)
MIGRATION_SQL = """
ALTER TABLE runs ADD COLUMN IF NOT EXISTS op_uid VARCHAR(64);
ALTER TABLE runs ADD COLUMN IF NOT EXISTS user_email VARCHAR(128);
ALTER TABLE runs ADD COLUMN IF NOT EXISTS app_version VARCHAR(16);
"""
def init_db(): def init_db():
"""Применить схему — идемпотентно, безопасно для конкурентного вызова.""" """Применить схему + миграции — идемпотентно, безопасно для конкурентного вызова."""
if not os.getenv("DB_USER"): if not os.getenv("DB_USER"):
print("[DB] DB_USER not set — skipping init", flush=True) print("[DB] DB_USER not set — skipping init", flush=True)
return return
@@ -61,6 +73,7 @@ def init_db():
try: try:
cur = conn.cursor() cur = conn.cursor()
cur.execute(SCHEMA_SQL) cur.execute(SCHEMA_SQL)
cur.execute(MIGRATION_SQL)
conn.commit() conn.commit()
cur.close() cur.close()
print("[DB] Schema initialized", flush=True) print("[DB] Schema initialized", flush=True)
+10 -9
View File
@@ -6,9 +6,9 @@ import json
from db.pool import get_conn, put_conn from db.pool import get_conn, put_conn
def save_run(client_id, stand, svc_id, svc_name, op_name, svc_op_id, def save_run(client_id, stand, user_email, svc_id, svc_name, op_name, svc_op_id,
instance_uid, display_name, status, duration_sec, op_uid, instance_uid, display_name, status, duration_sec,
error_log, params, stages): error_log, params, stages, app_version):
"""Сохранить один запуск в таблицу runs.""" """Сохранить один запуск в таблицу runs."""
conn = get_conn() conn = get_conn()
if not conn: if not conn:
@@ -17,16 +17,17 @@ def save_run(client_id, stand, svc_id, svc_name, op_name, svc_op_id,
try: try:
cur = conn.cursor() cur = conn.cursor()
cur.execute(""" cur.execute("""
INSERT INTO runs (client_id, stand, svc_id, svc_name, op_name, svc_op_id, INSERT INTO runs (client_id, stand, user_email, svc_id, svc_name,
instance_uid, display_name, status, duration_sec, op_name, svc_op_id, op_uid, instance_uid, display_name,
error_log, params, stages) status, duration_sec, error_log, params, stages, app_version)
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
""", ( """, (
client_id, stand, svc_id, svc_name, op_name, svc_op_id, client_id, stand, user_email, svc_id, svc_name, op_name, svc_op_id,
instance_uid, display_name, status, duration_sec, op_uid, instance_uid, display_name, status, duration_sec,
error_log, error_log,
json.dumps(params) if params else None, json.dumps(params) if params else None,
json.dumps(stages) if stages else None, json.dumps(stages) if stages else None,
app_version,
)) ))
conn.commit() conn.commit()
cur.close() cur.close()
+16 -9
View File
@@ -234,7 +234,7 @@ def api_test():
client.post(f"/instanceOperations/{op_uid}/run") client.post(f"/instanceOperations/{op_uid}/run")
# фоном ждать завершения # фоном ждать завершения
threading.Thread(target=_finish_op, args=(client, op_uid, instance_uid, svc_id, display_name, op_name, svc_op_id, True, get_client_id(), get_stand()), daemon=True).start() threading.Thread(target=_finish_op, args=(client, op_uid, instance_uid, svc_id, display_name, op_name, svc_op_id, True, get_client_id(), get_stand(), params), daemon=True).start()
return jsonify({"status": "RUNNING", "opUid": op_uid, "instanceUid": instance_uid, "displayName": display_name}) return jsonify({"status": "RUNNING", "opUid": op_uid, "instanceUid": instance_uid, "displayName": display_name})
else: else:
@@ -264,7 +264,7 @@ def api_test():
# Получить реальное имя инстанса из API (не UUID!) # Получить реальное имя инстанса из API (не UUID!)
if not display_name: if not display_name:
display_name = _get_instance_display_name(client, instance_uid) display_name = _get_instance_display_name(client, instance_uid)
threading.Thread(target=_finish_op, args=(client, op_uid, instance_uid, svc_id, display_name, op_name, svc_op_id, False, get_client_id(), get_stand(), is_delete), daemon=True).start() threading.Thread(target=_finish_op, args=(client, op_uid, instance_uid, svc_id, display_name, op_name, svc_op_id, False, get_client_id(), get_stand(), is_delete, params), daemon=True).start()
return jsonify({"status": "RUNNING", "opUid": op_uid, "instanceUid": instance_uid, "displayName": display_name}) return jsonify({"status": "RUNNING", "opUid": op_uid, "instanceUid": instance_uid, "displayName": display_name})
except Exception as e: except Exception as e:
@@ -303,7 +303,7 @@ def _get_instance_display_name(client, instance_uid):
return instance_uid return instance_uid
def _finish_op(client, op_uid, instance_uid, svc_id, display_name, op_name, svc_op_id, is_create, client_id, stand, is_delete=False): def _finish_op(client, op_uid, instance_uid, svc_id, display_name, op_name, svc_op_id, is_create, client_id, stand, is_delete=False, params=None):
"""Фоном ждать dtFinish и сохранить результат.""" """Фоном ждать dtFinish и сохранить результат."""
import time import time
_cleanup_op_results() # очистить старые записи перед добавлением новой _cleanup_op_results() # очистить старые записи перед добавлением новой
@@ -344,13 +344,18 @@ def _finish_op(client, op_uid, instance_uid, svc_id, display_name, op_name, svc_
# Сохранить в БД историю # Сохранить в БД историю
try: try:
from db.save_run import save_run from db.save_run import save_run
save_run(client_id, stand, svc_id, op.get("svc", ""), op_name, svc_op_id, from api.auth import get_token_info
instance_uid, display_name, user = get_token_info()
save_run(client_id, stand,
user.get("email", ""),
svc_id, op.get("svc", ""), op_name, svc_op_id,
op_uid, instance_uid, display_name,
"OK" if is_ok else "FAIL", "OK" if is_ok else "FAIL",
round(time.time() - t0, 1), round(time.time() - t0, 1),
str(err) if err else "", str(err) if err else "",
{}, # params недоступны в _finish_op — будут при следующем улучшении params or {},
op.get("stages", [])) op.get("stages", []),
current_app.config.get("VERSION", ""))
except Exception: except Exception:
pass # не ронять фоновый поток из-за БД pass # не ронять фоновый поток из-за БД
return return
@@ -413,8 +418,10 @@ def api_history():
return jsonify([]) return jsonify([])
cur = conn.cursor() cur = conn.cursor()
cur.execute(""" cur.execute("""
SELECT id, created_at, client_id, stand, svc_id, svc_name, SELECT id, created_at, client_id, stand, user_email,
op_name, instance_uid, display_name, status, duration_sec, error_log svc_id, svc_name, op_name, svc_op_id, op_uid,
instance_uid, display_name,
status, duration_sec, error_log, app_version
FROM runs FROM runs
WHERE client_id = %s AND stand = %s WHERE client_id = %s AND stand = %s
ORDER BY created_at DESC ORDER BY created_at DESC