From f212be8d830cc3dcaf8cad0d37650ca4cf0b7ac6 Mon Sep 17 00:00:00 2001 From: dhgoal <153369624+dhgoal@users.noreply.github.com> Date: Sun, 12 Jul 2026 21:15:13 +0900 Subject: [PATCH] feat(analytics): add slop + duplicate signal to the maintainer quality dashboard (#2202) Fold a slop + duplicate aggregate into MaintainerQualityDashboard: how many of the maintainer's open PRs carry an elevated/high deterministic slop band and how many sit in a high-risk duplicate cluster, each as a rate of the open-PR total (null when there are no open PRs, guarding the divide). Pure in-memory aggregation over data the shaping loop already produces (no new IO). Adds SlopDuplicateCard rendering the two rates with proportional bars + low/watch/ high bands, wired into MaintainerPanel; empty states for a clear queue and an absent field. Current-window rates; a time-series trend is a follow-up. --- .../site/app-panels/maintainer-panel.tsx | 7 ++ .../app-panels/slop-duplicate-card.test.tsx | 63 ++++++++++ .../site/app-panels/slop-duplicate-card.tsx | 109 ++++++++++++++++++ src/services/maintainer-quality-dashboard.ts | 20 ++++ .../unit/maintainer-quality-dashboard.test.ts | 20 ++++ 5 files changed, 219 insertions(+) create mode 100644 apps/gittensory-ui/src/components/site/app-panels/slop-duplicate-card.test.tsx create mode 100644 apps/gittensory-ui/src/components/site/app-panels/slop-duplicate-card.tsx diff --git a/apps/gittensory-ui/src/components/site/app-panels/maintainer-panel.tsx b/apps/gittensory-ui/src/components/site/app-panels/maintainer-panel.tsx index d81b50e5f..89970c86c 100644 --- a/apps/gittensory-ui/src/components/site/app-panels/maintainer-panel.tsx +++ b/apps/gittensory-ui/src/components/site/app-panels/maintainer-panel.tsx @@ -27,6 +27,10 @@ import { QueueHealthCard, type MaintainerQueueHealth, } from "@/components/site/app-panels/queue-health-card"; +import { + SlopDuplicateCard, + type SlopDuplicateSignal, +} from "@/components/site/app-panels/slop-duplicate-card"; import { MaintainerSettings } from "@/components/site/app-panels/maintainer-settings"; import { OnboardingPreviewCard } from "@/components/site/app-panels/onboarding-preview-card"; import { CheckRunReadinessTable } from "@/components/site/check-run-readiness-table"; @@ -92,6 +96,7 @@ type MaintainerDashboard = { topContributors: MaintainerTopContributor[]; gateOutcomeBreakdown: GateOutcomeCardData; queueHealth?: MaintainerQueueHealth; + slopDuplicate?: SlopDuplicateSignal; }; }; @@ -388,6 +393,8 @@ function MaintainerDashboardView({ + + diff --git a/apps/gittensory-ui/src/components/site/app-panels/slop-duplicate-card.test.tsx b/apps/gittensory-ui/src/components/site/app-panels/slop-duplicate-card.test.tsx new file mode 100644 index 000000000..09d44c491 --- /dev/null +++ b/apps/gittensory-ui/src/components/site/app-panels/slop-duplicate-card.test.tsx @@ -0,0 +1,63 @@ +import { render, screen } from "@testing-library/react"; +import { describe, expect, it } from "vitest"; + +import { SlopDuplicateCard } from "@/components/site/app-panels/slop-duplicate-card"; + +describe("SlopDuplicateCard", () => { + it("renders both signal rows with rates and flagged/total counts when populated", () => { + render( + , + ); + expect(screen.getByText("Slop-flagged")).toBeTruthy(); + expect(screen.getByText("Duplicate-flagged")).toBeTruthy(); + expect(screen.getByText("15%")).toBeTruthy(); + expect(screen.getByText("40%")).toBeTruthy(); + expect(screen.getByText(/3 of 20 open PRs/)).toBeTruthy(); + expect(screen.getByText(/8 of 20 open PRs/)).toBeTruthy(); + }); + + it("renders an em-dash when a rate is null (unassessed)", () => { + render( + , + ); + expect(screen.getByText("—")).toBeTruthy(); + expect(screen.getByText("no data")).toBeTruthy(); + }); + + it("shows the 'queue is clear' empty state when there are no open PRs", () => { + render( + , + ); + expect(screen.getByText("Queue is clear")).toBeTruthy(); + expect(screen.queryByText("Slop-flagged")).toBeNull(); + }); + + it("shows the 'not yet available' empty state when the signal is absent", () => { + render(); + expect(screen.getByText("Not yet available")).toBeTruthy(); + }); +}); diff --git a/apps/gittensory-ui/src/components/site/app-panels/slop-duplicate-card.tsx b/apps/gittensory-ui/src/components/site/app-panels/slop-duplicate-card.tsx new file mode 100644 index 000000000..0e154ae90 --- /dev/null +++ b/apps/gittensory-ui/src/components/site/app-panels/slop-duplicate-card.tsx @@ -0,0 +1,109 @@ +import { AnalyticsCardShell } from "@/components/site/app-panels/analytics-card-shell"; +import { StatusPill, type Status } from "@/components/site/control-primitives"; +import { cn } from "@/lib/utils"; + +/** Aggregate slop + duplicate signal for the maintainer quality dashboard (#2202): the share of the maintainer's + * open PRs carrying an elevated/high deterministic slop band and the share sitting in a high-risk duplicate + * cluster, each as a rate of the open-PR total. Display slice over the dashboard payload's `slopDuplicate` + * aggregate — observable rates + bands, never raw scores. Degrades to an empty state when the queue is clear + * or the field is absent. (Current-window rates; a time-series trend is a follow-up per the issue thread.) */ +export type SlopDuplicateSignal = { + openPullRequests: number; + slopFlaggedPullRequests: number; + duplicateFlaggedPullRequests: number; + slopRate: number | null; + duplicateRate: number | null; +}; + +/** Lower is healthier — a small flagged share is good; a large one is a burden signal. */ +function rateStatus(rate: number | null): Status { + if (rate === null) return "info"; + if (rate < 0.1) return "ready"; + if (rate < 0.3) return "warn"; + return "degraded"; +} + +function rateBandLabel(rate: number | null): string { + if (rate === null) return "no data"; + if (rate < 0.1) return "low"; + if (rate < 0.3) return "watch"; + return "high"; +} + +function SignalRow({ + label, + rate, + flagged, + total, + bar, +}: { + label: string; + rate: number | null; + flagged: number; + total: number; + bar: string; +}) { + const pct = rate === null ? null : Math.round(rate * 100); + return ( +
+
+ {label} + + {pct === null ? "—" : `${pct}%`} + +
+
+ {pct === null ? null :
} +
+
+ + {flagged} of {total} open PR{total === 1 ? "" : "s"} + + {rateBandLabel(rate)} +
+
+ ); +} + +export function SlopDuplicateCard({ signal }: { signal?: SlopDuplicateSignal }) { + if (!signal || signal.openPullRequests === 0) { + return ( + + ); + } + + return ( + +
+ + +
+
+ ); +} diff --git a/src/services/maintainer-quality-dashboard.ts b/src/services/maintainer-quality-dashboard.ts index bfc171ca1..8336f88f5 100644 --- a/src/services/maintainer-quality-dashboard.ts +++ b/src/services/maintainer-quality-dashboard.ts @@ -58,6 +58,16 @@ export type MaintainerQualityDashboard = { ageBuckets: { under7Days: number; days7To30: number; over30Days: number }; bandCounts: Record; }; + /** Aggregate slop + duplicate signal across the SHAPED repos' open PRs (#2202): how many open PRs carry an + * elevated/high deterministic slop band, how many sit in a high-risk duplicate cluster, and each as a rate + * of the open-PR total (null when there are no open PRs). Observable counts + rates, never raw scores. */ + slopDuplicate: { + openPullRequests: number; + slopFlaggedPullRequests: number; + duplicateFlaggedPullRequests: number; + slopRate: number | null; + duplicateRate: number | null; + }; summary: string; }; @@ -91,6 +101,7 @@ export function buildMaintainerQualityDashboard(args: { repos: MaintainerQuality let openPrs = 0; let duplicatePrRisk = 0; let missingLinkedIssue = 0; + let slopFlaggedPrs = 0; const queueHealthAggregate = { openPullRequests: 0, stalePullRequests: 0, @@ -141,6 +152,8 @@ export function buildMaintainerQualityDashboard(args: { repos: MaintainerQuality const inHighRiskCluster = highRiskPrNumbers.has(pr.number); if (pr.linkedIssues.length === 0) missingLinkedIssue += 1; if (inHighRiskCluster) duplicatePrRisk += 1; + // #2202: an "elevated" or "high" deterministic slop band is the slop flag (null/absent = not assessed). + if (pr.slopBand === "elevated" || pr.slopBand === "high") slopFlaggedPrs += 1; const linkedToRealIssue = pr.linkedIssues.some((number) => realIssueNumbers.has(number)); const author = pr.authorLogin ?? "unknown"; const tally = contributorTotals.get(author) ?? { open: 0, clean: 0 }; @@ -171,6 +184,13 @@ export function buildMaintainerQualityDashboard(args: { repos: MaintainerQuality topContributors, qualitySignals: { openPrs, duplicatePrRisk, missingLinkedIssue }, queueHealth: queueHealthAggregate, + slopDuplicate: { + openPullRequests: openPrs, + slopFlaggedPullRequests: slopFlaggedPrs, + duplicateFlaggedPullRequests: duplicatePrRisk, + slopRate: openPrs > 0 ? slopFlaggedPrs / openPrs : null, + duplicateRate: openPrs > 0 ? duplicatePrRisk / openPrs : null, + }, summary, }; } diff --git a/test/unit/maintainer-quality-dashboard.test.ts b/test/unit/maintainer-quality-dashboard.test.ts index b9ced8cf6..36f3aff8a 100644 --- a/test/unit/maintainer-quality-dashboard.test.ts +++ b/test/unit/maintainer-quality-dashboard.test.ts @@ -68,6 +68,26 @@ describe("buildMaintainerQualityDashboard", () => { expect(JSON.stringify(dashboard)).not.toMatch(/"burdenScore"/); }); + it("aggregates the slop + duplicate signal and its rates, failing safe to null rates with no open PRs (#2202)", () => { + const flagged = buildMaintainerQualityDashboard({ + repos: [input({ pullRequests: [pr(1), pr(2, { slopBand: "high" })] })], + generatedAt: "2026-06-14T00:00:00.000Z", + }); + expect(flagged.slopDuplicate.openPullRequests).toBe(2); + expect(flagged.slopDuplicate.slopFlaggedPullRequests).toBe(1); + expect(flagged.slopDuplicate.slopRate).toBeCloseTo(0.5); + expect(flagged.slopDuplicate.duplicateRate).not.toBeNull(); + + // No open PRs → the rates divide-guard to null rather than 0/0. + const empty = buildMaintainerQualityDashboard({ + repos: [input({ pullRequests: [] })], + generatedAt: "2026-06-14T00:00:00.000Z", + }); + expect(empty.slopDuplicate.openPullRequests).toBe(0); + expect(empty.slopDuplicate.slopRate).toBeNull(); + expect(empty.slopDuplicate.duplicateRate).toBeNull(); + }); + it("counts PRs without a linked issue toward the missing-linked-issue signal", () => { const dashboard = buildMaintainerQualityDashboard({ repos: [input({ pullRequests: [pr(1, { linkedIssues: [] }), pr(2, { linkedIssues: [5] })] })],