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 && (