Skip to content

Commit acbcee1

Browse files
authored
Move Performance Trend to Rankings (#174)
* push * lint * lint again
1 parent 077dc1c commit acbcee1

File tree

5 files changed

+286
-473
lines changed

5 files changed

+286
-473
lines changed

frontend/src/api/api.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -259,7 +259,7 @@ export async function fetchEvents(): Promise<DiscordEvent[]> {
259259
return r.data;
260260
}
261261

262-
export interface AiTrendDataPoint {
262+
export interface CustomTrendDataPoint {
263263
score: string;
264264
submission_id: number;
265265
submission_time: string;
@@ -269,23 +269,23 @@ export interface AiTrendDataPoint {
269269
model?: string;
270270
}
271271

272-
export interface AiTrendTimeSeries {
272+
export interface CustomTrendTimeSeries {
273273
[gpuType: string]: {
274-
[model: string]: AiTrendDataPoint[];
274+
[model: string]: CustomTrendDataPoint[];
275275
};
276276
}
277277

278-
export interface AiTrendResponse {
278+
export interface CustomTrendResponse {
279279
leaderboard_id: number;
280-
time_series: AiTrendTimeSeries;
280+
time_series: CustomTrendTimeSeries;
281281
}
282282

283-
export async function fetchAiTrend(leaderboardId: string): Promise<AiTrendResponse> {
284-
const res = await fetch(`/api/leaderboard/${leaderboardId}/ai_trend`);
283+
export async function fetchCustomTrend(leaderboardId: string): Promise<CustomTrendResponse> {
284+
const res = await fetch(`/api/leaderboard/${leaderboardId}/custom_trend`);
285285
if (!res.ok) {
286286
const json = await res.json();
287287
const message = json?.message || "Unknown error";
288-
throw new APIError(`Failed to fetch AI trend: ${message}`, res.status);
288+
throw new APIError(`Failed to fetch custom trend: ${message}`, res.status);
289289
}
290290
const r = await res.json();
291291
return r.data;
@@ -294,7 +294,7 @@ export async function fetchAiTrend(leaderboardId: string): Promise<AiTrendRespon
294294
export interface UserTrendResponse {
295295
leaderboard_id: number;
296296
user_ids: string[];
297-
time_series: AiTrendTimeSeries;
297+
time_series: CustomTrendTimeSeries;
298298
}
299299

300300
export async function fetchUserTrend(

frontend/src/pages/leaderboard/Leaderboard.tsx

Lines changed: 46 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import {
99
Typography,
1010
} from "@mui/material";
1111
import Grid from "@mui/material/Grid";
12-
import { useEffect, useState, useMemo } from "react";
12+
import { useEffect, useState } from "react";
1313
import { fetchLeaderBoard, searchUsers } from "../../api/api";
1414
import { fetcherApiCallback } from "../../lib/hooks/useApi";
1515
import { isExpired, toDateUtc } from "../../lib/date/utils";
@@ -24,14 +24,13 @@ import { SubmissionMode } from "../../lib/types/mode";
2424
import { useAuthStore } from "../../lib/store/authStore";
2525
import SubmissionHistorySection from "./components/submission-history/SubmissionHistorySection";
2626
import LeaderboardSubmit from "./components/LeaderboardSubmit";
27-
import AiTrendChart from "./components/AiTrendChart";
2827
import UserTrendChart from "./components/UserTrendChart";
2928
export const CardTitle = styled(Typography)(() => ({
3029
fontSize: "1.5rem",
3130
fontWeight: "bold",
3231
}));
3332

34-
type TabKey = "rankings" | "reference" | "submission" | "ai_trend";
33+
type TabKey = "rankings" | "reference" | "submission";
3534

3635
// Tab accessibility props
3736
function a11yProps(index: number) {
@@ -70,27 +69,16 @@ export default function Leaderboard() {
7069
const isAuthed = !!(me && me.authenticated);
7170
const userId = me?.user?.identity ?? null;
7271

73-
// State for top user (strongest submission) and default GPU type
74-
const [defaultUser, setDefaultUser] = useState<{
75-
userId: string;
76-
username: string;
77-
} | null>(null);
72+
// State for top users (strongest submissions) and default GPU type
73+
const [defaultUsers, setDefaultUsers] = useState<
74+
Array<{ userId: string; username: string }>
75+
>([]);
7876
const [defaultGpuType, setDefaultGpuType] = useState<string | null>(null);
7977

8078
// Sync tab state with query parameter
8179
const [searchParams, setSearchParams] = useSearchParams();
8280

83-
// Check if AI Trend should be shown
84-
const showAiTrend = searchParams.get("showAiTrend") === "true";
85-
86-
// Build tab keys dynamically based on showAiTrend
87-
const TAB_KEYS: TabKey[] = useMemo(() => {
88-
const keys: TabKey[] = ["rankings", "reference", "submission"];
89-
if (showAiTrend) {
90-
keys.push("ai_trend");
91-
}
92-
return keys;
93-
}, [showAiTrend]);
81+
const TAB_KEYS: TabKey[] = ["rankings", "reference", "submission"];
9482

9583
const initialTabFromUrl = ((): TabKey => {
9684
const t = (searchParams.get("tab") || "").toLowerCase();
@@ -116,10 +104,10 @@ export default function Leaderboard() {
116104
if (id) call(id);
117105
}, [id, call]);
118106

119-
// Fetch top user (strongest submission) when rankings are available
107+
// Fetch top users (strongest submissions) when rankings are available
120108
// Select from the GPU with the most unique users
121109
useEffect(() => {
122-
const findTopUser = async () => {
110+
const findTopUsers = async () => {
123111
if (!id || !data?.rankings) return;
124112

125113
const gpuTypes = Object.keys(data.rankings);
@@ -139,26 +127,35 @@ export default function Leaderboard() {
139127
const mostActiveGpuRankings = data.rankings[mostActiveGpu];
140128
if (!mostActiveGpuRankings || mostActiveGpuRankings.length === 0) return;
141129

142-
// The first item is the top user (sorted by score ascending)
143-
const topUserName = mostActiveGpuRankings[0].user_name;
144-
if (!topUserName) return;
130+
// Get top 5 users (sorted by score ascending)
131+
const topUserNames = mostActiveGpuRankings
132+
.slice(0, 5)
133+
.map((r) => r.user_name)
134+
.filter(Boolean);
135+
136+
if (topUserNames.length === 0) return;
145137

146138
try {
147-
// Search for the user by username to get their user_id
148-
const result = await searchUsers(id, topUserName, 1);
149-
if (result.users && result.users.length > 0) {
150-
const foundUser = result.users[0];
151-
setDefaultUser({
152-
userId: foundUser.user_id,
153-
username: foundUser.username,
154-
});
155-
}
139+
// Search for each user by username to get their user_id
140+
const userPromises = topUserNames.map((userName: string) =>
141+
searchUsers(id, userName, 1)
142+
);
143+
const results = await Promise.all(userPromises);
144+
145+
const foundUsers = results
146+
.filter((result) => result.users && result.users.length > 0)
147+
.map((result) => ({
148+
userId: result.users[0].user_id,
149+
username: result.users[0].username,
150+
}));
151+
152+
setDefaultUsers(foundUsers);
156153
} catch (err) {
157-
console.error("Failed to fetch top user:", err);
154+
console.error("Failed to fetch top users:", err);
158155
}
159156
};
160157

161-
findTopUser();
158+
findTopUsers();
162159
}, [id, data?.rankings]);
163160

164161
if (loading) return <Loading />;
@@ -213,21 +210,27 @@ export default function Leaderboard() {
213210
<Tab label="Rankings" value="rankings" {...a11yProps(0)} />
214211
<Tab label="Reference" value="reference" {...a11yProps(1)} />
215212
<Tab label="Submission" value="submission" {...a11yProps(2)} />
216-
{showAiTrend && (
217-
<Tab label="AI Trend" value="ai_trend" {...a11yProps(3)} />
218-
)}
219213
</Tabs>
220214
</Box>
221215

222216
{/* Ranking Tab */}
223217
<TabPanel value={tab} tabKey="rankings">
224218
<Box>
225219
{Object.entries(data.rankings).length > 0 ? (
226-
<RankingsList
227-
rankings={data.rankings}
228-
leaderboardId={id}
229-
deadline={data.deadline}
230-
/>
220+
<>
221+
<RankingsList
222+
rankings={data.rankings}
223+
leaderboardId={id}
224+
deadline={data.deadline}
225+
/>
226+
<Box sx={{ my: 4, borderTop: 1, borderColor: "divider" }} />
227+
<Card>
228+
<CardContent>
229+
<CardTitle fontWeight="bold">Performance Trend</CardTitle>
230+
<UserTrendChart leaderboardId={id!} defaultUsers={defaultUsers} defaultGpuType={defaultGpuType} rankings={data.rankings} />
231+
</CardContent>
232+
</Card>
233+
</>
231234
) : (
232235
<Box display="flex" flexDirection="column" alignItems="center">
233236
<Typography variant="h6" fontWeight="bold">
@@ -305,25 +308,6 @@ export default function Leaderboard() {
305308
)}
306309
</TabPanel>
307310

308-
{/* AI Trend Tab - only shown when showAiTrend=true */}
309-
{showAiTrend && (
310-
<TabPanel value={tab} tabKey="ai_trend">
311-
<Card>
312-
<CardContent>
313-
<CardTitle fontWeight="bold">
314-
AI Model Performance Trend
315-
</CardTitle>
316-
<AiTrendChart leaderboardId={id!} rankings={data.rankings} />
317-
</CardContent>
318-
</Card>
319-
<Card sx={{ mt: 2 }}>
320-
<CardContent>
321-
<CardTitle fontWeight="bold">User Performance Trend</CardTitle>
322-
<UserTrendChart leaderboardId={id!} defaultUser={defaultUser} defaultGpuType={defaultGpuType} />
323-
</CardContent>
324-
</Card>
325-
</TabPanel>
326-
)}
327311
</Box>
328312
</ConstrainedContainer>
329313
);

0 commit comments

Comments
 (0)