feat: 项目/迭代 CRUD + fix_stuck 加 owner 校验; submit_bug 同机构校验; sd_project_create 写 created_by

This commit is contained in:
ymq 2026-08-24 12:25:11 +08:00
parent 33e5a2d045
commit a3cba2ea49
8 changed files with 64 additions and 17 deletions

View File

@ -1,24 +1,33 @@
# fix_stuck.dspy - 清除卡住任务的 claimed_by
# fix_stuck.dspy - 清除指定项目卡住任务的 claimed_by仅项目 owner 可操作)
user_id = await get_user()
if not user_id:
return json.dumps({"error": "未登录"}, ensure_ascii=False)
project_id = params_kw.get('project_id', '').strip()
if not project_id:
return json.dumps({"error": "缺少 project_id"}, ensure_ascii=False)
dbname = get_module_dbname('pipeline-sdlc')
async with DBPools().sqlorContext(dbname) as sor:
# 清除 submitted 状态但有 claimed_by 的僵尸任务
result = await sor.sqlExe(
"UPDATE pipeline_tasks SET claimed_by=NULL WHERE state='submitted' AND claimed_by IS NOT NULL",
{})
# 同时清除 review 状态但有 claimed_by 的(超时未处理的)
result2 = await sor.sqlExe(
"UPDATE pipeline_tasks SET claimed_by=NULL WHERE state='review' AND claimed_by IS NOT NULL",
{})
ok, emsg = await check_project_owner(project_id, user_id, sor)
if not ok:
return json.dumps({"error": emsg}, ensure_ascii=False)
# 清除该项目 submitted/review 状态但有 claimed_by 的僵尸任务
await sor.sqlExe(
"UPDATE pipeline_tasks SET claimed_by=NULL "
"WHERE tenant_id=${pid}$ AND state IN ('submitted','review') AND claimed_by IS NOT NULL",
{"pid": project_id})
await sor.sqlExe("COMMIT", {})
# 查看还有多少卡住的
stuck = await sor.sqlExe(
"SELECT id, title, role, state, claimed_by FROM pipeline_tasks WHERE claimed_by IS NOT NULL",
{})
"SELECT id, title, role, state, claimed_by FROM pipeline_tasks "
"WHERE tenant_id=${pid}$ AND claimed_by IS NOT NULL",
{"pid": project_id})
await sor.sqlExe("COMMIT", {})
result = {
"cleared_submitted": "done",
"cleared_review": "done",
"cleared": "done",
"remaining_stuck": [{"id": getattr(t,'id','')[:12], "state": getattr(t,'state',''),
"claimed_by": getattr(t,'claimed_by','')[:8]} for t in (stuck or [])]
}

View File

