v1.2.1: fix tracker_add in executor + labelCls + descr context + script order

Opus review fixes:
- executor.py: tracker_add inside executor after instance_uid, before params/run (A3, orphan protection)
- executor.py: descr parameter (version/context instead of hardcoded)
- api_test.py: pass client_id/stand/descr, remove duplicate tracker_add
- scenario.py: pass client_id/stand/descr with scenario context
- params-render.js: fix labelCls ReferenceError in renderMapFixedRow (pre-existing bug)
- index.html: params-render.js already before operations.js (verified)
This commit is contained in:
2026-07-31 09:45:05 +04:00
parent 91e97f6f75
commit 353e07bfa8
5 changed files with 24 additions and 13 deletions
+1 -1
View File
@@ -19,7 +19,7 @@ from routes.api_scenario_run import bp_run as api_scenario_run_bp
from routes.api_scenario_defs import bp_defs as api_scenario_defs_bp from routes.api_scenario_defs import bp_defs as api_scenario_defs_bp
# Версия — меняется при КАЖДОМ изменении кода. Показывается в топбаре UI. # Версия — меняется при КАЖДОМ изменении кода. Показывается в топбаре UI.
VERSION = "1.2.0" VERSION = "1.2.1"
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")
+15 -2
View File
@@ -2,15 +2,18 @@
Единый executor операции Nubes. Единый executor операции Nubes.
Делает всё до /run включительно. НЕ поллит. Делает всё до /run включительно. НЕ поллит.
Вызывает tracker_add для create сразу после получения instance_uid (защита от сирот).
Используется api_test.py (ручной) и scenario.py (сценарный). Используется api_test.py (ручной) и scenario.py (сценарный).
""" """
from api.utils import find_uid, uid_from_location from api.utils import find_uid, uid_from_location
from operations.terraform import send_params_terraform from operations.terraform import send_params_terraform
from operations.tracker import add as tracker_add
def execute_operation(client, service_id, operation, instance_uid, params, def execute_operation(client, service_id, operation, instance_uid, params,
svc_op_id=None, display_name=None): svc_op_id=None, display_name=None, descr=None,
client_id="", stand=""):
"""Запустить операцию и вернуть результат. """Запустить операцию и вернуть результат.
Args: Args:
@@ -21,6 +24,9 @@ def execute_operation(client, service_id, operation, instance_uid, params,
params: dict — {numeric_param_id: value} params: dict — {numeric_param_id: value}
svc_op_id: int|None — svcOperationId (игнорируется для create) svc_op_id: int|None — svcOperationId (игнорируется для create)
display_name: str|None — displayName (только для create) display_name: str|None — displayName (только для create)
descr: str|None — описание инстанса
client_id: str — для tracker_add
stand: str — для tracker_add
Returns: Returns:
dict {ok, error, failed_step, instance_uid, op_uid, display_name} dict {ok, error, failed_step, instance_uid, op_uid, display_name}
@@ -32,7 +38,8 @@ def execute_operation(client, service_id, operation, instance_uid, params,
if is_create: if is_create:
if not display_name: if not display_name:
display_name = f"autotest-{service_id}" display_name = f"autotest-{service_id}"
descr = f"created by autotest" if not descr:
descr = "created by autotest"
payload = {"serviceId": service_id, "displayName": display_name, "descr": descr} payload = {"serviceId": service_id, "displayName": display_name, "descr": descr}
try: try:
resp = client.post("/instances", payload) resp = client.post("/instances", payload)
@@ -44,6 +51,12 @@ def execute_operation(client, service_id, operation, instance_uid, params,
return {"ok": False, "error": "No instanceUid in response", "failed_step": "instances", return {"ok": False, "error": "No instanceUid in response", "failed_step": "instances",
"instance_uid": None, "op_uid": None, "display_name": display_name} "instance_uid": None, "op_uid": None, "display_name": display_name}
# tracker_add СРАЗУ после instance_uid, до params/run — защита от сирот
try:
tracker_add(client_id, stand, instance_uid, service_id, display_name)
except Exception:
pass # не ронять операцию из-за трекера
# --- Шаг 2: POST /instanceOperations --- # --- Шаг 2: POST /instanceOperations ---
if is_create: if is_create:
op_payload = {"instanceUid": instance_uid, "operation": operation} op_payload = {"instanceUid": instance_uid, "operation": operation}
+4 -1
View File
@@ -128,16 +128,19 @@ def run_scenario(client, steps, client_id, stand, user_email, app_version, scena
if is_create: if is_create:
instance_uid = None instance_uid = None
display_name = f"{AUTOTEST_PREFIX}{scenario_name}-{uuid.uuid4().hex[:6]}" display_name = f"{AUTOTEST_PREFIX}{scenario_name}-{uuid.uuid4().hex[:6]}"
descr = f"scenario {scenario_name} step {step_num}"
else: else:
instance_uid = _resolve_instance_uid(step, bindings, instance_map) instance_uid = _resolve_instance_uid(step, bindings, instance_map)
if not instance_uid: if not instance_uid:
raise ValueError(f"No instance for step {step_num} — need CREATE before non-create operations") raise ValueError(f"No instance for step {step_num} — need CREATE before non-create operations")
display_name = None display_name = None
descr = None
# 4. Запуск через executor # 4. Запуск через executor
result = execute_operation( result = execute_operation(
client, svc_id, op_name, instance_uid, resolved_params, client, svc_id, op_name, instance_uid, resolved_params,
svc_op_id=svc_op_id, display_name=display_name svc_op_id=svc_op_id, display_name=display_name, descr=descr,
client_id=client_id, stand=stand
) )
if not result["ok"]: if not result["ok"]:
raise RuntimeError(f"{op_name}: {result['error']} (step: {result['failed_step']})") raise RuntimeError(f"{op_name}: {result['error']} (step: {result['failed_step']})")
+3 -9
View File
@@ -193,9 +193,11 @@ def api_test():
if not display_name: if not display_name:
display_name = f"autotest-{svc_id}" display_name = f"autotest-{svc_id}"
display_name = _unique_display_name(client, display_name) display_name = _unique_display_name(client, display_name)
descr = f"created by autotest v{current_app.config.get('VERSION', '')}"
result = execute_operation( result = execute_operation(
client, svc_id, op_name, None, params, client, svc_id, op_name, None, params,
svc_op_id=svc_op_id, display_name=display_name svc_op_id=svc_op_id, display_name=display_name, descr=descr,
client_id=get_client_id(), stand=get_stand()
) )
if not result["ok"]: if not result["ok"]:
return jsonify({"status": "FAIL", "error": result["error"]}), 500 return jsonify({"status": "FAIL", "error": result["error"]}), 500
@@ -203,14 +205,6 @@ def api_test():
op_uid = result["op_uid"] op_uid = result["op_uid"]
display_name = result["display_name"] display_name = result["display_name"]
# Трекер
try:
tracker_add(get_client_id(), get_stand(), instance_uid, svc_id, display_name)
except Exception as e:
import traceback
print(f"[TRACKER ERROR] add failed: {e}", flush=True)
traceback.print_exc()
# Фоновый поллинг # Фоновый поллинг
user_email = get_token_info().get("email", "") user_email = get_token_info().get("email", "")
app_version = current_app.config.get("VERSION", "") app_version = current_app.config.get("VERSION", "")
+1
View File
@@ -31,6 +31,7 @@ function renderParamRow(p, allInst) {
function renderMapFixedRow(p, dfl) { function renderMapFixedRow(p, dfl) {
const dd = p.dataDescriptor; const dd = p.dataDescriptor;
const labelCls = p.isRequired ? (dfl ? 'req' : 'req-nodfl') : '';
let subHtml = ''; let subHtml = '';
let dflObj = {}; let dflObj = {};
try { dflObj = JSON.parse(dfl || '{}'); } catch (e) { } try { dflObj = JSON.parse(dfl || '{}'); } catch (e) { }