fix: sidebar highlighting for shared paths and report SQL errors
Fix sidebar nav so Dashboard/Monitoring and Audit Trail/System Logs highlight independently by using useSearchParams to distinguish query-param-based routes. Fix get_jobs_over_time SQL GroupingError by using literal_column for date_trunc interval. Add date filters to status_breakdown query and fix Decimal serialization in locale stats. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
5ef7e588b6
commit
52bc499272
2 changed files with 44 additions and 18 deletions
|
|
@ -2,7 +2,7 @@ from datetime import datetime
|
|||
from typing import Any
|
||||
from uuid import UUID
|
||||
|
||||
from sqlalchemy import case, func, select
|
||||
from sqlalchemy import case, func, literal_column, select, text
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.models.audit import TokenUsageLog
|
||||
|
|
@ -37,12 +37,16 @@ class ReportService:
|
|||
result = await db.execute(job_query)
|
||||
row = result.one()
|
||||
|
||||
# Status breakdown
|
||||
# Status breakdown (apply same filters as main query)
|
||||
status_query = select(
|
||||
Job.status, func.count(Job.id)
|
||||
).group_by(Job.status)
|
||||
if client_id:
|
||||
status_query = status_query.where(Job.client_id == client_id)
|
||||
if date_from:
|
||||
status_query = status_query.where(Job.created_at >= date_from)
|
||||
if date_to:
|
||||
status_query = status_query.where(Job.created_at <= date_to)
|
||||
status_result = await db.execute(status_query)
|
||||
status_breakdown = {
|
||||
status.value: count for status, count in status_result.all()
|
||||
|
|
@ -63,16 +67,17 @@ class ReportService:
|
|||
date_to: datetime | None = None,
|
||||
) -> list[dict[str, Any]]:
|
||||
"""Get token usage and cost data grouped by date."""
|
||||
day_expr = func.date_trunc(literal_column("'day'"), TokenUsageLog.timestamp)
|
||||
query = (
|
||||
select(
|
||||
func.date_trunc("day", TokenUsageLog.timestamp).label("date"),
|
||||
day_expr.label("date"),
|
||||
func.sum(TokenUsageLog.input_tokens).label("input_tokens"),
|
||||
func.sum(TokenUsageLog.output_tokens).label("output_tokens"),
|
||||
func.sum(TokenUsageLog.total_tokens).label("total_tokens"),
|
||||
func.sum(TokenUsageLog.estimated_cost_usd).label("total_cost"),
|
||||
)
|
||||
.group_by(func.date_trunc("day", TokenUsageLog.timestamp))
|
||||
.order_by(func.date_trunc("day", TokenUsageLog.timestamp))
|
||||
.group_by(day_expr)
|
||||
.order_by(day_expr)
|
||||
)
|
||||
|
||||
if client_id:
|
||||
|
|
@ -155,13 +160,14 @@ class ReportService:
|
|||
LocaleInstance.completed_at.isnot(None),
|
||||
).group_by(LocaleInstance.locale_code)
|
||||
|
||||
needs_job_join = bool(client_id or date_from or date_to)
|
||||
if needs_job_join:
|
||||
query = query.join(Job)
|
||||
if client_id:
|
||||
query = query.join(Job).where(Job.client_id == client_id)
|
||||
query = query.where(Job.client_id == client_id)
|
||||
if date_from:
|
||||
query = query.join(Job, isouter=True).where(Job.created_at >= date_from)
|
||||
query = query.where(Job.created_at >= date_from)
|
||||
if date_to:
|
||||
if "job" not in str(query):
|
||||
query = query.join(Job, isouter=True)
|
||||
query = query.where(Job.created_at <= date_to)
|
||||
|
||||
result = await db.execute(query)
|
||||
|
|
@ -171,7 +177,7 @@ class ReportService:
|
|||
"count": row.count,
|
||||
"total_tokens": row.total_tokens or 0,
|
||||
"total_cost": float(row.total_cost or 0.0),
|
||||
"avg_duration_minutes": round((row.avg_duration_seconds or 0) / 60, 1),
|
||||
"avg_duration_minutes": round(float(row.avg_duration_seconds or 0) / 60, 1),
|
||||
}
|
||||
for row in result.all()
|
||||
]
|
||||
|
|
@ -184,15 +190,17 @@ class ReportService:
|
|||
date_to: datetime | None = None,
|
||||
) -> list[dict[str, Any]]:
|
||||
"""Get job counts grouped by week."""
|
||||
week_expr = func.date_trunc(literal_column("'week'"), Job.created_at)
|
||||
|
||||
query = select(
|
||||
func.date_trunc("week", Job.created_at).label("week"),
|
||||
week_expr.label("week"),
|
||||
func.count(Job.id).label("total"),
|
||||
func.sum(case((Job.status == JobStatus.complete, 1), else_=0)).label("completed_raw"),
|
||||
func.sum(case((Job.status == JobStatus.error, 1), else_=0)).label("errors_raw"),
|
||||
).group_by(
|
||||
func.date_trunc("week", Job.created_at)
|
||||
week_expr
|
||||
).order_by(
|
||||
func.date_trunc("week", Job.created_at)
|
||||
week_expr
|
||||
)
|
||||
|
||||
if client_id:
|
||||
|
|
@ -209,7 +217,7 @@ class ReportService:
|
|||
rows.append({
|
||||
"name": week_str,
|
||||
"jobs": row.total or 0,
|
||||
"completed": row.completed_raw or 0,
|
||||
"errors": row.errors_raw or 0,
|
||||
"completed": int(row.completed_raw or 0),
|
||||
"errors": int(row.errors_raw or 0),
|
||||
})
|
||||
return rows
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
import React, { useState } from "react";
|
||||
import Link from "next/link";
|
||||
import Image from "next/image";
|
||||
import { usePathname } from "next/navigation";
|
||||
import { usePathname, useSearchParams } from "next/navigation";
|
||||
import {
|
||||
LayoutDashboard,
|
||||
PlusCircle,
|
||||
|
|
@ -52,11 +52,29 @@ const mockUser = {
|
|||
|
||||
export function Sidebar() {
|
||||
const pathname = usePathname();
|
||||
const searchParams = useSearchParams();
|
||||
const [mobileOpen, setMobileOpen] = useState(false);
|
||||
|
||||
const isActive = (href: string) => {
|
||||
if (href === "/dashboard") return pathname === "/dashboard";
|
||||
return pathname.startsWith(href.split("?")[0]);
|
||||
const [hrefPath, hrefQuery] = href.split("?");
|
||||
const basePath = hrefPath;
|
||||
|
||||
// For items that share the same base path but differ by query params,
|
||||
// we need exact matching including query params
|
||||
if (basePath === "/dashboard") {
|
||||
const hrefHasView = hrefQuery?.includes("view=monitoring");
|
||||
const currentHasView = searchParams.get("view") === "monitoring";
|
||||
if (hrefHasView) return pathname === "/dashboard" && currentHasView;
|
||||
return pathname === "/dashboard" && !currentHasView;
|
||||
}
|
||||
if (basePath === "/admin/logs") {
|
||||
const hrefHasSystem = hrefQuery?.includes("type=system");
|
||||
const currentHasSystem = searchParams.get("type") === "system";
|
||||
if (hrefHasSystem) return pathname === "/admin/logs" && currentHasSystem;
|
||||
return pathname === "/admin/logs" && !currentHasSystem;
|
||||
}
|
||||
|
||||
return pathname.startsWith(basePath);
|
||||
};
|
||||
|
||||
const sidebarContent = (
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue