feat: enhance task management with due date handling and mention support

This commit is contained in:
Abhimanyu Saharan
2026-02-12 21:46:22 +05:30
parent 8e5fcd9243
commit 6cb5702a2b
13 changed files with 843 additions and 203 deletions

View File

@@ -6,7 +6,7 @@ from dataclasses import dataclass
from datetime import datetime, timedelta
from uuid import UUID
from fastapi import APIRouter, Depends, Query
from fastapi import APIRouter, Depends, HTTPException, Query, status
from sqlalchemy import DateTime, case
from sqlalchemy import cast as sql_cast
from sqlalchemy import func
@@ -18,6 +18,7 @@ from app.core.time import utcnow
from app.db.session import get_session
from app.models.activity_events import ActivityEvent
from app.models.agents import Agent
from app.models.boards import Board
from app.models.tasks import Task
from app.schemas.metrics import (
DashboardBucketKey,
@@ -38,6 +39,8 @@ router = APIRouter(prefix="/metrics", tags=["metrics"])
ERROR_EVENT_PATTERN = "%failed"
_RUNTIME_TYPE_REFERENCES = (UUID, AsyncSession)
RANGE_QUERY = Query(default="24h")
BOARD_ID_QUERY = Query(default=None)
GROUP_ID_QUERY = Query(default=None)
SESSION_DEP = Depends(get_session)
ORG_MEMBER_DEP = Depends(require_org_member)
@@ -385,16 +388,54 @@ async def _tasks_in_progress(
return int(result)
async def _resolve_dashboard_board_ids(
session: AsyncSession,
*,
ctx: OrganizationContext,
board_id: UUID | None,
group_id: UUID | None,
) -> list[UUID]:
board_ids = await list_accessible_board_ids(session, member=ctx.member, write=False)
if not board_ids:
return []
allowed = set(board_ids)
if board_id is not None and board_id not in allowed:
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN)
if group_id is None:
return [board_id] if board_id is not None else board_ids
group_board_ids = list(
await session.exec(
select(Board.id)
.where(col(Board.organization_id) == ctx.member.organization_id)
.where(col(Board.board_group_id) == group_id)
.where(col(Board.id).in_(board_ids)),
),
)
if board_id is not None:
return [board_id] if board_id in set(group_board_ids) else []
return group_board_ids
@router.get("/dashboard", response_model=DashboardMetrics)
async def dashboard_metrics(
range_key: DashboardRangeKey = RANGE_QUERY,
board_id: UUID | None = BOARD_ID_QUERY,
group_id: UUID | None = GROUP_ID_QUERY,
session: AsyncSession = SESSION_DEP,
ctx: OrganizationContext = ORG_MEMBER_DEP,
) -> DashboardMetrics:
"""Return dashboard KPIs and time-series data for accessible boards."""
primary = _resolve_range(range_key)
comparison = _comparison_range(primary)
board_ids = await list_accessible_board_ids(session, member=ctx.member, write=False)
board_ids = await _resolve_dashboard_board_ids(
session,
ctx=ctx,
board_id=board_id,
group_id=group_id,
)
throughput_primary = await _query_throughput(session, primary, board_ids)
throughput_comparison = await _query_throughput(session, comparison, board_ids)

View File

@@ -14,6 +14,7 @@ from app.core.error_handling import (
_error_payload,
_get_request_id,
_http_exception_exception_handler,
_json_safe,
_request_validation_exception_handler,
_response_validation_exception_handler,
install_error_handling,
@@ -209,6 +210,20 @@ def test_error_payload_omits_request_id_when_none() -> None:
assert _error_payload(detail="x", request_id=None) == {"detail": "x"}
def test_json_safe_handles_binary_inputs() -> None:
assert _json_safe(b"\xf0\x9f\x92\xa1") == "💡"
assert _json_safe(bytearray(b"hello")) == "hello"
assert _json_safe(memoryview(b"world")) == "world"
def test_json_safe_falls_back_to_string_for_unknown_objects() -> None:
class Weird:
def __str__(self) -> str:
return "weird-value"
assert _json_safe(Weird()) == "weird-value"
@pytest.mark.asyncio
async def test_request_validation_exception_wrapper_rejects_wrong_exception() -> None:
req = Request({"type": "http", "headers": [], "state": {}})

View File

@@ -0,0 +1,128 @@
from __future__ import annotations
from types import SimpleNamespace
from uuid import uuid4
import pytest
from fastapi import HTTPException
from app.api import metrics as metrics_api
class _FakeSession:
def __init__(self, exec_result: list[object]) -> None:
self._exec_result = exec_result
async def exec(self, _statement: object) -> list[object]:
return self._exec_result
@pytest.mark.asyncio
async def test_resolve_dashboard_board_ids_returns_requested_board(
monkeypatch: pytest.MonkeyPatch,
) -> None:
board_id = uuid4()
async def _accessible(*_args: object, **_kwargs: object) -> list[object]:
return [board_id]
monkeypatch.setattr(
metrics_api,
"list_accessible_board_ids",
_accessible,
)
ctx = SimpleNamespace(member=SimpleNamespace(organization_id=uuid4()))
resolved = await metrics_api._resolve_dashboard_board_ids(
_FakeSession([]),
ctx=ctx,
board_id=board_id,
group_id=None,
)
assert resolved == [board_id]
@pytest.mark.asyncio
async def test_resolve_dashboard_board_ids_rejects_inaccessible_board(
monkeypatch: pytest.MonkeyPatch,
) -> None:
accessible_board_id = uuid4()
requested_board_id = uuid4()
async def _accessible(*_args: object, **_kwargs: object) -> list[object]:
return [accessible_board_id]
monkeypatch.setattr(
metrics_api,
"list_accessible_board_ids",
_accessible,
)
ctx = SimpleNamespace(member=SimpleNamespace(organization_id=uuid4()))
with pytest.raises(HTTPException) as exc_info:
await metrics_api._resolve_dashboard_board_ids(
_FakeSession([]),
ctx=ctx,
board_id=requested_board_id,
group_id=None,
)
assert exc_info.value.status_code == 403
@pytest.mark.asyncio
async def test_resolve_dashboard_board_ids_filters_by_group(
monkeypatch: pytest.MonkeyPatch,
) -> None:
board_a = uuid4()
board_b = uuid4()
group_id = uuid4()
async def _accessible(*_args: object, **_kwargs: object) -> list[object]:
return [board_a, board_b]
monkeypatch.setattr(
metrics_api,
"list_accessible_board_ids",
_accessible,
)
ctx = SimpleNamespace(member=SimpleNamespace(organization_id=uuid4()))
session = _FakeSession([board_b])
resolved = await metrics_api._resolve_dashboard_board_ids(
session,
ctx=ctx,
board_id=None,
group_id=group_id,
)
assert resolved == [board_b]
@pytest.mark.asyncio
async def test_resolve_dashboard_board_ids_returns_empty_when_board_not_in_group(
monkeypatch: pytest.MonkeyPatch,
) -> None:
board_id = uuid4()
group_id = uuid4()
async def _accessible(*_args: object, **_kwargs: object) -> list[object]:
return [board_id]
monkeypatch.setattr(
metrics_api,
"list_accessible_board_ids",
_accessible,
)
ctx = SimpleNamespace(member=SimpleNamespace(organization_id=uuid4()))
session = _FakeSession([])
resolved = await metrics_api._resolve_dashboard_board_ids(
session,
ctx=ctx,
board_id=board_id,
group_id=group_id,
)
assert resolved == []