fix: дубликаты RUNNING/OK в истории — UPSERT по op_uid (v1.2.37)

Причина: save_run всегда делал INSERT, сценарий вызывал его дважды
(до поллинга RUNNING, после OK) → две строки в БД.

Исправление:
- init_db.py: частичный уникальный индекс idx_runs_op_uid
- save_run.py: INSERT ... ON CONFLICT (op_uid) DO UPDATE
  (статус, длительность, ошибка, этапы, meta — обновляются)
This commit is contained in:
2026-08-03 10:00:29 +04:00
parent 9fda5f042c
commit 539cfd7b1a
3 changed files with 14 additions and 1 deletions
+1 -1
View File
@@ -32,7 +32,7 @@ from routes.api_scenario_defs import bp_defs as api_scenario_defs_bp
# Версия — показывается в топбаре UI. Меняется при КАЖДОМ изменении кода. # Версия — показывается в топбаре UI. Меняется при КАЖДОМ изменении кода.
# Нужна для фильтрации истории (пользователь видит только записи своей версии). # Нужна для фильтрации истории (пользователь видит только записи своей версии).
VERSION = "1.2.36" VERSION = "1.2.37"
# Flask-приложение с Jinja2-шаблонами из папки templates/ # Flask-приложение с Jinja2-шаблонами из папки templates/
app = Flask(__name__, template_folder="templates", static_folder="static") app = Flask(__name__, template_folder="templates", static_folder="static")
+6
View File
@@ -84,6 +84,12 @@ ALTER TABLE runs ADD COLUMN IF NOT EXISTS app_version VARCHAR(16);
ALTER TABLE runs ADD COLUMN IF NOT EXISTS scenario_run_id INTEGER; ALTER TABLE runs ADD COLUMN IF NOT EXISTS scenario_run_id INTEGER;
ALTER TABLE runs ADD COLUMN IF NOT EXISTS step_number INTEGER; ALTER TABLE runs ADD COLUMN IF NOT EXISTS step_number INTEGER;
ALTER TABLE runs ADD COLUMN IF NOT EXISTS instance_meta JSONB; ALTER TABLE runs ADD COLUMN IF NOT EXISTS instance_meta JSONB;
-- Уникальный индекс на op_uid (частичный — только не-NULL).
-- Нужен для UPSERT: сценарий вызывает save_run дважды (RUNNING → OK),
-- второй вызов обновляет существующую строку вместо INSERT дубликата.
CREATE UNIQUE INDEX IF NOT EXISTS idx_runs_op_uid
ON runs (op_uid) WHERE op_uid IS NOT NULL;
""" """
# ── DDL для scenario_runs (запуски сценариев) ── # ── DDL для scenario_runs (запуски сценариев) ──
+7
View File
@@ -57,6 +57,13 @@ def save_run(client_id, stand, user_email, svc_id, svc_name, op_name, svc_op_id,
status, duration_sec, error_log, params, stages, app_version, status, duration_sec, error_log, params, stages, app_version,
scenario_run_id, step_number, instance_meta) scenario_run_id, step_number, instance_meta)
VALUES (%s, %s, %s, %s, %s, %s, %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, %s, %s, %s)
ON CONFLICT (op_uid) WHERE op_uid IS NOT NULL DO UPDATE SET
status = EXCLUDED.status,
duration_sec = EXCLUDED.duration_sec,
error_log = EXCLUDED.error_log,
stages = EXCLUDED.stages,
svc_name = EXCLUDED.svc_name,
instance_meta = EXCLUDED.instance_meta
""", ( """, (
client_id, stand, user_email, svc_id, svc_name, op_name, svc_op_id, client_id, stand, user_email, svc_id, svc_name, op_name, svc_op_id,
op_uid, instance_uid, display_name, status, duration_sec, op_uid, instance_uid, display_name, status, duration_sec,