Skip to content

Commit 638d7c5

Browse files
committed
feat: add exceeded requests details
1 parent 1a8127d commit 638d7c5

2 files changed

Lines changed: 232 additions & 27 deletions

File tree

src/App.tsx

Lines changed: 144 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@
1313
import { Sheet, SheetContent, SheetHeader, SheetTitle, SheetTrigger } from "@/components/ui/sheet";
1414
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
1515
import { Tooltip as UITooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
16+
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog";
1617
import { DeploymentFooter } from "@/components/DeploymentFooter";
1718
import {
1819
AggregatedData,
@@ -21,6 +22,7 @@ import {
2122
DailyModelData,
2223
PowerUserSummary,
2324
PowerUserDailyBreakdown,
25+
ExceededRequestDetail,
2426
aggregateDataByDay,
2527
parseCSV,
2628
getModelUsageSummary,
@@ -29,7 +31,9 @@ import {
2931
getPowerUserDailyData,
3032
COPILOT_PLANS,
3133
getPowerUserDailyBreakdown,
32-
getLastDateFromData
34+
getLastDateFromData,
35+
getExceededRequestDetails,
36+
getUserExceededRequestSummary
3337
} from "@/lib/utils";
3438

3539
function App() {
@@ -46,6 +50,9 @@ function App() {
4650
const [selectedPlan, setSelectedPlan] = useState<string>(COPILOT_PLANS.BUSINESS); // Default to Business
4751
const [isProcessing, setIsProcessing] = useState(false);
4852
const [visibleBars, setVisibleBars] = useState(['compliantRequests', 'exceedingRequests']);
53+
const [showExceededDetails, setShowExceededDetails] = useState(false);
54+
const [exceededDetailsData, setExceededDetailsData] = useState<ExceededRequestDetail[]>([]);
55+
const [selectedDate, setSelectedDate] = useState<string | null>(null);
4956
const fileInputRef = useRef<HTMLInputElement>(null);
5057

5158
const handlePowerUserSelect = useCallback((userName: string | null) => {
@@ -355,6 +362,19 @@ function App() {
355362
}
356363
};
357364

365+
const handleExceedingBarClick = (barData) => {
366+
if (!data || !selectedPowerUser) return;
367+
368+
// Get the date from the clicked bar for display purposes
369+
const clickedDate = barData.date;
370+
setSelectedDate(clickedDate);
371+
372+
// Get exceeded request details for the selected power user across ALL dates
373+
const exceededDetails = getExceededRequestDetails(data, null, selectedPowerUser);
374+
setExceededDetailsData(exceededDetails);
375+
setShowExceededDetails(true);
376+
};
377+
358378
const CustomLegend = ({ payload }) => {
359379
// Define all possible bars
360380
const allPossibleBars = ['compliantRequests', 'exceedingRequests'];
@@ -608,6 +628,11 @@ function App() {
608628
- {selectedPowerUser}
609629
</span>
610630
)}
631+
{selectedPowerUser && (
632+
<div className="text-xs text-muted-foreground font-normal mt-1">
633+
💡 Click on red bars to see all exceeded request details for this user
634+
</div>
635+
)}
611636
</h3>
612637
{selectedPowerUser && (
613638
<Button
@@ -730,6 +755,8 @@ function App() {
730755
name="Exceeding Requests"
731756
stackId="requests"
732757
fill="#ef4444"
758+
onClick={handleExceedingBarClick}
759+
style={{ cursor: selectedPowerUser ? 'pointer' : 'default' }}
733760
onMouseOver={(e) => console.log('Hovered Exceeding', e)}
734761
/>
735762
)}
@@ -1020,6 +1047,122 @@ function App() {
10201047
</div>
10211048
</div>
10221049
)}
1050+
1051+
{/* Exceeded Request Details Dialog */}
1052+
<Dialog open={showExceededDetails} onOpenChange={setShowExceededDetails}>
1053+
<DialogContent className="w-[98vw] max-w-none max-h-[85vh] overflow-y-auto" style={{ width: '98vw', maxWidth: 'none' }}>
1054+
<DialogHeader>
1055+
<DialogTitle>
1056+
Exceeded Request Details
1057+
{selectedPowerUser && ` - ${selectedPowerUser}`}
1058+
</DialogTitle>
1059+
</DialogHeader>
1060+
1061+
<div className="space-y-6">
1062+
{exceededDetailsData.length > 0 ? (
1063+
<>
1064+
{/* Summary Card */}
1065+
{selectedPowerUser && data && (
1066+
<Card className="p-4">
1067+
<h3 className="text-md font-medium mb-3">User Summary</h3>
1068+
{(() => {
1069+
const userSummary = getUserExceededRequestSummary(data, selectedPowerUser);
1070+
return (
1071+
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
1072+
<div>
1073+
<div className="text-sm text-muted-foreground">Total Days with Exceeded Requests</div>
1074+
<div className="text-lg font-bold">{userSummary.totalExceededDays}</div>
1075+
</div>
1076+
<div>
1077+
<div className="text-sm text-muted-foreground">Total Exceeded Requests</div>
1078+
<div className="text-lg font-bold">{userSummary.totalExceededRequests.toLocaleString()}</div>
1079+
</div>
1080+
<div>
1081+
<div className="text-sm text-muted-foreground">Average Exceeded per Day</div>
1082+
<div className="text-lg font-bold">{userSummary.averageExceededPerDay.toFixed(1)}</div>
1083+
</div>
1084+
{userSummary.worstDay && (
1085+
<div>
1086+
<div className="text-sm text-muted-foreground">Worst Day</div>
1087+
<div className="text-sm font-bold">{userSummary.worstDay.date}</div>
1088+
<div className="text-xs text-muted-foreground">
1089+
{userSummary.worstDay.exceededRequests} exceeded of {userSummary.worstDay.totalRequests} total
1090+
</div>
1091+
</div>
1092+
)}
1093+
</div>
1094+
);
1095+
})()}
1096+
</Card>
1097+
)}
1098+
1099+
{/* Detailed Table */}
1100+
<Card className="p-4">
1101+
<h3 className="text-md font-medium mb-3">
1102+
All Exceeded Request Days ({exceededDetailsData.length} days)
1103+
</h3>
1104+
<div className="overflow-auto" style={{ minWidth: '100%' }}>
1105+
<Table className="min-w-full">
1106+
<TableHeader>
1107+
<TableRow>
1108+
<TableHead className="min-w-[120px]">Date</TableHead>
1109+
<TableHead className="text-right min-w-[140px]">Exceeded Requests</TableHead>
1110+
<TableHead className="text-right min-w-[160px]">Total Requests (Day)</TableHead>
1111+
<TableHead className="text-right min-w-[150px]">Compliant Requests</TableHead>
1112+
<TableHead className="min-w-[200px]">Models Used</TableHead>
1113+
<TableHead className="min-w-[200px]">Exceeding by Model</TableHead>
1114+
</TableRow>
1115+
</TableHeader>
1116+
<TableBody>
1117+
{exceededDetailsData.map((detail, index) => (
1118+
<TableRow key={`${detail.user}-${detail.date}-${index}`}>
1119+
<TableCell>{detail.date}</TableCell>
1120+
<TableCell className="text-right text-red-600 font-medium">
1121+
{detail.exceededRequests.toLocaleString()}
1122+
</TableCell>
1123+
<TableCell className="text-right">
1124+
{detail.totalRequestsOnDay.toLocaleString()}
1125+
</TableCell>
1126+
<TableCell className="text-right text-green-600">
1127+
{detail.compliantRequestsOnDay.toLocaleString()}
1128+
</TableCell>
1129+
<TableCell>
1130+
<div className="flex flex-wrap gap-1">
1131+
{detail.modelsUsed.map((model, idx) => (
1132+
<span
1133+
key={idx}
1134+
className="inline-block bg-secondary px-2 py-1 rounded text-xs"
1135+
>
1136+
{model}
1137+
</span>
1138+
))}
1139+
</div>
1140+
</TableCell>
1141+
<TableCell>
1142+
<div className="space-y-1">
1143+
{Object.entries(detail.exceedingByModel).map(([model, requests]) => (
1144+
<div key={model} className="text-xs">
1145+
<span className="font-medium">{model}:</span> {requests.toLocaleString()}
1146+
</div>
1147+
))}
1148+
</div>
1149+
</TableCell>
1150+
</TableRow>
1151+
))}
1152+
</TableBody>
1153+
</Table>
1154+
</div>
1155+
</Card>
1156+
</>
1157+
) : (
1158+
<Card className="p-8 text-center">
1159+
<p className="text-muted-foreground">No exceeded request details found for the selected criteria.</p>
1160+
</Card>
1161+
)}
1162+
</div>
1163+
</DialogContent>
1164+
</Dialog>
1165+
10231166
<Toaster position="top-right" />
10241167
<DeploymentFooter />
10251168
</div>

src/lib/utils.ts

Lines changed: 88 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -465,43 +465,105 @@ export function getLastDateFromData(data: CopilotUsageData[]): string | null {
465465
return sortedDates[sortedDates.length - 1];
466466
}
467467

468-
export interface ExceededRequestData {
468+
export interface ExceededRequestDetail {
469469
user: string;
470470
date: string;
471471
exceededRequests: number;
472-
totalRequests: number;
472+
totalRequestsOnDay: number;
473+
compliantRequestsOnDay: number;
474+
modelsUsed: string[];
475+
exceedingByModel: Record<string, number>;
473476
}
474477

475-
export function getExceededRequestData(data: CopilotUsageData[]): ExceededRequestData[] {
476-
const exceededData: Record<string, ExceededRequestData> = {};
478+
export function getExceededRequestDetails(data: CopilotUsageData[], targetDate?: string, targetUser?: string): ExceededRequestDetail[] {
479+
const exceededDetails: Record<string, ExceededRequestDetail> = {};
477480

478-
data.forEach(item => {
479-
if (item.exceedsQuota) {
480-
const date = item.timestamp.toISOString().split('T')[0];
481-
const key = `${item.user}-${date}`;
482-
483-
if (!exceededData[key]) {
484-
exceededData[key] = {
485-
user: item.user,
486-
date,
487-
exceededRequests: 0,
488-
totalRequests: 0,
489-
};
490-
}
491-
492-
exceededData[key].exceededRequests += item.requestsUsed;
493-
}
481+
// Filter data if specific date or user is provided
482+
let filteredData = data;
483+
if (targetDate) {
484+
filteredData = filteredData.filter(item =>
485+
item.timestamp.toISOString().split('T')[0] === targetDate
486+
);
487+
}
488+
if (targetUser) {
489+
filteredData = filteredData.filter(item => item.user === targetUser);
490+
}
494491

492+
// Process all data to find users who exceeded limits
493+
filteredData.forEach(item => {
495494
const date = item.timestamp.toISOString().split('T')[0];
496495
const key = `${item.user}-${date}`;
497496

498-
if (exceededData[key]) {
499-
exceededData[key].totalRequests += item.requestsUsed;
497+
if (!exceededDetails[key]) {
498+
exceededDetails[key] = {
499+
user: item.user,
500+
date,
501+
exceededRequests: 0,
502+
totalRequestsOnDay: 0,
503+
compliantRequestsOnDay: 0,
504+
modelsUsed: [],
505+
exceedingByModel: {},
506+
};
500507
}
501-
});
502508

503-
return Object.values(exceededData).sort((a, b) => {
504-
if (a.date !== b.date) return a.date.localeCompare(b.date);
505-
return a.user.localeCompare(b.user);
509+
const detail = exceededDetails[key];
510+
511+
// Track total requests for this day
512+
detail.totalRequestsOnDay += item.requestsUsed;
513+
514+
// Track models used
515+
if (!detail.modelsUsed.includes(item.model)) {
516+
detail.modelsUsed.push(item.model);
517+
}
518+
519+
// Track exceeded vs compliant requests
520+
if (item.exceedsQuota) {
521+
detail.exceededRequests += item.requestsUsed;
522+
detail.exceedingByModel[item.model] = (detail.exceedingByModel[item.model] || 0) + item.requestsUsed;
523+
} else {
524+
detail.compliantRequestsOnDay += item.requestsUsed;
525+
}
506526
});
527+
528+
// Filter to only return entries where users actually exceeded limits
529+
return Object.values(exceededDetails)
530+
.filter(detail => detail.exceededRequests > 0)
531+
.sort((a, b) => {
532+
if (a.date !== b.date) return b.date.localeCompare(a.date); // Most recent first
533+
return b.exceededRequests - a.exceededRequests; // Highest exceeded requests first
534+
});
535+
}
536+
537+
export function getUserExceededRequestSummary(data: CopilotUsageData[], userName: string): {
538+
totalExceededDays: number;
539+
totalExceededRequests: number;
540+
averageExceededPerDay: number;
541+
worstDay: { date: string; exceededRequests: number; totalRequests: number } | null;
542+
} {
543+
const userExceededDetails = getExceededRequestDetails(data, undefined, userName);
544+
545+
if (userExceededDetails.length === 0) {
546+
return {
547+
totalExceededDays: 0,
548+
totalExceededRequests: 0,
549+
averageExceededPerDay: 0,
550+
worstDay: null,
551+
};
552+
}
553+
554+
const totalExceededRequests = userExceededDetails.reduce((sum, detail) => sum + detail.exceededRequests, 0);
555+
const worstDay = userExceededDetails.reduce((worst, current) =>
556+
current.exceededRequests > worst.exceededRequests ? current : worst
557+
);
558+
559+
return {
560+
totalExceededDays: userExceededDetails.length,
561+
totalExceededRequests,
562+
averageExceededPerDay: totalExceededRequests / userExceededDetails.length,
563+
worstDay: {
564+
date: worstDay.date,
565+
exceededRequests: worstDay.exceededRequests,
566+
totalRequests: worstDay.totalRequestsOnDay,
567+
},
568+
};
507569
}

0 commit comments

Comments
 (0)