generated from coulomb/repo-seed
Improved workplan dependency management facilities
This commit is contained in:
@@ -97,9 +97,13 @@ async def get_summary(session: AsyncSession = Depends(get_session)) -> StateSumm
|
||||
|
||||
# Build a slug+title lookup for all workstreams referenced in deps
|
||||
dep_ws_ids = set()
|
||||
dep_task_ids = set()
|
||||
for d in dep_rows:
|
||||
dep_ws_ids.add(d.from_workstream_id)
|
||||
dep_ws_ids.add(d.to_workstream_id)
|
||||
if d.to_workstream_id:
|
||||
dep_ws_ids.add(d.to_workstream_id)
|
||||
if d.to_task_id:
|
||||
dep_task_ids.add(d.to_task_id)
|
||||
ws_lookup: dict = {w.id: w for w in open_ws}
|
||||
extra_ids = dep_ws_ids - set(ws_lookup.keys())
|
||||
if extra_ids:
|
||||
@@ -108,22 +112,39 @@ async def get_summary(session: AsyncSession = Depends(get_session)) -> StateSumm
|
||||
)
|
||||
for w in extra_rows.scalars():
|
||||
ws_lookup[w.id] = w
|
||||
task_lookup: dict = {}
|
||||
if dep_task_ids:
|
||||
task_rows = await session.execute(select(Task).where(Task.id.in_(dep_task_ids)))
|
||||
task_lookup = {t.id: t for t in task_rows.scalars().all()}
|
||||
|
||||
# Index: workstream_id → (depends_on stubs, blocks stubs)
|
||||
dep_index: dict = {w.id: {"depends_on": [], "blocks": []} for w in open_ws}
|
||||
for d in dep_rows:
|
||||
from_id, to_id = d.from_workstream_id, d.to_workstream_id
|
||||
if from_id in dep_index and to_id in ws_lookup:
|
||||
from_id, to_id, task_id = d.from_workstream_id, d.to_workstream_id, d.to_task_id
|
||||
if from_id in dep_index and to_id and to_id in ws_lookup:
|
||||
dep_index[from_id]["depends_on"].append(WorkstreamDepStub(
|
||||
dep_id=d.id,
|
||||
target_type="workstream",
|
||||
relationship_type=d.relationship_type,
|
||||
workstream_id=to_id,
|
||||
workstream_slug=ws_lookup[to_id].slug,
|
||||
workstream_title=ws_lookup[to_id].title,
|
||||
description=d.description,
|
||||
))
|
||||
if to_id in dep_index and from_id in ws_lookup:
|
||||
if from_id in dep_index and task_id and task_id in task_lookup:
|
||||
dep_index[from_id]["depends_on"].append(WorkstreamDepStub(
|
||||
dep_id=d.id,
|
||||
target_type="task",
|
||||
relationship_type=d.relationship_type,
|
||||
task_id=task_id,
|
||||
task_title=task_lookup[task_id].title,
|
||||
description=d.description,
|
||||
))
|
||||
if to_id and to_id in dep_index and from_id in ws_lookup:
|
||||
dep_index[to_id]["blocks"].append(WorkstreamDepStub(
|
||||
dep_id=d.id,
|
||||
target_type="workstream",
|
||||
relationship_type=d.relationship_type,
|
||||
workstream_id=from_id,
|
||||
workstream_slug=ws_lookup[from_id].slug,
|
||||
workstream_title=ws_lookup[from_id].title,
|
||||
@@ -142,7 +163,7 @@ async def get_summary(session: AsyncSession = Depends(get_session)) -> StateSumm
|
||||
"dependencies": [
|
||||
{"workstation": ws_lookup[d.to_workstream_id].status}
|
||||
for d in dep_rows
|
||||
if d.from_workstream_id == w.id and d.to_workstream_id in ws_lookup
|
||||
if d.from_workstream_id == w.id and d.to_workstream_id and d.to_workstream_id in ws_lookup
|
||||
],
|
||||
}
|
||||
flow_result = flow_engine.evaluate(flow_obj, workstream_flow)
|
||||
|
||||
@@ -5,6 +5,7 @@ from sqlalchemy import select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from api.database import get_session
|
||||
from api.models.task import Task
|
||||
from api.models.workstream import Workstream
|
||||
from api.models.workstream_dependency import WorkstreamDependency
|
||||
from api.schemas.workstream_dependency import WorkstreamDependencyCreate, WorkstreamDependencyRead
|
||||
@@ -22,17 +23,27 @@ async def create_dependency(
|
||||
body: WorkstreamDependencyCreate,
|
||||
session: AsyncSession = Depends(get_session),
|
||||
) -> WorkstreamDependency:
|
||||
"""Record that workstream_id depends on body.to_workstream_id."""
|
||||
"""Record that workstream_id depends on another workstream or a task."""
|
||||
if await session.get(Workstream, workstream_id) is None:
|
||||
raise HTTPException(status_code=404, detail="from workstream not found")
|
||||
if await session.get(Workstream, body.to_workstream_id) is None:
|
||||
raise HTTPException(status_code=404, detail="to workstream not found")
|
||||
|
||||
has_workstream_target = body.to_workstream_id is not None
|
||||
has_task_target = body.to_task_id is not None
|
||||
if has_workstream_target == has_task_target:
|
||||
raise HTTPException(status_code=422, detail="provide exactly one dependency target")
|
||||
|
||||
if body.to_workstream_id and await session.get(Workstream, body.to_workstream_id) is None:
|
||||
raise HTTPException(status_code=404, detail="target workstream not found")
|
||||
if body.to_task_id and await session.get(Task, body.to_task_id) is None:
|
||||
raise HTTPException(status_code=404, detail="target task not found")
|
||||
if workstream_id == body.to_workstream_id:
|
||||
raise HTTPException(status_code=422, detail="a workstream cannot depend on itself")
|
||||
|
||||
dep = WorkstreamDependency(
|
||||
from_workstream_id=workstream_id,
|
||||
to_workstream_id=body.to_workstream_id,
|
||||
to_task_id=body.to_task_id,
|
||||
relationship_type=body.relationship_type,
|
||||
description=body.description,
|
||||
)
|
||||
session.add(dep)
|
||||
|
||||
@@ -82,7 +82,11 @@ async def list_workstreams(
|
||||
q = q.where(Workstream.owner == owner)
|
||||
if slug:
|
||||
q = q.where(Workstream.slug == slug)
|
||||
q = q.order_by(Workstream.updated_at.desc())
|
||||
q = q.order_by(
|
||||
Workstream.planning_priority.asc().nullslast(),
|
||||
Workstream.planning_order.asc().nullslast(),
|
||||
Workstream.updated_at.desc(),
|
||||
)
|
||||
result = await session.execute(q)
|
||||
return list(result.scalars().all())
|
||||
|
||||
|
||||
Reference in New Issue
Block a user