mirror of
https://github.com/rennf93/roboco.git
synced 2026-08-03 07:23:24 +02:00
feat(auditor): waive_finding verb + findings queue panel
Wire the long-unwired mark_waived repo method to a new auditor-only flow verb waive_finding, severity-scoped to minor/nit (blocker/major must be fixed, never waived), requiring a note, with a task.finding_waived audit event and no task status change. Add the verb to the IntentSpec table (auto-derived into the auditor manifest), the flow_auditor route, and the flow_server MCP tool. Surface open review findings (cross-task, blocking-first) on the auditor dashboard via ReviewFindingsRepository.list_open_findings and a new findings field on AuditorDashboard. Restore the panel's 4-card auditor layout with a new read-only FindingsQueuePanel as the 4th card.
This commit is contained in:
@@ -10,6 +10,7 @@ import {
|
||||
import { QualityMetricsPanel } from "./quality-metrics-panel";
|
||||
import { FlaggedItemsPanel } from "./flagged-items-panel";
|
||||
import { ReportsPanel } from "./reports-panel";
|
||||
import { FindingsQueuePanel } from "./findings-queue-panel";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { FileText } from "lucide-react";
|
||||
import { toast } from "sonner";
|
||||
@@ -73,11 +74,17 @@ export function AuditorDashboard() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Quality Metrics */}
|
||||
<QualityMetricsPanel
|
||||
metrics={dashboard?.metrics}
|
||||
isLoading={loadingDashboard}
|
||||
/>
|
||||
{/* Top Row: Open Findings + Quality Metrics */}
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 xl:grid-cols-2 2xl:grid-cols-2 gap-6">
|
||||
<FindingsQueuePanel
|
||||
findings={dashboard?.findings}
|
||||
isLoading={loadingDashboard}
|
||||
/>
|
||||
<QualityMetricsPanel
|
||||
metrics={dashboard?.metrics}
|
||||
isLoading={loadingDashboard}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Bottom Row: Flagged Items + Reports */}
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 xl:grid-cols-2 2xl:grid-cols-2 gap-6">
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
"use client";
|
||||
|
||||
import { AuditorFinding } from "@/types";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||
import { ListChecks } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
|
||||
interface FindingsQueuePanelProps {
|
||||
findings: AuditorFinding[] | undefined;
|
||||
isLoading: boolean;
|
||||
}
|
||||
|
||||
// Severity stored as the finding's lowercase value (blocker/major/minor/nit).
|
||||
const severityColors: Record<string, string> = {
|
||||
blocker: "bg-red-100 text-red-700",
|
||||
major: "bg-orange-100 text-orange-700",
|
||||
minor: "bg-yellow-100 text-yellow-700",
|
||||
nit: "bg-blue-100 text-blue-700",
|
||||
};
|
||||
|
||||
const severityOrder: Record<string, number> = {
|
||||
blocker: 0,
|
||||
major: 1,
|
||||
minor: 2,
|
||||
nit: 3,
|
||||
};
|
||||
|
||||
export function FindingsQueuePanel({
|
||||
findings,
|
||||
isLoading,
|
||||
}: FindingsQueuePanelProps) {
|
||||
// The API already returns blocking-first, but keep the sort stable client-side
|
||||
// in case a later re-fetch reorders.
|
||||
const sorted = [...(findings ?? [])].sort((a, b) => {
|
||||
const diff = (severityOrder[a.severity] ?? 9) - (severityOrder[b.severity] ?? 9);
|
||||
if (diff !== 0) return diff;
|
||||
return (b.created_at ?? "").localeCompare(a.created_at ?? "");
|
||||
});
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader className="pb-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<CardTitle className="text-lg flex items-center gap-2">
|
||||
<ListChecks className="h-5 w-5" />
|
||||
Open Findings
|
||||
</CardTitle>
|
||||
{sorted.length > 0 && (
|
||||
<Badge variant="destructive">{sorted.length}</Badge>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{isLoading ? (
|
||||
<div className="space-y-3">
|
||||
{[...Array(3)].map((_, i) => (
|
||||
<Skeleton key={i} className="h-24" />
|
||||
))}
|
||||
</div>
|
||||
) : sorted.length === 0 ? (
|
||||
<div className="text-center py-8 text-muted-foreground text-sm">
|
||||
<ListChecks className="h-8 w-8 mx-auto mb-2 opacity-50" />
|
||||
No open review findings
|
||||
</div>
|
||||
) : (
|
||||
<ScrollArea className="h-[400px] pr-4">
|
||||
<div className="space-y-3">
|
||||
{sorted.map((finding) => (
|
||||
<div
|
||||
key={finding.id}
|
||||
className="p-4 rounded-lg border bg-muted/50"
|
||||
>
|
||||
<div className="flex items-center gap-2 mb-1 flex-wrap">
|
||||
<Badge
|
||||
className={
|
||||
(severityColors[finding.severity] ?? "") + " text-xs"
|
||||
}
|
||||
>
|
||||
{finding.severity}
|
||||
</Badge>
|
||||
<Badge variant="outline" className="text-xs">
|
||||
{finding.origin}
|
||||
</Badge>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
round {finding.round}
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground mb-2">
|
||||
{finding.actual ?? finding.expected ?? finding.criterion ?? "—"}
|
||||
</p>
|
||||
<div className="flex items-center gap-4 text-xs text-muted-foreground">
|
||||
{finding.file && (
|
||||
<span className="font-mono">
|
||||
{finding.file}
|
||||
{finding.line ? `:${finding.line}` : ""}
|
||||
</span>
|
||||
)}
|
||||
<Link
|
||||
href={"/tasks/" + finding.task_id}
|
||||
prefetch={false}
|
||||
>
|
||||
<span className="text-primary hover:underline">
|
||||
Task #{finding.task_id.slice(0, 8)}
|
||||
</span>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</ScrollArea>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
@@ -1187,6 +1187,7 @@ export const mockAuditorDashboard = {
|
||||
},
|
||||
],
|
||||
recent_reports: mockAuditorReports,
|
||||
findings: [],
|
||||
};
|
||||
|
||||
// Mock mode: dev = mock, production = real backend
|
||||
|
||||
@@ -611,6 +611,20 @@ export interface AuditorReport {
|
||||
sent_at: string | null;
|
||||
}
|
||||
|
||||
export interface AuditorFinding {
|
||||
id: string;
|
||||
task_id: string;
|
||||
origin: string;
|
||||
severity: string;
|
||||
file: string | null;
|
||||
line: number | null;
|
||||
criterion: string | null;
|
||||
expected: string | null;
|
||||
actual: string | null;
|
||||
round: number;
|
||||
created_at: string | null;
|
||||
}
|
||||
|
||||
export interface AuditorDashboard {
|
||||
flagged_items: AuditorFlag[];
|
||||
metrics: Record<string, number>;
|
||||
@@ -621,6 +635,7 @@ export interface AuditorDashboard {
|
||||
team: string | null;
|
||||
}>;
|
||||
recent_reports: AuditorReport[];
|
||||
findings: AuditorFinding[];
|
||||
}
|
||||
|
||||
export interface TeamHealth {
|
||||
|
||||
Reference in New Issue
Block a user