Files
app-autotest/site/routes/api_scenario_defs.py
T
naeel 91e97f6f75 v1.2.0: unified executor + flexible instance refs + params-render.js
Phase 1 — New modules:
- api/utils.py: find_uid(), uid_from_location() (replaces 2 duplicates)
- operations/poll.py: poll_until_done() (shared sync/async polling)
- operations/executor.py: execute_operation() (single CREATE/non-CREATE flow)

Phase 2 — Format + validation:
- routes/api_scenario_defs.py: _validate_steps with output/instance_ref/instance_uid
- operations/scenario.py: resolve instance_uid > instance_ref > service_id,
  use executor + poll_until_done, persist instance_bindings

Phase 3 — Migration:
- routes/api_test.py: create/non-create through executor, _finish_op through poll_until_done
- db/init_db.py: startup cleanup of stuck scenario_runs (>1h)

Phase 4 — UI shared module:
- static/js/params-render.js: renderParamRow, renderMapFixedRow, collectParams
- static/js/operations.js: use params-render.js (remove duplicates)
- templates/index.html: include params-render.js
- app.py: bump 1.1.57 → 1.2.0
2026-07-31 08:55:06 +04:00

134 lines
5.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
CRUD определений сценариев.
GET /api/scenario/definitions — список
POST /api/scenario/definitions — создать
GET /api/scenario/definitions/<id> — один
PUT /api/scenario/definitions/<id> — обновить (version → 409)
DELETE /api/scenario/definitions/<id> — мягкое удаление
"""
from flask import Blueprint, jsonify, request
from api.auth import get_client_id, get_stand, get_token_info
from db.scenario_defs import (
list_definitions, get_definition, create_definition,
update_definition, delete_definition,
)
bp_defs = Blueprint("api_scenario_defs", __name__)
def _validate_steps(steps):
"""Проверить структуру шагов сценария. Возвращает None или str с ошибкой."""
if not isinstance(steps, list) or len(steps) == 0:
return "Шаги: непустой массив"
outputs = {} # output_name → step_index (для проверки ссылок и уникальности)
for i, s in enumerate(steps):
if not isinstance(s, dict):
return f"Шаг {i+1}: должен быть объектом"
svc_id = s.get("service_id")
if not isinstance(svc_id, int) or svc_id <= 0:
return f"Шаг {i+1}: service_id — положительный int"
op = (s.get("operation") or "").strip()
if not op:
return f"Шаг {i+1}: operation обязателен"
if not isinstance(s.get("params", {}), dict):
return f"Шаг {i+1}: params — объект"
# Новые опциональные ключи: output, instance_ref, instance_uid
out = (s.get("output") or "").strip()
ref = (s.get("instance_ref") or "").strip()
uid = (s.get("instance_uid") or "").strip()
# output — только для create, уникален
if out:
if op != "create":
return f"Шаг {i+1}: output допустим только для create"
if out in outputs:
return f"Шаг {i+1}: output '{out}' уже используется в шаге {outputs[out]+1}"
outputs[out] = i
# instance_ref — должен ссылаться на существующий output из предыдущих шагов
if ref:
if ref not in outputs:
return f"Шаг {i+1}: instance_ref '{ref}' — output не найден (должен быть в create-шаге выше)"
# Для не-create: должен быть instance_ref, instance_uid, или старый формат (fallback)
if op != "create":
has_target = bool(ref or uid or (not out)) # out в не-create — ошибка выше
# ok: ref есть, uid есть, или старый формат без новых ключей (fallback)
return None
@bp_defs.route("/api/scenario/definitions", methods=["GET", "POST"])
def api_definitions():
cid = get_client_id()
stand = get_stand()
if request.method == "GET":
try:
defs = list_definitions(cid, stand)
return jsonify(defs)
except Exception as e:
return jsonify({"error": str(e)}), 500
if request.method == "POST":
try:
data = request.get_json()
name = (data.get("name") or "").strip()
if not name:
return jsonify({"error": "name is required"}), 400
steps = data.get("steps", [])
err = _validate_steps(steps)
if err:
return jsonify({"error": err}), 400
user = get_token_info().get("email", "")
result = create_definition(cid, stand, name, steps, user)
if result is None:
return jsonify({"error": "create failed"}), 500
return jsonify(result), 201
except Exception as e:
return jsonify({"error": str(e)}), 500
@bp_defs.route("/api/scenario/definitions/<int:def_id>", methods=["GET", "PUT", "DELETE"])
def api_definition(def_id):
cid = get_client_id()
stand = get_stand()
if request.method == "GET":
d = get_definition(def_id, cid, stand)
if not d:
return jsonify({"error": "not found"}), 404
return jsonify(d)
if request.method == "PUT":
try:
data = request.get_json()
name = (data.get("name") or "").strip()
version = data.get("version")
if not name:
return jsonify({"error": "name is required"}), 400
if not isinstance(version, int):
return jsonify({"error": "version (int) is required"}), 400
steps = data.get("steps", [])
err = _validate_steps(steps)
if err:
return jsonify({"error": err}), 400
user = get_token_info().get("email", "")
result = update_definition(def_id, cid, stand, name, steps, version, user)
if result.get("conflict"):
return jsonify({"error": "Conflict: definition was modified"}), 409
if result.get("error"):
return jsonify({"error": result["error"]}), 500
return jsonify(result)
except Exception as e:
return jsonify({"error": str(e)}), 500
if request.method == "DELETE":
ok = delete_definition(def_id, cid, stand)
if not ok:
return jsonify({"error": "not found"}), 404
return jsonify({"ok": True})