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
2 changes: 1 addition & 1 deletion frontend/src/api/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ export interface LeaderboardDetail {
score: number;
user_name: string;
submission_id: number;
line_count?: number;
}>
>;
}
Expand All @@ -43,7 +44,6 @@ export interface CodesResponse {
results: Array<{
submission_id: number;
code: string;
line_count?: number;
}>;
}

Expand Down
2 changes: 0 additions & 2 deletions frontend/src/pages/leaderboard/Leaderboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -417,7 +417,6 @@ function LeaderboardWithSidebar() {
navigationItems,
navigationIndex,
codes,
lineCounts,
isOpen,
isLoadingCodes,
navigate,
Expand Down Expand Up @@ -456,7 +455,6 @@ function LeaderboardWithSidebar() {
navigationItems={navigationItems}
navigationIndex={navigationIndex}
codes={codes}
lineCounts={lineCounts}
isLoadingCodes={isLoadingCodes}
onClose={close}
onNavigate={navigate}
Expand Down
29 changes: 25 additions & 4 deletions frontend/src/pages/leaderboard/components/RankingLists.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ interface RankingItem {
submission_id: number;
submission_count?: number;
submission_time?: string;
line_count?: number;
}

interface RankingsListProps {
Expand Down Expand Up @@ -84,6 +85,10 @@ const styles: Record<string, SxProps<Theme>> = {
fontFamily: "monospace",
color: "text.secondary",
},
lineCount: {
fontFamily: "monospace",
color: "text.secondary",
},
};

export default function RankingsList({
Expand Down Expand Up @@ -160,6 +165,13 @@ export default function RankingsList({
}
const isExpanded = expanded[field] ?? true;
const visibleItems = isExpanded ? items : items.slice(0, 3);
const showLineCount = expired && items.some(
(item) => typeof item.line_count === "number",
);
const scoreSize = isAdmin ? 2 : showLineCount ? 2 : 3;
const deltaSize = isAdmin ? 1 : showLineCount ? 2 : 3;
const fileSize = 3;
const adminSubmissionCountSize = showLineCount ? 1 : 2;
return (
<Box key={field}>
<Box sx={styles.header}>
Expand Down Expand Up @@ -189,18 +201,18 @@ export default function RankingsList({
{item.user_name} {getMedalIcon(item.rank)}
</Typography>
</Grid>
<Grid size={isAdmin ? 2 : 3}>
<Grid size={scoreSize}>
<Typography sx={styles.score}>
{formatMicroseconds(item.score)}
</Typography>
</Grid>
<Grid size={isAdmin ? 1 : 3}>
<Grid size={deltaSize}>
<Typography sx={styles.delta}>
{item.prev_score > 0 &&
`+${formatMicroseconds(item.prev_score)}`}
</Typography>
</Grid>
<Grid size={isAdmin ? 2 : 3}>
<Grid size={isAdmin ? 2 : fileSize}>
<Typography
sx={{
overflow: "hidden",
Expand Down Expand Up @@ -234,8 +246,17 @@ export default function RankingsList({
)}
</Typography>
</Grid>
{showLineCount && (
<Grid size={isAdmin ? 1 : 2}>
<Typography sx={styles.lineCount}>
{typeof item.line_count === "number"
? `LOC: ${item.line_count.toLocaleString()}`
: ""}
</Typography>
</Grid>
)}
{isAdmin && (
<Grid size={2}>
<Grid size={adminSubmissionCountSize}>
<Typography sx={styles.submissionId}>
Subs: {item.submission_count}
</Typography>
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ interface SubmissionCodeSidebarProps {
navigationItems: NavigationItem[];
navigationIndex: number;
codes: Map<number, string>;
lineCounts?: Map<number, number>;
isLoadingCodes?: boolean;
onClose: () => void;
onNavigate: (newIndex: number, item: NavigationItem) => void;
Expand All @@ -35,7 +34,6 @@ export default function SubmissionCodeSidebar({
navigationItems,
navigationIndex,
codes,
lineCounts,
isLoadingCodes = false,
onClose,
onNavigate,
Expand Down Expand Up @@ -93,9 +91,6 @@ export default function SubmissionCodeSidebar({
}, [onWidthChange]);

const drawerWidth = isMobile ? "100vw" : width;
const lineCount = selectedSubmission
? lineCounts?.get(selectedSubmission.submissionId)
: undefined;

return (
<Drawer
Expand Down Expand Up @@ -199,8 +194,7 @@ export default function SubmissionCodeSidebar({
{/* Metadata */}
{(selectedSubmission.timestamp ||
selectedSubmission.originalTimestamp ||
selectedSubmission.score !== undefined ||
lineCount !== undefined) && (
selectedSubmission.score !== undefined) && (
<Box
sx={{
px: 2,
Expand Down Expand Up @@ -262,20 +256,6 @@ export default function SubmissionCodeSidebar({
</Typography>
</Box>
)}
{lineCount !== undefined && (
<Box>
<Typography
variant="caption"
color="text.secondary"
display="block"
>
LOC
</Typography>
<Typography variant="body2" sx={{ fontFamily: "monospace" }}>
{lineCount.toLocaleString()}
</Typography>
</Box>
)}
</Box>
{selectedSubmission.fileName && (
<Typography
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ interface SubmissionSidebarStateType {
navigationItems: NavigationItem[];
navigationIndex: number;
codes: Map<number, string>;
lineCounts: Map<number, number>;
isOpen: boolean;
isLoadingCodes: boolean;
navigate: (newIndex: number, item: NavigationItem) => void;
Expand All @@ -41,7 +40,6 @@ export function SubmissionSidebarProvider({
const [navigationItems, setNavigationItems] = useState<NavigationItem[]>([]);
const [navigationIndex, setNavigationIndex] = useState(0);
const [codes, setCodes] = useState<Map<number, string>>(new Map());
const [lineCounts, setLineCounts] = useState<Map<number, number>>(new Map());
const [isLoadingCodes, setIsLoadingCodes] = useState(false);
const codesRef = useRef(codes);
codesRef.current = codes;
Expand Down Expand Up @@ -74,15 +72,6 @@ export function SubmissionSidebarProvider({
}
return next;
});
setLineCounts((prev) => {
const next = new Map(prev);
for (const item of response?.results ?? []) {
if (typeof item.line_count === "number") {
next.set(item.submission_id, item.line_count);
}
}
return next;
});
})
.catch((err) => {
console.warn("[SubmissionSidebar] Failed to fetch codes:", err);
Expand Down Expand Up @@ -124,7 +113,6 @@ export function SubmissionSidebarProvider({
navigationItems,
navigationIndex,
codes,
lineCounts,
isOpen: !!selectedSubmission,
isLoadingCodes,
navigate,
Expand Down
86 changes: 86 additions & 0 deletions kernelboard/api/leaderboard.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import logging
import time
from datetime import datetime, timezone
from http import HTTPStatus
from typing import Any, List

Expand Down Expand Up @@ -45,6 +46,16 @@ def leaderboard(leaderboard_id: int):
# 3. Data transformation
transform_start = time.perf_counter()
res = to_api_leaderboard_item(data)
try:
if _is_leaderboard_concluded(data["leaderboard"]["deadline"]):
_attach_line_counts(conn, leaderboard_id, res)
except Exception:
conn.rollback()
logger.warning(
"Failed to attach line counts for leaderboard_id=%s",
leaderboard_id,
exc_info=True,
)
transform_time = (time.perf_counter() - transform_start) * 1000

total_time = (time.perf_counter() - total_start) * 1000
Expand Down Expand Up @@ -122,6 +133,81 @@ def to_api_leaderboard_item(data: dict[str, Any]):
}


def _is_leaderboard_concluded(deadline) -> bool:
if deadline is None:
return False
if isinstance(deadline, str):
try:
deadline = datetime.fromisoformat(deadline)
except ValueError:
return False

now = datetime.now(deadline.tzinfo or timezone.utc)
if deadline.tzinfo is None:
now = now.replace(tzinfo=None)

return deadline < now


def _attach_line_counts(conn, leaderboard_id: int, leaderboard: dict[str, Any]) -> None:
submission_ids = {
entry["submission_id"]
for ranking in leaderboard["rankings"].values()
for entry in ranking
if entry.get("submission_id") is not None
}
if not submission_ids:
return

with conn.cursor() as cur:
cur.execute(
"""
SELECT s.id, cf.code
FROM leaderboard.submission s
JOIN leaderboard.code_files cf ON cf.id = s.code_id
WHERE s.leaderboard_id = %s
AND s.id = ANY(%s::int[])
""",
(leaderboard_id, list(submission_ids)),
)
rows = cur.fetchall()

line_counts = {
submission_id: _count_lines_of_code(_decode_code_text(code))
for submission_id, code in rows
}

for ranking in leaderboard["rankings"].values():
for entry in ranking:
line_count = line_counts.get(entry["submission_id"])
if line_count is not None:
entry["line_count"] = line_count


def _count_lines_of_code(code_text: str) -> int:
if not code_text:
return 0
return len(code_text.splitlines())


def _decode_code_text(code_text) -> str:
if isinstance(code_text, memoryview):
return code_text.tobytes().decode("utf-8", errors="replace")
if isinstance(code_text, bytes):
return code_text.decode("utf-8", errors="replace")
if isinstance(code_text, str):
if code_text.startswith("\\x"):
try:
return bytes.fromhex(code_text[2:]).decode("utf-8", errors="replace")
except Exception:
logger.info("failed to decode hex code text", exc_info=True)
return code_text
return code_text

logger.info("unexpected code text type: %s", type(code_text))
return str(code_text)


def _get_query():
query = """
WITH
Expand Down
Loading
Loading