feat: task_tree 任务树状结构(项目→迭代→阶段→任务,懒加载)
This commit is contained in:
parent
44b2de96f3
commit
270adbce47
@ -1,4 +1,9 @@
|
|||||||
# task_tree.dspy - 任务树(懒加载:无 id 返回根节点,id=__root__ 返回任务列表)
|
# task_tree.dspy - 任务树(树状结构:项目 → 迭代 → 阶段 → 任务)
|
||||||
|
# 懒加载协议:
|
||||||
|
# 无 id → 返回项目根节点
|
||||||
|
# __root__ → 返回迭代列表(sd_iterations + 未分配迭代)
|
||||||
|
# iter:{iter_id} → 返回阶段列表(需求/设计/开发/测试/部署,只显示有任务的)
|
||||||
|
# iter:{iter_id}:phase:{role} → 返回该迭代该阶段的任务列表(叶子)
|
||||||
|
|
||||||
import json
|
import json
|
||||||
|
|
||||||
@ -11,17 +16,34 @@ node_id = (params_kw or {}).get('id', '').strip()
|
|||||||
dbname = get_module_dbname('pipeline-sdlc')
|
dbname = get_module_dbname('pipeline-sdlc')
|
||||||
|
|
||||||
_state_icons = {
|
_state_icons = {
|
||||||
'completed': '\u2705',
|
'completed': '✅', 'approved': '✅', 'running': '🟡', 'failed': '❌',
|
||||||
'approved': '\u2705',
|
'waiting': '⚠️', 'submitted': '⬜', 'review': '👀', 'rejected': '🚫',
|
||||||
'running': '\U0001f7e1',
|
'paused': '⏸️', 'cancelled': '🛑',
|
||||||
'failed': '\u274c',
|
|
||||||
'waiting': '\u26a0\ufe0f',
|
|
||||||
'submitted': '\u2b1c',
|
|
||||||
'review': '\U0001f440',
|
|
||||||
'rejected': '\U0001f6ab',
|
|
||||||
'paused': '\u23f8\ufe0f',
|
|
||||||
'cancelled': '\U0001f6d1',
|
|
||||||
}
|
}
|
||||||
|
_state_zh = {
|
||||||
|
'completed': '已完成', 'approved': '已批准', 'running': '运行中',
|
||||||
|
'failed': '失败', 'waiting': '等待中', 'submitted': '已提交',
|
||||||
|
'review': '审核中', 'rejected': '已驳回', 'paused': '已暂停', 'cancelled': '已取消',
|
||||||
|
}
|
||||||
|
# 阶段顺序(role 规范名 → 中文名)
|
||||||
|
_phase_order = [
|
||||||
|
('agent.requirement', '需求阶段'),
|
||||||
|
('agent.design', '设计阶段'),
|
||||||
|
('agent.develop', '开发阶段'),
|
||||||
|
('agent.test', '测试阶段'),
|
||||||
|
('agent.deploy', '部署阶段'),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def _iter_of(task):
|
||||||
|
"""从任务 params 里提取 iteration_id(存的是迭代名称,非 id)。"""
|
||||||
|
params_str = getattr(task, 'params', '') or ''
|
||||||
|
try:
|
||||||
|
p = json.loads(params_str) if params_str else {}
|
||||||
|
return (p.get('iteration_id') or '').strip()
|
||||||
|
except Exception:
|
||||||
|
return ''
|
||||||
|
|
||||||
|
|
||||||
async with DBPools().sqlorContext(dbname) as sor:
|
async with DBPools().sqlorContext(dbname) as sor:
|
||||||
# 当前项目
|
# 当前项目
|
||||||
@ -41,31 +63,107 @@ async with DBPools().sqlorContext(dbname) as sor:
|
|||||||
if precs:
|
if precs:
|
||||||
pname = getattr(precs[0], 'name', '') or ''
|
pname = getattr(precs[0], 'name', '') or ''
|
||||||
|
|
||||||
|
# 一次查所有任务 + 所有迭代(任务数不大,Python 分组避免 SQL JSON 函数坑)
|
||||||
|
all_tasks = await sor.sqlExe(
|
||||||
|
"SELECT id, title, role, state, params FROM pipeline_tasks WHERE tenant_id=${p}$ ORDER BY created_at ASC",
|
||||||
|
{"p": pid}) or []
|
||||||
|
iters = await sor.sqlExe(
|
||||||
|
"SELECT id, iteration_name, status FROM sd_iterations WHERE project_id=${p}$ ORDER BY created_at ASC",
|
||||||
|
{"p": pid}) or []
|
||||||
|
|
||||||
if not node_id:
|
if not node_id:
|
||||||
# 根节点
|
# 根节点
|
||||||
return json.dumps([{"id": "__root__", "label": pname or "项目", "is_leaf": False}], ensure_ascii=False)
|
return json.dumps([{"id": "__root__", "label": pname or "项目", "is_leaf": False}], ensure_ascii=False)
|
||||||
|
|
||||||
if node_id != '__root__':
|
if node_id == '__root__':
|
||||||
return json.dumps([], ensure_ascii=False)
|
# 迭代列表
|
||||||
|
nodes = []
|
||||||
# 任务列表(子节点)
|
for it in iters:
|
||||||
tasks = await sor.sqlExe(
|
iid = getattr(it, 'id', '')
|
||||||
"SELECT id, title, role, state FROM pipeline_tasks WHERE tenant_id=${p}$ ORDER BY created_at ASC",
|
iname = getattr(it, 'iteration_name', '') or ''
|
||||||
{"p": pid})
|
istatus = getattr(it, 'status', '') or ''
|
||||||
|
if not iid:
|
||||||
nodes = []
|
continue
|
||||||
for t in (tasks or []):
|
cnt = sum(1 for t in all_tasks if _iter_of(t) == iname)
|
||||||
tid = getattr(t, 'id', '')
|
nodes.append({
|
||||||
title = getattr(t, 'title', '') or ''
|
"id": "iter:" + iid,
|
||||||
role = getattr(t, 'role', '') or ''
|
"label": "🗂 {} [{}] ({})".format(iname, istatus, cnt),
|
||||||
state = getattr(t, 'state', '') or ''
|
"is_leaf": False,
|
||||||
if not tid or not title:
|
})
|
||||||
continue
|
# 未分配迭代(无 iteration_id 的任务)
|
||||||
icon = _state_icons.get(state, '\u2b1c')
|
none_cnt = sum(1 for t in all_tasks if not _iter_of(t))
|
||||||
nodes.append({
|
nodes.append({
|
||||||
"id": tid,
|
"id": "iter:__none__",
|
||||||
"label": "{} [{}] {}".format(icon, role, title),
|
"label": "🗂 未分配迭代 ({})".format(none_cnt),
|
||||||
"is_leaf": True,
|
"is_leaf": False,
|
||||||
})
|
})
|
||||||
|
return json.dumps(nodes, ensure_ascii=False)
|
||||||
|
|
||||||
return json.dumps(nodes, ensure_ascii=False)
|
if node_id.startswith('iter:'):
|
||||||
|
rest = node_id[5:]
|
||||||
|
parts = rest.split(':phase:')
|
||||||
|
iter_id = parts[0]
|
||||||
|
phase_role = parts[1] if len(parts) > 1 else ''
|
||||||
|
|
||||||
|
# 该迭代的迭代名
|
||||||
|
iter_name = ''
|
||||||
|
if iter_id != '__none__':
|
||||||
|
for it in iters:
|
||||||
|
if getattr(it, 'id', '') == iter_id:
|
||||||
|
iter_name = getattr(it, 'iteration_name', '') or ''
|
||||||
|
break
|
||||||
|
|
||||||
|
# 该迭代的任务
|
||||||
|
def _in_iter(t):
|
||||||
|
if iter_id == '__none__':
|
||||||
|
return not _iter_of(t)
|
||||||
|
return _iter_of(t) == iter_name
|
||||||
|
|
||||||
|
iter_tasks = [t for t in all_tasks if _in_iter(t)]
|
||||||
|
|
||||||
|
if not phase_role:
|
||||||
|
# 阶段列表
|
||||||
|
role_cnt = {}
|
||||||
|
for t in iter_tasks:
|
||||||
|
r = (getattr(t, 'role', '') or '').strip()
|
||||||
|
role_cnt[r] = role_cnt.get(r, 0) + 1
|
||||||
|
nodes = []
|
||||||
|
for rn, rzh in _phase_order:
|
||||||
|
if rn in role_cnt:
|
||||||
|
nodes.append({
|
||||||
|
"id": "iter:{}:phase:{}".format(iter_id, rn),
|
||||||
|
"label": "📌 {} ({})".format(rzh, role_cnt[rn]),
|
||||||
|
"is_leaf": False,
|
||||||
|
})
|
||||||
|
if '' in role_cnt:
|
||||||
|
nodes.append({
|
||||||
|
"id": "iter:{}:phase:__none__".format(iter_id),
|
||||||
|
"label": "📌 其他 ({})".format(role_cnt['']),
|
||||||
|
"is_leaf": False,
|
||||||
|
})
|
||||||
|
return json.dumps(nodes, ensure_ascii=False)
|
||||||
|
|
||||||
|
# 任务列表
|
||||||
|
nodes = []
|
||||||
|
for t in iter_tasks:
|
||||||
|
r = (getattr(t, 'role', '') or '').strip()
|
||||||
|
if phase_role == '__none__':
|
||||||
|
if r:
|
||||||
|
continue
|
||||||
|
elif r != phase_role:
|
||||||
|
continue
|
||||||
|
tid = getattr(t, 'id', '')
|
||||||
|
title = getattr(t, 'title', '') or ''
|
||||||
|
state = getattr(t, 'state', '') or ''
|
||||||
|
if not tid or not title:
|
||||||
|
continue
|
||||||
|
icon = _state_icons.get(state, '⬜')
|
||||||
|
zh = _state_zh.get(state, state)
|
||||||
|
nodes.append({
|
||||||
|
"id": tid,
|
||||||
|
"label": "{} [{}] {}".format(icon, zh, title),
|
||||||
|
"is_leaf": True,
|
||||||
|
})
|
||||||
|
return json.dumps(nodes, ensure_ascii=False)
|
||||||
|
|
||||||
|
return json.dumps([], ensure_ascii=False)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user