forked from Rockachopa/Timmy-time-dashboard
Test data was bleeding into production tasks.db because swarm.task_queue.models.DB_PATH (relative path) was never patched in conftest.clean_database. Fixed by switching to absolute paths via settings.repo_root and adding the missing module to the patching list. Discord bot could leak orphaned clients on retry after ERROR state. Added _cleanup_stale() to close stale client/task before each start() attempt, with improved logging in the token watcher. Rewrote test_paperclip_client.py to use httpx.MockTransport instead of patching _get/_post/_delete — tests now exercise real HTTP status codes, error handling, and JSON parsing. Added end-to-end test for capture_error → create_task DB isolation. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
122 lines
3.8 KiB
Python
122 lines
3.8 KiB
Python
"""Bridge module: exposes create_task() for programmatic task creation.
|
|
|
|
Used by infrastructure.error_capture to auto-create bug report tasks
|
|
in the same SQLite database the dashboard routes use.
|
|
"""
|
|
|
|
import logging
|
|
import sqlite3
|
|
import uuid
|
|
from dataclasses import dataclass
|
|
from datetime import datetime
|
|
from pathlib import Path
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
# Use absolute path via settings.repo_root so tests can reliably redirect it
|
|
# and relative-path CWD differences don't cause DB leaks.
|
|
try:
|
|
from config import settings as _settings
|
|
|
|
DB_PATH = Path(_settings.repo_root) / "data" / "tasks.db"
|
|
except Exception:
|
|
DB_PATH = Path("data/tasks.db")
|
|
|
|
|
|
@dataclass
|
|
class TaskRecord:
|
|
"""Lightweight return value from create_task()."""
|
|
|
|
id: str
|
|
title: str
|
|
status: str
|
|
|
|
|
|
def _ensure_db() -> sqlite3.Connection:
|
|
DB_PATH.parent.mkdir(parents=True, exist_ok=True)
|
|
conn = sqlite3.connect(str(DB_PATH))
|
|
conn.row_factory = sqlite3.Row
|
|
conn.execute("PRAGMA journal_mode=WAL")
|
|
conn.execute("PRAGMA busy_timeout=5000")
|
|
conn.execute("""
|
|
CREATE TABLE IF NOT EXISTS tasks (
|
|
id TEXT PRIMARY KEY,
|
|
title TEXT NOT NULL,
|
|
description TEXT DEFAULT '',
|
|
status TEXT DEFAULT 'pending_approval',
|
|
priority TEXT DEFAULT 'normal',
|
|
assigned_to TEXT DEFAULT '',
|
|
created_by TEXT DEFAULT 'operator',
|
|
result TEXT DEFAULT '',
|
|
created_at TEXT DEFAULT (datetime('now')),
|
|
completed_at TEXT
|
|
)
|
|
""")
|
|
conn.commit()
|
|
return conn
|
|
|
|
|
|
def create_task(
|
|
title: str,
|
|
description: str = "",
|
|
assigned_to: str = "default",
|
|
created_by: str = "system",
|
|
priority: str = "normal",
|
|
requires_approval: bool = True,
|
|
auto_approve: bool = False,
|
|
task_type: str = "",
|
|
) -> TaskRecord:
|
|
"""Insert a task into the SQLite task queue and return a TaskRecord.
|
|
|
|
Args:
|
|
title: Task title (e.g. "[BUG] ConnectionError: ...")
|
|
description: Markdown body with error details / stack trace
|
|
assigned_to: Agent or queue to assign to
|
|
created_by: Who created the task ("system", "operator", etc.)
|
|
priority: "low" | "normal" | "high" | "urgent"
|
|
requires_approval: If False and auto_approve, skip pending_approval
|
|
auto_approve: If True, set status to "approved" immediately
|
|
task_type: Optional tag (e.g. "bug_report")
|
|
|
|
Returns:
|
|
TaskRecord with the new task's id, title, and status.
|
|
"""
|
|
valid_priorities = {"low", "normal", "high", "urgent"}
|
|
if priority not in valid_priorities:
|
|
priority = "normal"
|
|
|
|
status = "approved" if (auto_approve and not requires_approval) else "pending_approval"
|
|
task_id = str(uuid.uuid4())
|
|
now = datetime.utcnow().isoformat()
|
|
|
|
# Store task_type in description header if provided
|
|
if task_type:
|
|
description = f"**Type:** {task_type}\n{description}"
|
|
|
|
db = _ensure_db()
|
|
try:
|
|
db.execute(
|
|
"INSERT INTO tasks (id, title, description, status, priority, assigned_to, created_by, created_at) "
|
|
"VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
|
|
(task_id, title, description, status, priority, assigned_to, created_by, now),
|
|
)
|
|
db.commit()
|
|
finally:
|
|
db.close()
|
|
|
|
logger.info("Task created: %s — %s [%s]", task_id[:8], title[:60], status)
|
|
return TaskRecord(id=task_id, title=title, status=status)
|
|
|
|
|
|
def get_task_summary_for_briefing() -> dict:
|
|
"""Return a summary of task counts by status for the morning briefing."""
|
|
db = _ensure_db()
|
|
try:
|
|
rows = db.execute("SELECT status, COUNT(*) as cnt FROM tasks GROUP BY status").fetchall()
|
|
finally:
|
|
db.close()
|
|
|
|
summary = {r["status"]: r["cnt"] for r in rows}
|
|
summary["total"] = sum(summary.values())
|
|
return summary
|