Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions frontend/src/api/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export interface LeaderboardDetail {
description: string;
name: string;
reference: string;
starter: string;
benchmarks: Record<string, unknown>[];
gpu_types: string[];
rankings: Record<
Expand Down
46 changes: 26 additions & 20 deletions frontend/src/pages/leaderboard/Leaderboard.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ const mockDeadline = "2025-06-29T17:00:00-07:00";
const mockExpiredDeadline = "2024-01-29T12:00:00-02:00";
const mockDescription = "Implement a 2D the given specifications";
const mockReference = "import torch";
const mockStarter = "def custom_kernel(data):\n return data";
const mockName = "test-game";

describe("Leaderboard", () => {
Expand All @@ -44,12 +45,13 @@ describe("Leaderboard", () => {

// -------------------- Basic rendering --------------------

it("renders name, description, gpu types; rankings visible on Rankings tab; reference visible after switching to Reference tab", () => {
it("renders name, description, gpu types; rankings visible on Rankings tab; starter code visible after switching to Starter Code tab", () => {
const mockData = {
deadline: mockDeadline,
description: mockDescription,
name: mockName,
reference: mockReference,
starter: mockStarter,
gpu_types: ["T1", "T2"],
rankings: {
T1: [
Expand Down Expand Up @@ -90,7 +92,7 @@ describe("Leaderboard", () => {

// Tabs exist
const rankingsTab = screen.getByRole("tab", { name: /Rankings/i });
const referenceTab = screen.getByRole("tab", { name: /Reference/i });
const referenceTab = screen.getByRole("tab", { name: /Starter Code/i });
const submissionTab = screen.getByRole("tab", { name: /Submission/i });
expect(rankingsTab).toBeInTheDocument();
expect(referenceTab).toBeInTheDocument();
Expand All @@ -100,11 +102,10 @@ describe("Leaderboard", () => {
expect(screen.getByText(/user1/)).toBeInTheDocument();
expect(screen.getByText(/user2/)).toBeInTheDocument();

// Switch to Reference tab before asserting its content
// Switch to Starter Code tab before asserting its content
fireEvent.click(referenceTab);
expect(screen.getByText(/Reference Implementation/i)).toBeInTheDocument();
expect(screen.getByText(/import/)).toBeInTheDocument();
expect(screen.getByText(/torch/)).toBeInTheDocument();
expect(screen.getByText(/Starter Submission/i)).toBeInTheDocument();
expect(screen.getByText(/custom_kernel/)).toBeInTheDocument();
});

it("shows loading state", () => {
Expand Down Expand Up @@ -276,15 +277,16 @@ describe("Leaderboard", () => {
expect(screen.queryAllByTestId("ranking-0-row")).toHaveLength(3);
});

// -------------------- Reference codeblock --------------------
// -------------------- Starter codeblock --------------------

it("show reference codeblock (after switching to Reference tab)", () => {
it("shows starter codeblock (after switching to Starter Code tab)", () => {
const mockData = {
name: "test-code",
description: "",
deadline: "",
gpu_types: ["T1"],
reference: mockReference,
starter: mockStarter,
rankings: { T1: [] },
};

Expand All @@ -298,23 +300,23 @@ describe("Leaderboard", () => {

renderWithRouter(<Leaderboard />);

// Must switch to Reference tab first
fireEvent.click(screen.getByRole("tab", { name: /Reference/i }));
// Must switch to Starter Code tab first
fireEvent.click(screen.getByRole("tab", { name: /Starter Code/i }));

// Reference codeblock should be visible
expect(screen.getByText(/Reference Implementation/i)).toBeInTheDocument();
expect(screen.getByText(/import/)).toBeInTheDocument();
expect(screen.getByText(/torch/)).toBeInTheDocument();
// Starter codeblock should be visible
expect(screen.getByText(/Starter Submission/i)).toBeInTheDocument();
expect(screen.getByText(/custom_kernel/)).toBeInTheDocument();
});

// -------------------- Tabs behavior (switching) --------------------

it("starts on Rankings tab by default and can switch to Reference and back", () => {
it("starts on Rankings tab by default and can switch to Starter Code and back", () => {
const mockData = {
deadline: mockDeadline,
description: mockDescription,
name: mockName,
reference: mockReference,
starter: mockStarter,
gpu_types: ["T1"],
rankings: {
T1: [
Expand Down Expand Up @@ -342,11 +344,10 @@ describe("Leaderboard", () => {
// Default selected tab should be Rankings (content visible)
expect(screen.getByText(/user1/)).toBeInTheDocument();

// Switch to Reference tab
fireEvent.click(screen.getByRole("tab", { name: /Reference/i }));
expect(screen.getByText(/Reference Implementation/i)).toBeInTheDocument();
expect(screen.getByText(/import/)).toBeInTheDocument();
expect(screen.getByText(/torch/)).toBeInTheDocument();
// Switch to Starter Code tab
fireEvent.click(screen.getByRole("tab", { name: /Starter Code/i }));
expect(screen.getByText(/Starter Submission/i)).toBeInTheDocument();
expect(screen.getByText(/custom_kernel/)).toBeInTheDocument();

// Switch back to Rankings tab
fireEvent.click(screen.getByRole("tab", { name: /Rankings/i }));
Expand All @@ -359,6 +360,7 @@ describe("Leaderboard", () => {
description: mockDescription,
name: mockName,
reference: mockReference,
starter: mockStarter,
gpu_types: ["T1"],
rankings: { T1: [] },
};
Expand Down Expand Up @@ -386,6 +388,7 @@ describe("Leaderboard", () => {
deadline: mockDeadline,
gpu_types: ["T1"],
reference: mockReference,
starter: mockStarter,
rankings: { T1: [] },
};

Expand Down Expand Up @@ -416,6 +419,7 @@ describe("Leaderboard", () => {
deadline: mockDeadline,
gpu_types: ["T1"],
reference: mockReference,
starter: mockStarter,
rankings: { T1: [] },
};

Expand Down Expand Up @@ -451,6 +455,7 @@ describe("Leaderboard", () => {
deadline: mockDeadline,
gpu_types: ["T1"],
reference: mockReference,
starter: mockStarter,
rankings: { T1: [] },
};

Expand Down Expand Up @@ -488,6 +493,7 @@ describe("Leaderboard", () => {
deadline: mockExpiredDeadline,
gpu_types: ["T1"],
reference: mockReference,
starter: mockStarter,
rankings: { T1: [] },
};

Expand Down
14 changes: 10 additions & 4 deletions frontend/src/pages/leaderboard/Leaderboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,7 @@ const LeaderboardContent = memo(function LeaderboardContent() {
allowScrollButtonsMobile
>
<Tab label="Rankings" value="rankings" {...a11yProps(0)} />
<Tab label="Reference" value="reference" {...a11yProps(1)} />
<Tab label="Starter Code" value="reference" {...a11yProps(1)} />
<Tab label="Submission" value="submission" {...a11yProps(2)} />
</Tabs>
</Box>
Expand Down Expand Up @@ -330,13 +330,19 @@ const LeaderboardContent = memo(function LeaderboardContent() {
</Box>
</TabPanel>

{/* Reference Implementation Tab */}
{/* Starter Code Tab */}
<TabPanel value={tab} tabKey="reference">
<Card>
<CardContent>
<CardTitle fontWeight="bold">Reference Implementation</CardTitle>
<CardTitle fontWeight="bold">Starter Submission</CardTitle>
<Box>
<CodeBlock code={data.reference} bordered />
{data.starter ? (
<CodeBlock code={data.starter} bordered />
) : (
<Typography variant="body2" color="text.secondary">
No starter submission is available for this problem.
</Typography>
)}
</Box>
</CardContent>
</Card>
Expand Down
12 changes: 12 additions & 0 deletions kernelboard/api/leaderboard.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,9 @@ def to_api_leaderboard_item(data: dict[str, Any]):
reference = leaderboard_data["reference"] or ""
reference = reference.replace("\\n", "\n")

starter = leaderboard_data["starter"] or ""
starter = starter.replace("\\n", "\n")

benchmarks = leaderboard_data.get("benchmarks") or []

gpu_types = leaderboard_data["gpu_types"]
Expand Down Expand Up @@ -113,6 +116,7 @@ def to_api_leaderboard_item(data: dict[str, Any]):
"gpu_types": gpu_types,
"description": description,
"reference": reference,
"starter": starter,
"benchmarks": benchmarks,
"rankings": rankings,
}
Expand All @@ -130,6 +134,13 @@ def _get_query():
task->>'lang' AS lang,
description AS description,
task->'files'->>'reference.py' AS reference,
(
SELECT code
FROM leaderboard.templates
WHERE leaderboard_id = %(leaderboard_id)s
ORDER BY CASE WHEN lang = 'Python' THEN 0 ELSE 1 END
LIMIT 1
) AS starter,
task->'benchmarks' AS benchmarks
FROM leaderboard.leaderboard
WHERE id = %(leaderboard_id)s
Expand Down Expand Up @@ -213,6 +224,7 @@ def _get_query():
'lang', lang,
'description', description,
'reference', reference,
'starter', starter,
'benchmarks', benchmarks,
'gpu_types', (SELECT jsonb_agg(gpu_type) FROM gpu_types)
) FROM leaderboard_info)
Expand Down
15 changes: 13 additions & 2 deletions kernelboard/leaderboard.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,14 @@ def leaderboard(leaderboard_id: int):
deadline,
task->>'lang' AS lang,
description AS description,
task->'files'->>'reference.py' AS reference
task->'files'->>'reference.py' AS reference,
(
SELECT code
FROM leaderboard.templates
WHERE leaderboard_id = %(leaderboard_id)s
ORDER BY CASE WHEN lang = 'Python' THEN 0 ELSE 1 END
LIMIT 1
) AS starter
FROM leaderboard.leaderboard
WHERE id = %(leaderboard_id)s
),
Expand Down Expand Up @@ -86,6 +93,7 @@ def leaderboard(leaderboard_id: int):
'lang', lang,
'description', description,
'reference', reference,
'starter', starter,
'gpu_types', (SELECT jsonb_agg(gpu_type) FROM gpu_types)
) FROM leaderboard_info)
) AS result FROM (SELECT gpu_type FROM gpu_types) g;
Expand Down Expand Up @@ -118,6 +126,9 @@ def leaderboard(leaderboard_id: int):
reference = leaderboard_data["reference"] or ""
reference = reference.replace("\\n", "\n")

starter = leaderboard_data["starter"] or ""
starter = starter.replace("\\n", "\n")

gpu_types = leaderboard_data["gpu_types"]
gpu_types.sort()

Expand Down Expand Up @@ -150,6 +161,6 @@ def leaderboard(leaderboard_id: int):
lang=lang,
gpu_types=gpu_types,
description=description,
reference=reference,
reference=starter,
rankings=rankings,
)
Loading