feat(v4.5.0): Sprints & My Tasks
FlowDeck CI / test (push) Failing after 4s
FlowDeck CI / docker (push) Has been skipped

- New tables: sprints + sprint_pages with velocity_points
- API: CRUD sprints (list/create/update/delete)
- API: assign/remove pages to sprints
- API: burndown chart data (total/completed/remaining points, ideal line)
- 4 new tests (175 total)
This commit is contained in:
2026-07-21 22:28:30 -04:00
parent 3c5eac3429
commit 44bf469c53
3 changed files with 341 additions and 2 deletions
+140 -2
View File
@@ -812,7 +812,7 @@ def test_my_tasks_page(client):
assert "My Tasks" in resp.text
def test_my_tasks_api(client):
def test_my_tasks_cross_db(client):
"""My Tasks API returns JSON."""
r = client.post("/db/api", json={"name": "My Project"})
cid = r.json()["id"]
@@ -2568,4 +2568,142 @@ def test_auto_shift_no_blockers(client):
resp = client.post(f"/db/{coll_id}/pages/{p.json()['id']}/auto-shift/api")
assert resp.status_code == 200
assert resp.json()["shifted"] is False
assert resp.json()["shifted"] is False
# ── v4.5.0: Sprints ──
def test_sprint_crud(client):
"""Full CRUD lifecycle for sprints."""
resp = client.post("/db/api", json={"name": "Sprint DB"})
coll_id = resp.json()["id"]
# Create
resp = client.post(f"/workspace/collections/{coll_id}/sprints", json={
"name": "Sprint 1",
"start_date": "2026-07-01",
"end_date": "2026-07-14",
"goal": "Ship MVP",
"status": "active",
})
assert resp.status_code == 200
assert resp.json()["status"] == "created"
sid = resp.json()["id"]
# List
resp = client.get(f"/workspace/collections/{coll_id}/sprints")
assert len(resp.json()["sprints"]) == 1
assert resp.json()["sprints"][0]["name"] == "Sprint 1"
# Update
resp = client.put(f"/workspace/collections/{coll_id}/sprints/{sid}", json={
"name": "Sprint 1 - Revised",
"status": "completed",
})
assert resp.status_code == 200
resp = client.get(f"/workspace/collections/{coll_id}/sprints")
assert resp.json()["sprints"][0]["name"] == "Sprint 1 - Revised"
# Delete
resp = client.delete(f"/workspace/collections/{coll_id}/sprints/{sid}")
assert resp.status_code == 200
assert resp.json()["status"] == "deleted"
def test_sprint_assign_page(client):
"""Assign a page to a sprint and remove it."""
resp = client.post("/db/api", json={"name": "Assign DB"})
coll_id = resp.json()["id"]
# Create sprint
resp = client.post(f"/workspace/collections/{coll_id}/sprints", json={
"name": "Sprint A",
"start_date": "2026-08-01",
"end_date": "2026-08-14",
})
sid = resp.json()["id"]
# Create page
resp = client.post(f"/db/{coll_id}/pages/api", json={"title": "Task X"})
pid = resp.json()["id"]
# Assign
resp = client.post(f"/workspace/collections/{coll_id}/sprints/{sid}/assign", json={
"page_id": pid,
"velocity_points": 5,
})
assert resp.status_code == 200
assert resp.json()["status"] == "assigned"
# Verify page count
resp = client.get(f"/workspace/collections/{coll_id}/sprints")
assert resp.json()["sprints"][0]["page_count"] == 1
# Remove
resp = client.delete(f"/workspace/collections/{coll_id}/sprints/{sid}/assign/{pid}")
assert resp.status_code == 200
assert resp.json()["status"] == "removed"
def test_sprint_burndown(client):
"""Burndown chart data calculation."""
resp = client.post("/db/api", json={"name": "Burndown DB"})
coll_id = resp.json()["id"]
resp = client.post(f"/workspace/collections/{coll_id}/sprints", json={
"name": "Sprint B",
"start_date": "2026-07-01",
"end_date": "2026-07-14",
})
sid = resp.json()["id"]
# Add pages with different statuses
p1 = client.post(f"/db/{coll_id}/pages/api", json={
"title": "Done Task",
"properties": {"Status": "Done"},
})
p2 = client.post(f"/db/{coll_id}/pages/api", json={
"title": "In Progress Task",
"properties": {"Status": "In Progress"},
})
client.post(f"/workspace/collections/{coll_id}/sprints/{sid}/assign", json={
"page_id": p1.json()["id"], "velocity_points": 3,
})
client.post(f"/workspace/collections/{coll_id}/sprints/{sid}/assign", json={
"page_id": p2.json()["id"], "velocity_points": 5,
})
resp = client.get(f"/workspace/collections/{coll_id}/sprints/burndown/{sid}")
assert resp.status_code == 200
data = resp.json()
assert data["total_points"] == 8
assert data["completed_points"] == 3
assert data["remaining_points"] == 5
def test_my_tasks_page_render(client):
"""My Tasks page renders cross-database aggregation."""
# Create a task collection
resp = client.post("/db/api", json={"name": "My Tasks DB"})
coll_id = resp.json()["id"]
# Toggle to task mode
client.put(f"/db/{coll_id}/toggle-task/api")
# Add pages
client.post(f"/db/{coll_id}/pages/api", json={
"title": "Urgent fix",
"properties": {"Status": "Todo"},
})
client.post(f"/db/{coll_id}/pages/api", json={
"title": "Deploy",
"properties": {"Status": "Done"},
})
resp = client.get("/my-tasks?view=all")
assert resp.status_code == 200
assert "Urgent fix" in resp.text
assert "My Tasks DB" in resp.text