+
+ {row.category}
+ {row.total}
+
+
+ {segments.map((tier) => (
+
0 ? ((row.bySeverity[tier] ?? 0) / row.total) * 100 : 0}%`,
+ }}
+ />
+ ))}
+
+
+ {segments.map((tier) => (
+
+ {tier} {row.bySeverity[tier]}
+
+ ))}
+
+
+ );
+}
+
+export function FindingsBreakdownCard({ findings }: { findings?: FindingsBreakdown }) {
+ const hasData = findings != null && findings.categories.length > 0;
+
+ if (!hasData) {
+ return (
+
+ );
+ }
+
+ return (
+
+
+ {findings.windowDays}d window
+
+
+ {findings.categories.map((row) => (
+
+ ))}
+
+
+ );
+}
diff --git a/apps/gittensory-ui/src/routes/app.analytics.tsx b/apps/gittensory-ui/src/routes/app.analytics.tsx
index 094868e8e6..ef7c60d014 100644
--- a/apps/gittensory-ui/src/routes/app.analytics.tsx
+++ b/apps/gittensory-ui/src/routes/app.analytics.tsx
@@ -20,6 +20,10 @@ import {
AcceptanceRateCard,
type FindingAcceptance,
} from "@/components/site/app-panels/acceptance-rate-card";
+import {
+ FindingsBreakdownCard,
+ type FindingsBreakdown,
+} from "@/components/site/app-panels/findings-breakdown-card";
import { useApiResource } from "@/lib/api/use-api-resource";
import { exportOperatorDashboardCsv } from "@/lib/csv-export";
@@ -113,6 +117,7 @@ type OperatorDashboard = {
gateEval?: GateEvalReport;
cycleTime?: CycleTimeAggregate;
acceptance?: FindingAcceptance;
+ findingsBreakdown?: FindingsBreakdown;
};
function ProductAnalytics() {
@@ -216,6 +221,8 @@ function ProductAnalytics() {
+
+
{data.usageSummary ? (