feat(v4.2.0): Database Templates & Dashboards
- Enhance page_templates: add description, is_recurring, recurrence_rule columns
- New table collection_dashboards for widget-based dashboard layouts
- API: PUT/DELETE /workspace/collections/{id}/templates/page/{tid} — template CRUD
- API: CRUD /workspace/collections/{id}/dashboards (create/list/update/delete)
- Dashboard layout_json supports columns + widgets with view_id/x/y/w/h
- 6 new tests (159 total)
This commit is contained in:
@@ -2228,3 +2228,150 @@ def test_remove_data_source_not_found(client):
|
||||
|
||||
resp = client.delete(f"/db/{coll_id}/sources/99999/api")
|
||||
assert resp.status_code == 404
|
||||
|
||||
|
||||
# ── v4.2.0: Templates & Dashboards ──
|
||||
|
||||
|
||||
def test_page_template_update(client):
|
||||
"""Update a page template with recurrence settings."""
|
||||
resp = client.post("/db/api", json={"name": "Template Update DB"})
|
||||
coll_id = resp.json()["id"]
|
||||
|
||||
resp = client.post(f"/workspace/collections/{coll_id}/templates/page", json={
|
||||
"name": "Weekly Report",
|
||||
"properties": {"Status": "Todo", "Priority": "P1"},
|
||||
})
|
||||
assert resp.status_code == 200
|
||||
tid = resp.json()["id"]
|
||||
|
||||
resp = client.put(f"/workspace/collections/{coll_id}/templates/page/{tid}", json={
|
||||
"name": "Weekly Report v2",
|
||||
"description": "Updated weekly report template",
|
||||
"is_recurring": True,
|
||||
"recurrence_rule": "weekly",
|
||||
"properties": {"Status": "In Progress", "Priority": "P2"},
|
||||
})
|
||||
assert resp.status_code == 200
|
||||
assert resp.json()["status"] == "updated"
|
||||
|
||||
resp = client.get(f"/workspace/collections/{coll_id}/templates/page")
|
||||
tmpls = resp.json()["templates"]
|
||||
assert len(tmpls) == 1
|
||||
assert tmpls[0]["name"] == "Weekly Report v2"
|
||||
assert tmpls[0]["is_recurring"] == 1
|
||||
assert tmpls[0]["recurrence_rule"] == "weekly"
|
||||
|
||||
|
||||
def test_page_template_delete(client):
|
||||
"""Delete a page template."""
|
||||
resp = client.post("/db/api", json={"name": "Template Delete DB"})
|
||||
coll_id = resp.json()["id"]
|
||||
|
||||
resp = client.post(f"/workspace/collections/{coll_id}/templates/page", json={
|
||||
"name": "To Delete",
|
||||
})
|
||||
tid = resp.json()["id"]
|
||||
|
||||
resp = client.delete(f"/workspace/collections/{coll_id}/templates/page/{tid}")
|
||||
assert resp.status_code == 200
|
||||
assert resp.json()["status"] == "deleted"
|
||||
|
||||
resp = client.get(f"/workspace/collections/{coll_id}/templates/page")
|
||||
assert len(resp.json()["templates"]) == 0
|
||||
|
||||
|
||||
def test_page_template_apply_with_content(client):
|
||||
"""Apply a page template that has content_json."""
|
||||
resp = client.post("/db/api", json={"name": "Content Template DB"})
|
||||
coll_id = resp.json()["id"]
|
||||
|
||||
resp = client.post(f"/workspace/collections/{coll_id}/templates/page", json={
|
||||
"name": "Meeting Notes",
|
||||
"properties": {"Status": "Todo"},
|
||||
"content": [{"type": "heading", "text": "Meeting Notes"}],
|
||||
})
|
||||
tid = resp.json()["id"]
|
||||
|
||||
resp = client.post(f"/workspace/collections/{coll_id}/templates/page/{tid}/apply", json={
|
||||
"title": "Sprint Review 2026-07-21",
|
||||
})
|
||||
assert resp.status_code == 200
|
||||
page_id = resp.json()["id"]
|
||||
|
||||
resp = client.get(f"/db/pages/{page_id}/api")
|
||||
assert resp.status_code == 200
|
||||
assert resp.json()["title"] == "Sprint Review 2026-07-21"
|
||||
|
||||
|
||||
def test_dashboard_crud(client):
|
||||
"""Full CRUD lifecycle for dashboards."""
|
||||
resp = client.post("/db/api", json={"name": "Dashboard CRUD DB"})
|
||||
coll_id = resp.json()["id"]
|
||||
|
||||
resp = client.post(f"/workspace/collections/{coll_id}/dashboards", json={
|
||||
"name": "Project Dashboard",
|
||||
"layout": {
|
||||
"columns": 2,
|
||||
"widgets": [
|
||||
{"view_id": 1, "x": 0, "y": 0, "width": 1, "height": 1},
|
||||
{"view_id": 2, "x": 1, "y": 0, "width": 1, "height": 1},
|
||||
],
|
||||
},
|
||||
})
|
||||
assert resp.status_code == 200
|
||||
assert resp.json()["status"] == "created"
|
||||
did = resp.json()["id"]
|
||||
|
||||
resp = client.get(f"/workspace/collections/{coll_id}/dashboards")
|
||||
dashboards = resp.json()["dashboards"]
|
||||
assert len(dashboards) == 1
|
||||
assert dashboards[0]["name"] == "Project Dashboard"
|
||||
|
||||
resp = client.put(f"/workspace/collections/{coll_id}/dashboards/{did}", json={
|
||||
"name": "Project Dashboard v2",
|
||||
"layout": {"columns": 3, "widgets": []},
|
||||
})
|
||||
assert resp.status_code == 200
|
||||
assert resp.json()["status"] == "updated"
|
||||
|
||||
resp = client.get(f"/workspace/collections/{coll_id}/dashboards")
|
||||
assert resp.json()["dashboards"][0]["name"] == "Project Dashboard v2"
|
||||
|
||||
resp = client.delete(f"/workspace/collections/{coll_id}/dashboards/{did}")
|
||||
assert resp.status_code == 200
|
||||
assert resp.json()["status"] == "deleted"
|
||||
|
||||
resp = client.get(f"/workspace/collections/{coll_id}/dashboards")
|
||||
assert len(resp.json()["dashboards"]) == 0
|
||||
|
||||
|
||||
def test_template_recurrence_defaults(client):
|
||||
"""New templates default to non-recurring with empty recurrence_rule."""
|
||||
resp = client.post("/db/api", json={"name": "Recur Defaults DB"})
|
||||
coll_id = resp.json()["id"]
|
||||
|
||||
resp = client.post(f"/workspace/collections/{coll_id}/templates/page", json={
|
||||
"name": "Default Template",
|
||||
})
|
||||
resp = client.get(f"/workspace/collections/{coll_id}/templates/page")
|
||||
tmpl = resp.json()["templates"][0]
|
||||
assert tmpl["is_recurring"] == 0
|
||||
assert tmpl["recurrence_rule"] == ""
|
||||
|
||||
|
||||
def test_dashboard_create_default_layout(client):
|
||||
"""Dashboard creation with no layout parameter gets default grid."""
|
||||
resp = client.post("/db/api", json={"name": "Default Layout DB"})
|
||||
coll_id = resp.json()["id"]
|
||||
|
||||
resp = client.post(f"/workspace/collections/{coll_id}/dashboards", json={
|
||||
"name": "Auto Layout",
|
||||
})
|
||||
assert resp.status_code == 200
|
||||
|
||||
resp = client.get(f"/workspace/collections/{coll_id}/dashboards")
|
||||
dash = resp.json()["dashboards"][0]
|
||||
layout = json.loads(dash["layout_json"])
|
||||
assert layout["columns"] == 1
|
||||
assert layout["widgets"] == []
|
||||
|
||||
Reference in New Issue
Block a user