UX: implement visual product roadmap
This commit is contained in:
@@ -2,10 +2,11 @@ from __future__ import annotations
|
||||
|
||||
import uuid
|
||||
from collections.abc import Sequence
|
||||
from datetime import datetime
|
||||
from typing import Any
|
||||
|
||||
from fastapi import APIRouter, Depends, Query
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy import func, select
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.api.deps import get_db, require_operations_manager
|
||||
@@ -14,7 +15,7 @@ from app.models.booking import Booking
|
||||
from app.models.customer import Customer
|
||||
from app.models.data_quality import DataQualityIssue
|
||||
from app.models.vehicle import Vehicle
|
||||
from app.schemas import AuditEventOut, CurrentUser
|
||||
from app.schemas import AuditEventOut, AuditEventPageOut, CurrentUser
|
||||
|
||||
router = APIRouter(prefix="/api/v1/audit", tags=["audit"])
|
||||
|
||||
@@ -51,26 +52,51 @@ def _resolve_entity_refs(db: Session, events: Sequence[AuditEvent]) -> dict[uuid
|
||||
return refs
|
||||
|
||||
|
||||
@router.get("", response_model=list[AuditEventOut])
|
||||
@router.get("", response_model=list[AuditEventOut] | AuditEventPageOut)
|
||||
def list_audit_events(
|
||||
actor_label: str | None = Query(default=None),
|
||||
action: str | None = Query(default=None),
|
||||
entity_type: str | None = Query(default=None),
|
||||
entity_ref: str | None = Query(default=None, min_length=1, max_length=100),
|
||||
correlation_id: str | None = Query(default=None),
|
||||
limit: int = Query(default=100, le=500),
|
||||
occurred_from: datetime | None = Query(default=None),
|
||||
occurred_to: datetime | None = Query(default=None),
|
||||
page: int | None = Query(default=None, ge=1),
|
||||
page_size: int = Query(default=25, ge=1, le=25),
|
||||
db: Session = Depends(get_db),
|
||||
_user: CurrentUser = Depends(require_operations_manager),
|
||||
) -> list[AuditEventOut]:
|
||||
stmt = select(AuditEvent).order_by(AuditEvent.occurred_at.desc()).limit(limit)
|
||||
) -> list[AuditEventOut] | AuditEventPageOut:
|
||||
stmt = select(AuditEvent).order_by(AuditEvent.occurred_at.desc())
|
||||
if actor_label:
|
||||
stmt = stmt.where(AuditEvent.actor_label == actor_label)
|
||||
if action:
|
||||
stmt = stmt.where(AuditEvent.action == action)
|
||||
if entity_type:
|
||||
stmt = stmt.where(AuditEvent.entity_type == entity_type)
|
||||
if entity_ref:
|
||||
matched_ids: set[uuid.UUID] = set()
|
||||
for model in _ENTITY_MODELS.values():
|
||||
matched_ids.update(
|
||||
db.scalars(select(model.id).where(model.public_ref.ilike(f"%{entity_ref.strip()}%"))).all()
|
||||
)
|
||||
if not matched_ids:
|
||||
if page is None:
|
||||
return []
|
||||
return AuditEventPageOut(
|
||||
items=[], page=1, page_size=page_size, total=0, total_pages=1
|
||||
)
|
||||
stmt = stmt.where(AuditEvent.entity_id.in_(matched_ids))
|
||||
if correlation_id:
|
||||
stmt = stmt.where(AuditEvent.correlation_id == correlation_id)
|
||||
events = db.scalars(stmt).all()
|
||||
if occurred_from:
|
||||
stmt = stmt.where(AuditEvent.occurred_at >= occurred_from)
|
||||
if occurred_to:
|
||||
stmt = stmt.where(AuditEvent.occurred_at <= occurred_to)
|
||||
total = db.scalar(select(func.count()).select_from(stmt.subquery())) or 0
|
||||
page_number = page or 1
|
||||
events = db.scalars(
|
||||
stmt if page is None else stmt.offset((page_number - 1) * page_size).limit(page_size)
|
||||
).all()
|
||||
entity_refs = _resolve_entity_refs(db, events)
|
||||
|
||||
out = []
|
||||
@@ -94,4 +120,13 @@ def list_audit_events(
|
||||
metadata=e.metadata_json,
|
||||
)
|
||||
)
|
||||
return out
|
||||
if page is None:
|
||||
return out
|
||||
total_pages = max(1, (total + page_size - 1) // page_size)
|
||||
return AuditEventPageOut(
|
||||
items=out,
|
||||
page=min(page_number, total_pages),
|
||||
page_size=page_size,
|
||||
total=total,
|
||||
total_pages=total_pages,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user