@ -14,7 +14,10 @@ data = {
try:
async with get_sor_context(request._run_ns, 'pipeline') as sor:
ok, emsg = await check_project_owner(data.get('project_id', ''), user_id, sor)
if not ok:
return {"widgettype":"Error","options":{"title":"操作失败","message":emsg,"cwidth":16,"cheight":9,"timeout":3}}
await sor.C('sd_iterations', data)
return {"widgettype":"Message","options":{"title":"创建成功","message":"ok","cwidth":16,"cheight":9,"timeout":3,"user_data": data}
return {"widgettype":"Message","options":{"title":"创建成功","message":"ok","cwidth":16,"cheight":9,"timeout":3},"user_data": data}
except Exception as e:
return {"widgettype":"Error","options":{"title":"操作失败","message":str(e),"cwidth":16,"cheight":9,"timeout":3}}

View File

@ -8,6 +8,12 @@ if not rec_id:
try:
async with get_sor_context(request._run_ns, 'pipeline') as sor:
recs = await sor.sqlExe("SELECT project_id FROM sd_iterations WHERE id=${id}$", {"id": rec_id})
await sor.sqlExe("COMMIT", {})
pid = getattr(recs[0], 'project_id', '') if recs else ''
ok, emsg = await check_project_owner(pid, user_id, sor)
if not ok:
return {"widgettype":"Error","options":{"title":"操作失败","message":emsg,"cwidth":16,"cheight":9,"timeout":3}}
await sor.D('sd_iterations', {'id': rec_id})
return {"widgettype":"Message","options":{"title":"操作成功","message":"ok","cwidth":16,"cheight":9,"timeout":3}}
except Exception as e:

View File

@ -20,6 +20,12 @@ if 'priority' in params_kw:
try:
async with get_sor_context(request._run_ns, 'pipeline') as sor:
recs = await sor.sqlExe("SELECT project_id FROM sd_iterations WHERE id=${id}$", {"id": rec_id})
await sor.sqlExe("COMMIT", {})
pid = getattr(recs[0], 'project_id', '') if recs else ''
ok, emsg = await check_project_owner(pid, user_id, sor)
if not ok:
return {"widgettype":"Error","options":{"title":"操作失败","message":emsg,"cwidth":16,"cheight":9,"timeout":3}}
await sor.U('sd_iterations', data)
return {"widgettype":"Message","options":{"title":"操作成功","message":"ok","cwidth":16,"cheight":9,"timeout":3}}
except Exception as e:

View File

@ -13,12 +13,13 @@ data = {
'pipeline_id': params_kw.get('pipeline_id', '') or 'sdlc_general',
'status': params_kw.get('status', ''),
'org_id': org_id,
'created_by': user_id,
'created_at': curDateString(),
}
try:
async with get_sor_context(request._run_ns, 'pipeline') as sor:
await sor.C('sd_projects', data)
return {"widgettype":"Message","options":{"title":"创建成功","message":"ok","cwidth":16,"cheight":9,"timeout":3,"user_data": data}
return {"widgettype":"Message","options":{"title":"创建成功","message":"ok","cwidth":16,"cheight":9,"timeout":3},"user_data": data}
except Exception as e:
return {"widgettype":"Error","options":{"title":"操作失败","message":str(e),"cwidth":16,"cheight":9,"timeout":3}}

View File

@ -8,6 +8,9 @@ if not rec_id:
try:
async with get_sor_context(request._run_ns, 'pipeline') as sor:
ok, emsg = await check_project_owner(rec_id, user_id, sor)
if not ok:
return {"widgettype":"Error","options":{"title":"操作失败","message":emsg,"cwidth":16,"cheight":9,"timeout":3}}
await sor.D('sd_projects', {'id': rec_id})
return {"widgettype":"Message","options":{"title":"操作成功","message":"ok","cwidth":16,"cheight":9,"timeout":3}}
except Exception as e:

View File

@ -24,6 +24,9 @@ if 'status' in params_kw:
try:
async with get_sor_context(request._run_ns, 'pipeline') as sor:
ok, emsg = await check_project_owner(rec_id, user_id, sor)
if not ok:
return {"widgettype":"Error","options":{"title":"操作失败","message":emsg,"cwidth":16,"cheight":9,"timeout":3}}
await sor.U('sd_projects', data)
return {"widgettype":"Message","options":{"title":"操作成功","message":"ok","cwidth":16,"cheight":9,"timeout":3}}
except Exception as e:

View File

@ -24,6 +24,22 @@ data = {
try:
async with get_sor_context(request._run_ns, 'pipeline') as sor:
# 同机构校验:人类报告的 bug 须与项目同机构agent 报告跳过)
if reporter_type == 'human' and data.get('iteration_id'):
it = await sor.sqlExe(
"SELECT project_id FROM sd_iterations WHERE id=${iid}$",
{"iid": data['iteration_id']})
await sor.sqlExe("COMMIT", {})
pid = getattr(it[0], 'project_id', '') if it else ''
if pid:
p = await sor.sqlExe("SELECT org_id FROM sd_projects WHERE id=${pid}$", {"pid": pid})
await sor.sqlExe("COMMIT", {})
proj_org = getattr(p[0], 'org_id', '') if p else ''
u = await sor.sqlExe("SELECT orgid FROM users WHERE id=${u}$", {"u": user_id})
await sor.sqlExe("COMMIT", {})
user_org = getattr(u[0], 'orgid', '') if u else ''
if proj_org and user_org != proj_org:
return json.dumps({'status': 'error', 'message': '仅同机构用户可报告 Bug'}, ensure_ascii=False)
await sor.C('sd_bugs', data)
return json.dumps({'status': 'ok', 'id': data['id']}, ensure_ascii=False)
except Exception as e: