Skip to content

Commit

Permalink
Add copilot usage page
Browse files Browse the repository at this point in the history
  • Loading branch information
Starefossen committed Jan 16, 2025
1 parent d428998 commit 93eb6eb
Show file tree
Hide file tree
Showing 5 changed files with 221 additions and 1 deletion.
30 changes: 30 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,11 @@
"@octokit/auth-app": "^7.1.4",
"@octokit/rest": "^21.1.0",
"@opentelemetry/api": "^1.9.0",
"chart.js": "^4.4.7",
"next": "15.1.4",
"pino": "^9.6.0",
"react": "^19.0.0",
"react-chartjs-2": "^5.3.0",
"react-dom": "^19.0.0"
},
"devDependencies": {
Expand Down
81 changes: 81 additions & 0 deletions src/app/usage/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import React from "react";
import { getCopilotUsage } from "@/lib/github";
import UsageChart from "@/components/usage";

export default async function Usage() {
const { usage, error } = await getCopilotUsage("navikt");


return (
<main className="p-4 mx-4">
<h1 className="text-3xl font-bold mb-4">Copilot Usage Stats</h1>
{error ? (
<p className="text-red-500">Error fetching usage data: {error}</p>
) : (
<><div className="m-4">
{usage && usage.length > 0 && (
<div className="flex space-x-4 overflow-x-auto">
<div className="bg-white shadow-md rounded-lg p-4 w-64 border border-gray-300 text-center">
<p className="text-2xl font-bold">{usage[usage.length - 1].total_active_users || 0}</p>
<p><strong>Active Users</strong></p>
</div>
<div className="bg-white shadow-md rounded-lg p-4 w-64 border border-gray-300 text-center">
<p className="text-2xl font-bold">{usage[usage.length - 1].total_active_chat_users || 0}</p>
<p><strong>Active Chat Users</strong></p>
</div>
<div className="bg-white shadow-md rounded-lg p-4 w-64 border border-gray-300 text-center">
<p className="text-2xl font-bold">
{(() => {
const languageCount: Record<string, number> = {};

usage[usage.length - 1].breakdown?.forEach((breakdownItem) => {
if (breakdownItem.language) {
if (!languageCount[breakdownItem.language]) {
languageCount[breakdownItem.language] = 0;
}
languageCount[breakdownItem.language] += breakdownItem.active_users || 0;
}
});

const topLanguage = Object.entries(languageCount).reduce(
(topLang, [language, users]) => users > topLang[1] ? [language, users] : topLang,
['', 0]
)[0];

return topLanguage || 'N/A';
})()}
</p>
<p><strong>Top Language</strong></p>
</div>
<div className="bg-white shadow-md rounded-lg p-4 w-64 border border-gray-300 text-center">
<p className="text-2xl font-bold">
{(() => {
const editorCount: Record<string, number> = {};

usage[usage.length - 1].breakdown?.forEach((breakdownItem) => {
if (breakdownItem.editor) {
if (!editorCount[breakdownItem.editor]) {
editorCount[breakdownItem.editor] = 0;
}
editorCount[breakdownItem.editor] += breakdownItem.active_users || 0;
}
});

const topEditor = Object.entries(editorCount).reduce(
(topEd, [editor, users]) => users > topEd[1] ? [editor, users] : topEd,
['', 0]
)[0];

return topEditor || 'N/A';
})()}
</p>
<p><strong>Top Editor</strong></p>
</div>
</div>
)}
</div><UsageChart usage={usage!} /></>
)
}
</main >
);
};
73 changes: 73 additions & 0 deletions src/components/usage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
'use client';

import { CopilotUsage } from "@/lib/github";
import React from "react";
import { Line } from 'react-chartjs-2';
import { Chart as ChartJS, CategoryScale, LinearScale, PointElement, LineElement, Title, Tooltip, Legend } from 'chart.js';

ChartJS.register(CategoryScale, LinearScale, PointElement, LineElement, Title, Tooltip, Legend);

interface UsageChartProps {
usage: CopilotUsage[];
}

const UsageChart: React.FC<UsageChartProps> = ({ usage }) => {
const labels = usage ? usage.map((dayUsage) => dayUsage.day) : [];
const data = {
labels,
datasets: [
{
label: 'Total Suggestions',
data: usage ? usage.map((dayUsage) => dayUsage.total_suggestions_count) : [],
borderColor: 'rgba(75, 192, 192, 1)',
backgroundColor: 'rgba(75, 192, 192, 0.2)',
},
{
label: 'Total Acceptances',
data: usage ? usage.map((dayUsage) => dayUsage.total_acceptances_count) : [],
borderColor: 'rgba(153, 102, 255, 1)',
backgroundColor: 'rgba(153, 102, 255, 0.2)',
},
{
label: 'Total Lines Suggested',
data: usage ? usage.map((dayUsage) => dayUsage.total_lines_suggested) : [],
borderColor: 'rgba(255, 159, 64, 1)',
backgroundColor: 'rgba(255, 159, 64, 0.2)',
},
{
label: 'Total Lines Accepted',
data: usage ? usage.map((dayUsage) => dayUsage.total_lines_accepted) : [],
borderColor: 'rgba(54, 162, 235, 1)',
backgroundColor: 'rgba(54, 162, 235, 0.2)',
},
{
label: 'Total Active Users',
data: usage ? usage.map((dayUsage) => dayUsage.total_active_users) : [],
borderColor: 'rgba(255, 206, 86, 1)',
backgroundColor: 'rgba(255, 206, 86, 0.2)',
},
{
label: 'Total Chat Acceptances',
data: usage ? usage.map((dayUsage) => dayUsage.total_chat_acceptances) : [],
borderColor: 'rgba(75, 192, 192, 1)',
backgroundColor: 'rgba(75, 192, 192, 0.2)',
},
{
label: 'Total Chat Turns',
data: usage ? usage.map((dayUsage) => dayUsage.total_chat_turns) : [],
borderColor: 'rgba(153, 102, 255, 1)',
backgroundColor: 'rgba(153, 102, 255, 0.2)',
},
{
label: 'Total Active Chat Users',
data: usage ? usage.map((dayUsage) => dayUsage.total_active_chat_users) : [],
borderColor: 'rgba(54, 162, 235, 1)',
backgroundColor: 'rgba(54, 162, 235, 0.2)',
},
],
};

return <Line data={data} />;
};

export default UsageChart;
36 changes: 35 additions & 1 deletion src/lib/github.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,4 +159,38 @@ export async function unassignUserFromCopilot(org: string, username: string): Pr
} catch (error) {
return { seats_cancelled: null, error: (error instanceof Error ? error.message : String(error)) };
}
}
}

export type CopilotUsage = {
day: string;
total_suggestions_count?: number | undefined;
total_acceptances_count?: number | undefined;
total_lines_suggested?: number | undefined;
total_lines_accepted?: number | undefined;
total_active_users?: number | undefined;
total_chat_acceptances?: number | undefined;
total_chat_turns?: number | undefined;
total_active_chat_users?: number | undefined;
breakdown: Array<{
language?: string | undefined;
editor?: string | undefined;
suggestions_count?: number | undefined;
acceptances_count?: number | undefined;
lines_suggested?: number | undefined;
lines_accepted?: number | undefined;
active_users?: number | undefined;
[key: string]: unknown;
}> | null
}

export async function getCopilotUsage(org: string): Promise<{ usage: CopilotUsage[] | null, error: string | null }> {
try {
const { data } = await octokit.request('GET /orgs/{org}/copilot/usage', {
org
});

return { usage: data, error: null };
} catch (error) {
return { usage: null, error: (error instanceof Error ? error.message : String(error)) };
}
}

0 comments on commit 93eb6eb

Please sign in to comment.