Skip to content

Commit 8801e5d

Browse files
authored
Merge pull request #33 from devops-actions/copilot/fix-32
Add "Power users" button with detailed analytics panel
2 parents 935a41c + b1617e0 commit 8801e5d

3 files changed

Lines changed: 416 additions & 1 deletion

File tree

src/App.tsx

Lines changed: 143 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,19 @@ import { Button } from "@/components/ui/button";
1010
import { Separator } from "@/components/ui/separator";
1111
import { ChartContainer, ChartTooltip, ChartTooltipContent } from "@/components/ui/chart";
1212
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
13+
import { Sheet, SheetContent, SheetHeader, SheetTitle, SheetTrigger } from "@/components/ui/sheet";
1314
import {
1415
AggregatedData,
1516
CopilotUsageData,
1617
ModelUsageSummary,
1718
DailyModelData,
19+
PowerUserSummary,
1820
aggregateDataByDay,
1921
parseCSV,
2022
getModelUsageSummary,
21-
getDailyModelData
23+
getDailyModelData,
24+
getPowerUsers,
25+
getPowerUserDailyData
2226
} from "@/lib/utils";
2327

2428
function App() {
@@ -27,6 +31,7 @@ function App() {
2731
const [uniqueModels, setUniqueModels] = useState<string[]>([]);
2832
const [modelSummary, setModelSummary] = useState<ModelUsageSummary[]>([]);
2933
const [dailyModelData, setDailyModelData] = useState<DailyModelData[]>([]);
34+
const [powerUserSummary, setPowerUserSummary] = useState<PowerUserSummary | null>(null);
3035
const [isDragging, setIsDragging] = useState(false);
3136
const fileInputRef = useRef<HTMLInputElement>(null);
3237

@@ -79,6 +84,10 @@ function App() {
7984
const dailyData = getDailyModelData(parsedData);
8085
setDailyModelData(dailyData);
8186

87+
// Get power users data
88+
const powerUsers = getPowerUsers(parsedData);
89+
setPowerUserSummary(powerUsers);
90+
8291
toast.success(`Loaded ${parsedData.length} records successfully`);
8392
} catch (error) {
8493
// Provide user-friendly error messages
@@ -115,6 +124,7 @@ function App() {
115124
setAggregatedData([]);
116125
setModelSummary([]);
117126
setDailyModelData([]);
127+
setPowerUserSummary(null);
118128
}
119129
};
120130

@@ -344,6 +354,138 @@ function App() {
344354
{uniqueModels.length}
345355
</span>
346356
</div>
357+
{powerUserSummary && (
358+
<Sheet>
359+
<SheetTrigger asChild>
360+
<Button variant="outline" className="flex items-center gap-2">
361+
<span className="text-sm">Power Users:</span>
362+
<span className="font-bold">{powerUserSummary.totalPowerUsers}</span>
363+
</Button>
364+
</SheetTrigger>
365+
<SheetContent className="w-[600px] sm:w-[800px] overflow-y-auto">
366+
<SheetHeader>
367+
<SheetTitle>Power Users Analysis</SheetTitle>
368+
</SheetHeader>
369+
<div className="mt-6 space-y-6">
370+
{/* Power User Summary */}
371+
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
372+
<Card className="p-4">
373+
<h3 className="text-md font-medium mb-3">Total Requests by Power Users</h3>
374+
<div className="space-y-2">
375+
<div className="flex justify-between items-center">
376+
<span className="text-sm text-muted-foreground">Total Requests:</span>
377+
<span className="font-bold">
378+
{powerUserSummary.totalPowerUserRequests.toLocaleString(undefined, {maximumFractionDigits: 2, minimumFractionDigits: 0})}
379+
</span>
380+
</div>
381+
<div className="flex justify-between items-center">
382+
<span className="text-sm text-muted-foreground">Power Users Count:</span>
383+
<span className="font-bold">{powerUserSummary.totalPowerUsers}</span>
384+
</div>
385+
</div>
386+
</Card>
387+
388+
<Card className="p-4">
389+
<h3 className="text-md font-medium mb-3">Requests per Model</h3>
390+
<div className="overflow-auto max-h-40">
391+
<Table>
392+
<TableHeader>
393+
<TableRow>
394+
<TableHead>Model</TableHead>
395+
<TableHead className="text-right">Requests</TableHead>
396+
</TableRow>
397+
</TableHeader>
398+
<TableBody>
399+
{powerUserSummary.powerUserModelSummary.map((item) => (
400+
<TableRow key={item.model}>
401+
<TableCell className="font-medium">{item.model}</TableCell>
402+
<TableCell className="text-right">{item.totalRequests.toLocaleString(undefined, {maximumFractionDigits: 2, minimumFractionDigits: 0})}</TableCell>
403+
</TableRow>
404+
))}
405+
</TableBody>
406+
</Table>
407+
</div>
408+
</Card>
409+
</div>
410+
411+
{/* Power User Activity Chart */}
412+
<Card className="p-4">
413+
<h3 className="text-md font-medium mb-3">Power User Activity Over Time</h3>
414+
<div className="h-[300px]">
415+
<ChartContainer
416+
config={{
417+
requests: { color: "#3b82f6" },
418+
}}
419+
className="h-full w-full"
420+
>
421+
<LineChart data={getPowerUserDailyData(powerUserSummary.powerUsers)}>
422+
<CartesianGrid strokeDasharray="3 3" opacity={0.2} />
423+
<XAxis
424+
dataKey="date"
425+
tick={{ fill: 'var(--foreground)' }}
426+
tickLine={{ stroke: 'var(--border)' }}
427+
/>
428+
<YAxis
429+
tick={{ fill: 'var(--foreground)' }}
430+
tickLine={{ stroke: 'var(--border)' }}
431+
/>
432+
<ChartTooltip
433+
content={({ active, payload, label }) => {
434+
if (active && payload && payload.length) {
435+
return (
436+
<div className="border rounded-lg bg-background shadow-lg p-3 text-xs">
437+
<div className="font-medium mb-2">{label}</div>
438+
<div className="flex items-center gap-2">
439+
<div className="w-2 h-2 rounded-full bg-[#3b82f6]" />
440+
<span>Requests: {Number(payload[0].value).toLocaleString(undefined, {maximumFractionDigits: 2, minimumFractionDigits: 0})}</span>
441+
</div>
442+
</div>
443+
);
444+
}
445+
return null;
446+
}}
447+
/>
448+
<Line
449+
type="monotone"
450+
dataKey="requests"
451+
name="Requests"
452+
stroke="#3b82f6"
453+
strokeWidth={2}
454+
activeDot={{ r: 6 }}
455+
/>
456+
</LineChart>
457+
</ChartContainer>
458+
</div>
459+
</Card>
460+
461+
{/* Individual Power Users List */}
462+
<Card className="p-4">
463+
<h3 className="text-md font-medium mb-3">Individual Power Users</h3>
464+
<div className="overflow-auto max-h-60">
465+
<Table>
466+
<TableHeader>
467+
<TableRow>
468+
<TableHead>User</TableHead>
469+
<TableHead className="text-right">Total Requests</TableHead>
470+
<TableHead className="text-right">Models Used</TableHead>
471+
</TableRow>
472+
</TableHeader>
473+
<TableBody>
474+
{powerUserSummary.powerUsers.map((user) => (
475+
<TableRow key={user.user}>
476+
<TableCell className="font-medium">{user.user}</TableCell>
477+
<TableCell className="text-right">{user.totalRequests.toLocaleString(undefined, {maximumFractionDigits: 2, minimumFractionDigits: 0})}</TableCell>
478+
<TableCell className="text-right">{Object.keys(user.requestsByModel).length}</TableCell>
479+
</TableRow>
480+
))}
481+
</TableBody>
482+
</Table>
483+
</div>
484+
</Card>
485+
</div>
486+
</SheetContent>
487+
</Sheet>
488+
)}
347489
</div>
348490
</div>
349491
</Card>

src/lib/utils.ts

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,3 +213,102 @@ export function getDailyModelData(data: CopilotUsageData[]): DailyModelData[] {
213213
return a.model.localeCompare(b.model);
214214
});
215215
}
216+
217+
// Power user interfaces and functions
218+
export interface PowerUserData {
219+
user: string;
220+
totalRequests: number;
221+
requestsByModel: Record<string, number>;
222+
dailyActivity: Array<{
223+
date: string;
224+
requests: number;
225+
}>;
226+
}
227+
228+
export interface PowerUserSummary {
229+
powerUsers: PowerUserData[];
230+
totalPowerUsers: number;
231+
totalPowerUserRequests: number;
232+
powerUserModelSummary: ModelUsageSummary[];
233+
}
234+
235+
// Define power user threshold - users with more than 10 requests
236+
export const POWER_USER_THRESHOLD = 10;
237+
238+
export function getPowerUsers(data: CopilotUsageData[]): PowerUserSummary {
239+
// First, aggregate total requests per user
240+
const userTotals: Record<string, number> = {};
241+
data.forEach(item => {
242+
userTotals[item.user] = (userTotals[item.user] || 0) + item.requestsUsed;
243+
});
244+
245+
// Identify power users (users exceeding threshold)
246+
const powerUserNames = Object.keys(userTotals).filter(
247+
user => userTotals[user] > POWER_USER_THRESHOLD
248+
);
249+
250+
// Filter data to only power users
251+
const powerUserData = data.filter(item => powerUserNames.includes(item.user));
252+
253+
// Create detailed power user objects
254+
const powerUsers: PowerUserData[] = powerUserNames.map(userName => {
255+
const userRequests = powerUserData.filter(item => item.user === userName);
256+
257+
// Aggregate by model
258+
const requestsByModel: Record<string, number> = {};
259+
userRequests.forEach(item => {
260+
requestsByModel[item.model] = (requestsByModel[item.model] || 0) + item.requestsUsed;
261+
});
262+
263+
// Aggregate daily activity
264+
const dailyActivity: Record<string, number> = {};
265+
userRequests.forEach(item => {
266+
const date = item.timestamp.toISOString().split('T')[0];
267+
dailyActivity[date] = (dailyActivity[date] || 0) + item.requestsUsed;
268+
});
269+
270+
const dailyActivityArray = Object.entries(dailyActivity)
271+
.map(([date, requests]) => ({ date, requests }))
272+
.sort((a, b) => a.date.localeCompare(b.date));
273+
274+
return {
275+
user: userName,
276+
totalRequests: userTotals[userName],
277+
requestsByModel,
278+
dailyActivity: dailyActivityArray
279+
};
280+
});
281+
282+
// Sort power users by total requests (descending)
283+
powerUsers.sort((a, b) => b.totalRequests - a.totalRequests);
284+
285+
// Calculate total power user requests
286+
const totalPowerUserRequests = powerUsers.reduce((sum, user) => sum + user.totalRequests, 0);
287+
288+
// Get model usage summary for power users
289+
const powerUserModelSummary = getModelUsageSummary(powerUserData);
290+
291+
return {
292+
powerUsers,
293+
totalPowerUsers: powerUsers.length,
294+
totalPowerUserRequests,
295+
powerUserModelSummary
296+
};
297+
}
298+
299+
export function getPowerUserDailyData(powerUsers: PowerUserData[]): Array<{
300+
date: string;
301+
requests: number;
302+
}> {
303+
const dailyTotals: Record<string, number> = {};
304+
305+
powerUsers.forEach(user => {
306+
user.dailyActivity.forEach(day => {
307+
dailyTotals[day.date] = (dailyTotals[day.date] || 0) + day.requests;
308+
});
309+
});
310+
311+
return Object.entries(dailyTotals)
312+
.map(([date, requests]) => ({ date, requests }))
313+
.sort((a, b) => a.date.localeCompare(b.date));
314+
}

0 commit comments

Comments
 (0)