From 4b67a2b84571bb103e60bedd1e115ea6b2cd1aef Mon Sep 17 00:00:00 2001 From: moe-berri Date: Sat, 3 Oct 2026 17:24:18 -0700 Subject: [PATCH] feat(roi): default people and branch lists to matched accounts (#44465) * feat(roi): show matched people by default in contributor lists * fix(roi): keep matched filter tabs readable on narrow screens * fix(roi): retain spend-only users and support older browsers --- litellm/proxy/roi_calculator/README.md | 2 + .../_components/MatchedPeopleToggle.tsx | 12 ++ .../_components/ObservedDetails.tsx | 21 ++- .../ObservedROIView.integration.test.tsx | 76 +++++++++++ .../_components/ObservedReport.tsx | 68 ++++++---- .../ROICalculatorView.integration.test.tsx | 62 +++++++++ .../_components/ROICalculatorView.tsx | 14 +- .../_components/ROICalculatorViews.tsx | 33 ++--- .../_components/observedData.test.ts | 126 +++++++++++++++++- .../_components/observedData.ts | 94 ++++++++++++- .../_components/roiCalculatorData.test.ts | 55 +++++++- .../_components/roiCalculatorData.ts | 12 +- 12 files changed, 519 insertions(+), 56 deletions(-) create mode 100644 ui/litellm-dashboard/src/app/(dashboard)/roi-calculator/_components/MatchedPeopleToggle.tsx diff --git a/litellm/proxy/roi_calculator/README.md b/litellm/proxy/roi_calculator/README.md index 4db62fdad2b..82dd5463398 100644 --- a/litellm/proxy/roi_calculator/README.md +++ b/litellm/proxy/roi_calculator/README.md @@ -30,6 +30,8 @@ The gateway encrypts access and refresh tokens using its configured encryption k Use **Link accounts** to associate several current or historical usernames with one internal email. Each connection has a separate username field, so a GitHub username never matches a GitLab user implicitly. Saving immediately recalculates the report without fetching repositories again. Public profile emails match automatically when they resolve unambiguously to an internal user +**Matched people only** is on by default for people, merged changes, and branch lists. Turn it off to include outside contributors and their branches. Matching depends on the linked internal account, even when no spend was recorded. This switch filters the lists; summary metrics and quality signals still cover all selected repositories + Agent-authored changes count for a person only when the supported agent metadata explicitly names a requester. Repository issue counts and revert titles are quality signals, not an individual defect score Bug and regression counts combine repositories with issue tracking enabled. They remain unavailable when none of the selected repositories has issue tracking enabled diff --git a/ui/litellm-dashboard/src/app/(dashboard)/roi-calculator/_components/MatchedPeopleToggle.tsx b/ui/litellm-dashboard/src/app/(dashboard)/roi-calculator/_components/MatchedPeopleToggle.tsx new file mode 100644 index 00000000000..5c7eaf22384 --- /dev/null +++ b/ui/litellm-dashboard/src/app/(dashboard)/roi-calculator/_components/MatchedPeopleToggle.tsx @@ -0,0 +1,12 @@ +import { useId } from "react"; +import { Switch } from "@/components/ui/switch"; + +export function MatchedPeopleToggle({ checked, onChange }: { checked: boolean; onChange: (checked: boolean) => void }) { + const id = useId(); + return ( + + ); +} diff --git a/ui/litellm-dashboard/src/app/(dashboard)/roi-calculator/_components/ObservedDetails.tsx b/ui/litellm-dashboard/src/app/(dashboard)/roi-calculator/_components/ObservedDetails.tsx index 02d60a05029..84a4762f046 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/roi-calculator/_components/ObservedDetails.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/roi-calculator/_components/ObservedDetails.tsx @@ -24,11 +24,16 @@ import { export function PullList({ pulls, provider, + matchedOnly = false, }: { + matchedOnly?: boolean; pulls: ObservedPull[]; provider: ObservedSnapshot["source_provider"]; }) { const terms = changeTerms(provider); + const emptyMessage = matchedOnly + ? "No merged changes from matched people in this period" + : `No ${terms.lower} in this period`; const [query, setQuery] = useState(""); const [limit, setLimit] = useState(20); const filtered = pulls.filter((pull) => @@ -104,7 +109,7 @@ export function PullList({

{filtered.length === 0 && (

- {query ? `No ${terms.lower} match this search` : `No ${terms.lower} in this period`} + {query ? `No ${terms.lower} match this search` : emptyMessage}

)}
@@ -150,9 +155,7 @@ export function PersonDetails({ {person.name} - - {person.email} · {person.logins.join(", ")} - + {[person.email, person.logins.join(", ")].filter(Boolean).join(" · ")} {onEdit && ( @@ -269,7 +281,7 @@ function PeopleTable({ {people.length === 0 && (
- {query ? `No engineers match “${query}”` : "Link accounts to see your engineers"} + {query ? `No engineers match “${query}”` : emptyMessage}
)}
@@ -380,8 +392,11 @@ export default function ObservedReport({ snapshot.people.length && snapshot.periods.current.merged_prs > 0 ? "people" : "pulls", ); const [accountEmail, setAccountEmail] = useState(null); - const [personEmail, setPersonEmail] = useState(null); - const person = snapshot.people.find((entry) => entry.email === personEmail) ?? null; + const [matchedOnly, setMatchedOnly] = useState(true); + const people = useMemo(() => reportPeople(snapshot, matchedOnly), [snapshot, matchedOnly]); + const pulls = useMemo(() => filterObservedPulls(snapshot, "current", matchedOnly), [snapshot, matchedOnly]); + const [personId, setPersonId] = useState(null); + const person = people.find((entry) => entry.id === personId) ?? null; const terms = changeTerms(snapshot.source_provider); const current = snapshot.periods.current; const baseline = snapshot.periods[comparison]; @@ -497,14 +512,15 @@ export default function ObservedReport({
- + - Engineers {snapshot.people.length} + Engineers {people.length} {terms.requests} Quality Branch spend + {activeTab !== "quality" && } {!readOnly && (