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
6 changes: 6 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,12 @@ jobs:
- name: Install frontend dependencies
run: cd frontend && npm ci

- name: Lint Python
run: ruff check kernelboard/ tests/

- name: Lint frontend
run: cd frontend && npm run lint

- name: Run Python tests
run: pytest --tb=short
continue-on-error: true
Expand Down
36 changes: 36 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,42 @@ Look for the `hackathons` array around line 87. To add a hackathon:
Upcoming lectures are pulled live from Discord's scheduled events API (5-minute cache).
Requires `DISCORD_BOT_TOKEN` and `DISCORD_GUILD_ID` environment variables.

## Database Access

The production PostgreSQL database is hosted on Heroku under the `discord-cluster-manager` app:
- **Heroku Dashboard:** https://dashboard.heroku.com/apps/discord-cluster-manager
- **Connection:** Use `heroku pg:psql -a discord-cluster-manager` to connect interactively
- **Credentials:** Use `heroku pg:credentials:url -a discord-cluster-manager` to get the connection string
- **Schema:** All tables live under the `leaderboard` schema (e.g., `leaderboard.runs`, `leaderboard.submission`, `leaderboard.leaderboard`, `leaderboard.user_info`, `leaderboard.code_files`, `leaderboard.gpu_type`, `leaderboard.submission_job_status`)

### Key Tables
- `leaderboard.leaderboard` - Competition definitions (name, deadline, task JSONB)
- `leaderboard.submission` - User submissions linked to code files
- `leaderboard.runs` - Individual run results with scores (lower is better), GPU type, pass/fail
- `leaderboard.user_info` - User accounts (Discord/Google/GitHub OAuth)
- `leaderboard.gpu_type` - GPU types supported per leaderboard
- `leaderboard.code_files` - Submitted code with SHA256 hash
- `leaderboard.submission_job_status` - Job tracking (pending/running/succeeded/failed/timed_out)

### Ranking Logic
Rankings are computed via SQL window functions:
1. Best run per user per GPU type (lowest score wins, must be `passed=true`, `secret=false`, `score IS NOT NULL`)
2. Global rank via `RANK() OVER (PARTITION BY leaderboard_id, runner ORDER BY score ASC)`
3. GPU priority order: B200 > H100 > MI300 > A100 > L4 > T4

## Linting

CI enforces linting for both Python and the frontend. PRs will fail if either linter reports errors.

**Python (Ruff):**
- Check: `ruff check kernelboard/ tests/`
- Auto-fix: `ruff check --fix kernelboard/ tests/`
- Config: `pyproject.toml` under `[tool.ruff]`

**Frontend (ESLint):**
- Check: `cd frontend && npm run lint`
- Auto-fix: `cd frontend && npm run lint -- --fix`

## Project Structure

- `kernelboard/` - Flask backend
Expand Down
6 changes: 6 additions & 0 deletions frontend/eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@ export default tseslint.config(
'warn',
{ allowConstantExport: true },
],
'@typescript-eslint/no-explicit-any': 'warn',
'@typescript-eslint/no-unused-vars': ['error', {
argsIgnorePattern: '^_',
varsIgnorePattern: '^_',
caughtErrorsIgnorePattern: '^_',
}],
'eol-last': ['error', 'always']
},
},
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/App.test.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { describe, it } from "vitest";
import { render, screen } from "@testing-library/react";
import { screen } from "@testing-library/react";
import { MemoryRouter } from "react-router-dom";
import App from "./App";

Expand Down
1 change: 0 additions & 1 deletion frontend/src/components/app-layout/NavUserProfile.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { useState } from "react";
import {
Alert,
Avatar,
Button,
Divider,
Expand Down
2 changes: 0 additions & 2 deletions frontend/src/components/common/EllipsisWithTooltip.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import {
Tooltip,
Typography,
type SxProps,
type Theme,
type TypographyVariant,
} from "@mui/material";
import React, { useEffect, useRef, useState } from "react";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ const MarkdownRenderer: React.FC<MarkdownRendererProps> = ({
remarkPlugins={[remarkGfm, remarkMath]}
rehypePlugins={[rehypeRaw, rehypeKatex]}
components={{
a: ({ node, ...props }) => (
a: ({ node: _node, ...props }) => (
<a
style={{
color: theme.palette.primary.main,
Expand All @@ -63,10 +63,10 @@ const MarkdownRenderer: React.FC<MarkdownRendererProps> = ({
{...props}
/>
),
figure: ({ node, ...props }) => (
figure: ({ node: _node, ...props }) => (
<figure style={{ textAlign: align, margin: "1.5rem 0" }} {...props} />
),
figcaption: ({ node, ...props }) => (
figcaption: ({ node: _node, ...props }) => (
<figcaption
style={{
fontStyle: "italic",
Expand All @@ -76,7 +76,7 @@ const MarkdownRenderer: React.FC<MarkdownRendererProps> = ({
{...props}
/>
),
img: ({ node, ...props }) => {
img: ({ node: _node, ...props }) => {
return (
<div style={{ textAlign: align, margin: "0" }}>
<img style={styleProps} {...props} alt={props.alt} />
Expand Down
16 changes: 8 additions & 8 deletions frontend/src/lib/hooks/useApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,13 +53,13 @@ export function fetcherApiCallback<T, Args extends any[]>(
fetcher: Fetcher<T, Args>,
redirectMap: Record<number, string> = defaultRedirectMap,
) {
const navigate = useNavigate();
const [data, setData] = useState<T | null>(null);
const [error, setError] = useState<string | null>(null);
const [errorStatus, setErrorStatus] = useState<number | null>(null);
const [loading, setLoading] = useState(true);
const navigate = useNavigate(); // eslint-disable-line react-hooks/rules-of-hooks
const [data, setData] = useState<T | null>(null); // eslint-disable-line react-hooks/rules-of-hooks
const [error, setError] = useState<string | null>(null); // eslint-disable-line react-hooks/rules-of-hooks
const [errorStatus, setErrorStatus] = useState<number | null>(null); // eslint-disable-line react-hooks/rules-of-hooks
const [loading, setLoading] = useState(true); // eslint-disable-line react-hooks/rules-of-hooks

const call = useCallback(
const call = useCallback( // eslint-disable-line react-hooks/rules-of-hooks
async (...params: Args) => {
setLoading(true);

Expand All @@ -72,8 +72,8 @@ export function fetcherApiCallback<T, Args extends any[]>(
setData(result);
return result;
} catch (e: any) {
let status = e.status ? e.status : 0;
let msg = e.message ? e.message : "";
const status = e.status ? e.status : 0;
const msg = e.message ? e.message : "";

// set and logging the error if any
setError(status);
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/lib/store/authStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,14 @@ export const useAuthStore = create<AuthState>((set, get) => ({
);
try {
const res = await getMe();
set((s) => ({
set((_s) => ({
me: res,
loading: false,
inFlight: false,
error: null,
}));
} catch (e: any) {
set((s) => ({
set((_s) => ({
error: e?.message ?? "Failed to fetch user",
loading: false,
inFlight: false,
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/pages/home/Home.test.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { render, screen, fireEvent, within } from "@testing-library/react";
import { render, screen } from "@testing-library/react";
import { BrowserRouter } from "react-router-dom";
import { ThemeProvider } from "@mui/material";
import { appTheme } from "../../components/common/styles/theme";
Expand Down Expand Up @@ -52,7 +52,7 @@

renderWithProviders(<Home />);

expect(screen.getByText(/Summoning/i)).toBeInTheDocument();

Check failure on line 55 in frontend/src/pages/home/Home.test.tsx

View workflow job for this annotation

GitHub Actions / test

src/pages/home/Home.test.tsx > Home > shows loading state initially

TestingLibraryElementError: Unable to find an element with the text: /Summoning/i. This could be because the text is broken up by multiple elements. In this case, you can provide a function for your text matcher to make your matcher more flexible. Ignored nodes: comments, script, style <body> <div> <div class="MuiBox-root css-1v1atuu" > <span class="MuiCircularProgress-root MuiCircularProgress-indeterminate MuiCircularProgress-colorSecondary css-1lwet26-MuiCircularProgress-root" role="progressbar" style="width: 40px; height: 40px;" > <svg class="MuiCircularProgress-svg css-54pwck-MuiCircularProgress-svg" viewBox="22 22 44 44" > <circle class="MuiCircularProgress-circle MuiCircularProgress-circleIndeterminate css-19t5dcl-MuiCircularProgress-circle" cx="44" cy="44" fill="none" r="20.2" stroke-width="3.6" /> </svg> </span> <div class="MuiBox-root css-axw7ok" > <svg color="#f48fb1" fill="currentColor" height="40" stroke="currentColor" stroke-width="0" style="color: rgb(244, 143, 177);" viewBox="0 0 512 512" width="40" xmlns="http://www.w3.org/2000/svg" > <path d="M242.29 27.29c-4.165 0-4.79.695-5.593 3.058-.803 2.362-.714 8.368 4.065 17.097 8.758 16 32.356 39.726 78.675 64.582 7.15-7.48 15.604-12.92 24.57-16.713-25.48-14.076-44.05-29.58-58.892-42.158-18.93-16.04-31.326-25.867-42.826-25.867zm-66.274 54.66c-3.815.007-4.68.864-5.07 1.355-.39.49-1 1.882-.485 5.125 1.03 6.484 7.16 18.015 18 28.943 21.683 21.855 60.793 42.287 109.542 34.72 2.13-.33 4.725-.616 7.846 1.194 3.12 1.81 4.73 5.96 4.77 8.36.076 4.807-1.495 6.874-2.722 9.546-2.452 5.345-4.35 11.657-4.375 11.47v.003c1.98 15 14.374 26.28 32.396 34.63 18.023 8.353 40.75 13.004 58.875 14.737 12.552 1.2 27.23.995 40.9-1.388-1.555-2.715-2.676-5.576-3.31-8.516-1.763-8.156.105-16.39 4.093-23.327 6.392-11.12 18.112-19.685 32.36-22.83-4.64-7.837-10.434-16.323-17.024-24.592-19.92-24.992-47.433-46.955-67.978-47.384-20.465-.427-44.107 6.055-57.367 24.242-2.24 3.07-5.56 4.144-8.018 4.46-2.457.318-4.623.1-6.92-.284-4.592-.766-9.7-2.373-15.613-4.527-11.825-4.308-26.625-10.89-42.127-17.774-15.502-6.883-31.668-14.06-45.744-19.44-14.076-5.38-26.546-8.735-32.027-8.724zm224.422 61.915a16 16 0 0 1 15.533 15.994 16 16 0 0 1-32 0 16 16 0 0 1 16.467-15.995zM39.79 190.777c-.804.006-2.017.25-4.017 1.24a9 9 0 0 0-.002 0c-4.252 2.103-7.437 7.213-8.883 16.325-1.447 9.11-.696 21.27 1.888 33.53 2.585 12.263 6.954 24.683 12.116 34.634 3.023 5.827 6.386 10.568 9.53 14.133 4.966-17.297 13.943-33.833 27.697-48.44-3.153-2.038-5.968-4.422-8.365-7.046-7.05-7.717-11.604-16.873-15.648-24.877-4.045-8.003-7.82-14.854-10.64-17.605-1.408-1.376-2.22-1.714-2.99-1.84-.192-.032-.414-.057-.683-.055zm437.63 2.06c-11.655 1.13-21.29 7.89-25.342 14.936-2.183 3.797-2.794 7.368-2.105 10.555.51 2.36 1.71 4.797 4.408 7.29 11.853-4.564 21.157-11.42 26.145-20.938-.286.42.182-1.32-.504-4.184-.494-2.07-1.397-4.71-2.6-7.66zm-198.496 7.724c-2.463-.004-4.896.007-7.3.034-71.537.806-120.588 13.47-152.624 32.187-36.613 21.393-51.716 50.092-54.844 81.44-3.128 31.346 6.6 65.513 22 94.56 14.84 27.988 35.094 51.027 51.97 62.22H312.19c-.134-1.91-.67-3.555-1.502-5.188-1.637-3.21-4.918-6.56-10.032-9.687-10.228-6.256-27.12-11.045-44.812-14.438-17.693-3.392-36.175-5.596-50.625-8-7.227-1.2-13.393-2.377-18.532-4.125-2.57-.873-4.92-1.813-7.313-3.593-2.392-1.782-5.313-5.385-5.313-9.97 0-2.61 1.27-4.982 2.375-6.22 1.107-1.236 2.03-1.74 2.75-2.124 1.442-.765 2.283-.952 3.125-1.156 1.686-.408 3.238-.605 5.125-.813 3.776-.414 8.714-.75 14.72-1.187 12.01-.875 28.107-2.106 44.968-4.688 33.722-5.162 69.382-16.778 81.156-36.437 6.403-10.69 5.69-20.67-.56-31.156-6.253-10.487-18.818-20.728-35.72-27.376-33.803-13.297-84.07-12.464-132.72 22.47l-10.5-14.627c33.327-23.93 67.99-33.66 99-33.78 18.608-.072 35.892 3.33 50.782 9.187
});

it("shows error message", () => {
Expand Down Expand Up @@ -450,7 +450,7 @@

renderWithProviders(<Home />);

const link = screen.getByRole("link");

Check failure on line 453 in frontend/src/pages/home/Home.test.tsx

View workflow job for this annotation

GitHub Actions / test

src/pages/home/Home.test.tsx > Home > LeaderboardTile functionality > creates correct navigation links

TestingLibraryElementError: Found multiple elements with the role "link" Here are the matching elements: Ignored nodes: comments, script, style <a class="MuiButtonBase-root MuiButton-root MuiButton-contained MuiButton-containedPrimary MuiButton-sizeMedium MuiButton-containedSizeMedium MuiButton-colorPrimary MuiButton-disableElevation MuiButton-root MuiButton-contained MuiButton-containedPrimary MuiButton-sizeMedium MuiButton-containedSizeMedium MuiButton-colorPrimary MuiButton-disableElevation css-293ois-MuiButtonBase-root-MuiButton-root" href="https://github.com/gpu-mode/popcorn-cli" rel="noopener" tabindex="0" target="_blank" > Submit your first kernel <span class="MuiButton-icon MuiButton-endIcon MuiButton-iconSizeMedium css-1wyk03i-MuiButton-endIcon" > <svg aria-hidden="true" class="MuiSvgIcon-root MuiSvgIcon-fontSizeMedium css-1umw9bq-MuiSvgIcon-root" data-testid="ArrowOutwardIcon" focusable="false" viewBox="0 0 24 24" > <path d="M6 6v2h8.59L5 17.59 6.41 19 16 9.41V18h2V6z" /> </svg> </span> </a> Ignored nodes: comments, script, style <a class="MuiPaper-root MuiPaper-elevation MuiPaper-rounded MuiPaper-elevation1 MuiCard-root css-113j9t3-MuiPaper-root-MuiCard-root" data-discover="true" href="/leaderboard/42" style="--Paper-shadow: 0px 2px 1px -1px rgba(0,0,0,0.2),0px 1px 1px 0px rgba(0,0,0,0.14),0px 1px 3px 0px rgba(0,0,0,0.12);" > <div class="MuiCardContent-root css-1p4602r-MuiCardContent-root" > <div class="MuiBox-root css-19idom" > <h3 class="MuiTypography-root MuiTypography-h6 css-i8lmsl-MuiTypography-root" > <div class="MuiBox-root css-3n403j" /> test-leaderboard </h3> </div> <p class="MuiTypography-root MuiTypography-body1 css-5ww38-MuiTypography-root" > 2 days 5 hours remaining </p> <p class="MuiTypography-root MuiTypography-body2 css-1kh5lox-MuiTypography-root" > T4 </p> <div class="MuiBox-root css-8xl60i" > <div class="MuiBox-root css-85t6ji" > <div class="MuiChip-root MuiChip-filled MuiChip-sizeSmall MuiChip-colorDefault MuiChip-filledDefault css-xq0mjf-MuiChip-root" > <span class="MuiChip-label MuiChip-labelSmall css-eccknh-MuiChip-label" > T4 </span> </div> </div> <div class="MuiBox-root css-0" > <div class="MuiBox-root css-a4p8pb" > <div class="MuiBox-root css-171onha" > <p class="MuiTypography-root MuiTypography-body2 css-rx37n6-MuiTypography-root" > alice </p> <span class="MuiTypography-root MuiTypography-body1 css-6orwfa-MuiTypography-root" /> </div> <p class="MuiTypography-root MuiTypography-body2 css-6ed5ix-MuiTypography-root" > 123.000μs </p> </div> </div> </div> </div> </a> (If this is intentional, then use the `*AllBy*` variant of the query (like `queryAllByText`, `getAllByText`, or `findAllByText`)). Ignored nodes: comments, script, style <body> <div> <div class="MuiBox-root css-1hzmvx4" > <div class="MuiBox-root css-0" > <h1 class="MuiTypography-root MuiTypography-h1 css-1kn8utt-MuiTypography-root" > Leaderboards </h1> <div class="MuiBox-root css-1yjvs5a" > <a class="MuiButtonBase-root MuiButton-root MuiButton-contained MuiButton-containedPrimary MuiButton-sizeMedium MuiButton-containedSizeMedium MuiButton-colorPrimary MuiButton-disableElevation MuiButton-root MuiButton-contained MuiButton-containedPrimary MuiButton-sizeMedium MuiButton-containedSizeMedium MuiButton-colorPrimary MuiButton-disableElevation css-293ois-MuiButtonBase-root-MuiButton-root"
expect(link).toHaveAttribute("href", "/leaderboard/42");
});

Expand Down
2 changes: 1 addition & 1 deletion frontend/src/pages/leaderboard/Leaderboard.test.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { render, screen, fireEvent, within } from "@testing-library/react";
import { screen, fireEvent, within } from "@testing-library/react";
import { vi, expect, it, describe, beforeEach } from "vitest";
import Leaderboard from "./Leaderboard";
import * as apiHook from "../../lib/hooks/useApi";
Expand Down
10 changes: 5 additions & 5 deletions frontend/src/pages/leaderboard/Leaderboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,7 @@ export default function Leaderboard() {
>
<CardTitle fontWeight="bold">Submission</CardTitle>
<LeaderboardSubmit
leaderboardId={id!!}
leaderboardId={id!}
leaderboardName={data.name}
gpuTypes={data.gpu_types}
disabled={isExpired(data.deadline)}
Expand All @@ -295,9 +295,9 @@ export default function Leaderboard() {
)}
{/* History List */}
<SubmissionHistorySection
leaderboardId={id!!}
leaderboardId={id!}
leaderboardName={data.name}
userId={userId!!}
userId={userId!}
refreshFlag={refreshFlag}
/>
</CardContent>
Expand All @@ -313,13 +313,13 @@ export default function Leaderboard() {
<CardTitle fontWeight="bold">
AI Model Performance Trend
</CardTitle>
<AiTrendChart leaderboardId={id!!} rankings={data.rankings} />
<AiTrendChart leaderboardId={id!} rankings={data.rankings} />
</CardContent>
</Card>
<Card sx={{ mt: 2 }}>
<CardContent>
<CardTitle fontWeight="bold">User Performance Trend</CardTitle>
<UserTrendChart leaderboardId={id!!} defaultUser={defaultUser} defaultGpuType={defaultGpuType} />
<UserTrendChart leaderboardId={id!} defaultUser={defaultUser} defaultGpuType={defaultGpuType} />
</CardContent>
</Card>
</TabPanel>
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/pages/leaderboard/components/RankingLists.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -85,15 +85,15 @@ export default function RankingsList({
const me = useAuthStore((s) => s.me);
const isAdmin = !!me?.user?.is_admin;
const [expanded, setExpanded] = useState<Record<string, boolean>>({});
const [colorHash, _] = useState<string>(
const [colorHash] = useState<string>(
Math.random().toString(36).slice(2, 8),
);
const [codes, setCodes] = useState<Map<number, string>>(new Map());

const submissionIds = useMemo(() => {
if (!rankings) return [];
const ids: number[] = [];
Object.entries(rankings).forEach(([key, value]) => {
Object.entries(rankings).forEach(([_key, value]) => {
const li = value as any[];
if (Array.isArray(li) && li.length > 0) {
li.forEach((item) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ const styles = {

export default function SubmissionHistorySection({
leaderboardId,
leaderboardName,
leaderboardName: _leaderboardName,
userId,
pageSize = 10,
refreshFlag,
Expand Down Expand Up @@ -128,10 +128,10 @@ export default function SubmissionHistorySection({
setLastRefresh(new Date());
}, [leaderboardId, userId, page, pageSize, call]);

let totalPages =
const totalPages =
data?.limit && data?.total ? Math.ceil(data?.total / data?.limit) : 1;
let items: Submission[] = data?.items ?? [];
let total: number = data?.total ?? 0;
const items: Submission[] = data?.items ?? [];
const total: number = data?.total ?? 0;

const tooOld = lastRefresh && now - lastRefresh.getTime() > 10 * 60 * 1000;

Expand Down
3 changes: 1 addition & 2 deletions frontend/src/pages/news/News.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import { vi, describe, it, expect, beforeEach } from "vitest";
import News from "./News"; // 假设你当前文件路径为 pages/News.tsx
import * as apiHook from "../../lib/hooks/useApi";
import { useParams, useNavigate } from "react-router-dom";

// 统一 mock useApi hook
vi.mock("../../lib/hooks/useApi", () => ({
Expand Down Expand Up @@ -52,7 +53,6 @@
describe("News", () => {
beforeEach(() => {
vi.clearAllMocks();
const { useParams, useNavigate } = require("react-router-dom");
(useParams as ReturnType<typeof vi.fn>).mockReturnValue({});
(useNavigate as ReturnType<typeof vi.fn>).mockReturnValue(mockNavigate);
});
Expand All @@ -75,7 +75,7 @@
render(<News />);

// asserts
expect(screen.getByText(/Summoning/i)).toBeInTheDocument();

Check failure on line 78 in frontend/src/pages/news/News.test.tsx

View workflow job for this annotation

GitHub Actions / test

src/pages/news/News.test.tsx > News > shows loading state

TestingLibraryElementError: Unable to find an element with the text: /Summoning/i. This could be because the text is broken up by multiple elements. In this case, you can provide a function for your text matcher to make your matcher more flexible. Ignored nodes: comments, script, style <body> <div> <div class="MuiBox-root css-1v1atuu" > <span class="MuiCircularProgress-root MuiCircularProgress-indeterminate MuiCircularProgress-colorSecondary css-w58ak0-MuiCircularProgress-root" role="progressbar" style="width: 40px; height: 40px;" > <svg class="MuiCircularProgress-svg css-54pwck-MuiCircularProgress-svg" viewBox="22 22 44 44" > <circle class="MuiCircularProgress-circle MuiCircularProgress-circleIndeterminate css-19t5dcl-MuiCircularProgress-circle" cx="44" cy="44" fill="none" r="20.2" stroke-width="3.6" /> </svg> </span> <div class="MuiBox-root css-axw7ok" > <svg color="#f48fb1" fill="currentColor" height="40" stroke="currentColor" stroke-width="0" style="color: rgb(244, 143, 177);" viewBox="0 0 512 512" width="40" xmlns="http://www.w3.org/2000/svg" > <path d="M242.29 27.29c-4.165 0-4.79.695-5.593 3.058-.803 2.362-.714 8.368 4.065 17.097 8.758 16 32.356 39.726 78.675 64.582 7.15-7.48 15.604-12.92 24.57-16.713-25.48-14.076-44.05-29.58-58.892-42.158-18.93-16.04-31.326-25.867-42.826-25.867zm-66.274 54.66c-3.815.007-4.68.864-5.07 1.355-.39.49-1 1.882-.485 5.125 1.03 6.484 7.16 18.015 18 28.943 21.683 21.855 60.793 42.287 109.542 34.72 2.13-.33 4.725-.616 7.846 1.194 3.12 1.81 4.73 5.96 4.77 8.36.076 4.807-1.495 6.874-2.722 9.546-2.452 5.345-4.35 11.657-4.375 11.47v.003c1.98 15 14.374 26.28 32.396 34.63 18.023 8.353 40.75 13.004 58.875 14.737 12.552 1.2 27.23.995 40.9-1.388-1.555-2.715-2.676-5.576-3.31-8.516-1.763-8.156.105-16.39 4.093-23.327 6.392-11.12 18.112-19.685 32.36-22.83-4.64-7.837-10.434-16.323-17.024-24.592-19.92-24.992-47.433-46.955-67.978-47.384-20.465-.427-44.107 6.055-57.367 24.242-2.24 3.07-5.56 4.144-8.018 4.46-2.457.318-4.623.1-6.92-.284-4.592-.766-9.7-2.373-15.613-4.527-11.825-4.308-26.625-10.89-42.127-17.774-15.502-6.883-31.668-14.06-45.744-19.44-14.076-5.38-26.546-8.735-32.027-8.724zm224.422 61.915a16 16 0 0 1 15.533 15.994 16 16 0 0 1-32 0 16 16 0 0 1 16.467-15.995zM39.79 190.777c-.804.006-2.017.25-4.017 1.24a9 9 0 0 0-.002 0c-4.252 2.103-7.437 7.213-8.883 16.325-1.447 9.11-.696 21.27 1.888 33.53 2.585 12.263 6.954 24.683 12.116 34.634 3.023 5.827 6.386 10.568 9.53 14.133 4.966-17.297 13.943-33.833 27.697-48.44-3.153-2.038-5.968-4.422-8.365-7.046-7.05-7.717-11.604-16.873-15.648-24.877-4.045-8.003-7.82-14.854-10.64-17.605-1.408-1.376-2.22-1.714-2.99-1.84-.192-.032-.414-.057-.683-.055zm437.63 2.06c-11.655 1.13-21.29 7.89-25.342 14.936-2.183 3.797-2.794 7.368-2.105 10.555.51 2.36 1.71 4.797 4.408 7.29 11.853-4.564 21.157-11.42 26.145-20.938-.286.42.182-1.32-.504-4.184-.494-2.07-1.397-4.71-2.6-7.66zm-198.496 7.724c-2.463-.004-4.896.007-7.3.034-71.537.806-120.588 13.47-152.624 32.187-36.613 21.393-51.716 50.092-54.844 81.44-3.128 31.346 6.6 65.513 22 94.56 14.84 27.988 35.094 51.027 51.97 62.22H312.19c-.134-1.91-.67-3.555-1.502-5.188-1.637-3.21-4.918-6.56-10.032-9.687-10.228-6.256-27.12-11.045-44.812-14.438-17.693-3.392-36.175-5.596-50.625-8-7.227-1.2-13.393-2.377-18.532-4.125-2.57-.873-4.92-1.813-7.313-3.593-2.392-1.782-5.313-5.385-5.313-9.97 0-2.61 1.27-4.982 2.375-6.22 1.107-1.236 2.03-1.74 2.75-2.124 1.442-.765 2.283-.952 3.125-1.156 1.686-.408 3.238-.605 5.125-.813 3.776-.414 8.714-.75 14.72-1.187 12.01-.875 28.107-2.106 44.968-4.688 33.722-5.162 69.382-16.778 81.156-36.437 6.403-10.69 5.69-20.67-.56-31.156-6.253-10.487-18.818-20.728-35.72-27.376-33.803-13.297-84.07-12.464-132.72 22.47l-10.5-14.627c33.327-23.93 67.99-33.66 99-33.78 18.608-.072 35.892 3.33 50.782 9.187 1
});

it("shows error message", () => {
Expand Down Expand Up @@ -119,7 +119,7 @@
// asserts
expect(screen.getByText("News and Announcements")).toBeInTheDocument();

const sidebar = screen.getByTestId("news-sidbar");

Check failure on line 122 in frontend/src/pages/news/News.test.tsx

View workflow job for this annotation

GitHub Actions / test

src/pages/news/News.test.tsx > News > renders news items and markdown

TestingLibraryElementError: Unable to find an element by: [data-testid="news-sidbar"] Ignored nodes: comments, script, style <body> <div> <div class="MuiBox-root css-vjviqw" > <h4 class="MuiTypography-root MuiTypography-h4 MuiTypography-gutterBottom css-1it4a80-MuiTypography-root" > News and Announcements </h4> <div class="MuiPaper-root MuiPaper-outlined MuiPaper-rounded MuiCard-root css-1cp3svp-MuiPaper-root-MuiCard-root" > <button class="MuiButtonBase-root MuiCardActionArea-root css-coyn9m-MuiButtonBase-root-MuiCardActionArea-root" tabindex="0" type="button" > <div class="MuiCardContent-root css-1lt5qva-MuiCardContent-root" > <h2 class="MuiTypography-root MuiTypography-h6 css-1miy0lu-MuiTypography-root" > Title One </h2> <p class="MuiTypography-root MuiTypography-body2 css-tmy7vb-MuiTypography-root" > 2025-07-10 </p> <p class="MuiTypography-root MuiTypography-body2 css-2abux7-MuiTypography-root" > This is markdown content one. </p> </div> <span class="MuiCardActionArea-focusHighlight css-1h5un5t-MuiCardActionArea-focusHighlight" /> </button> </div> <div class="MuiPaper-root MuiPaper-outlined MuiPaper-rounded MuiCard-root css-1cp3svp-MuiPaper-root-MuiCard-root" > <button class="MuiButtonBase-root MuiCardActionArea-root css-coyn9m-MuiButtonBase-root-MuiCardActionArea-root" tabindex="0" type="button" > <div class="MuiCardContent-root css-1lt5qva-MuiCardContent-root" > <h2 class="MuiTypography-root MuiTypography-h6 css-1miy0lu-MuiTypography-root" > Title Two </h2> <p class="MuiTypography-root MuiTypography-body2 css-tmy7vb-MuiTypography-root" > 2025-07-09 </p> <p class="MuiTypography-root MuiTypography-body2 css-2abux7-MuiTypography-root" > Another markdown section. </p> </div> <span class="MuiCardActionArea-focusHighlight css-1h5un5t-MuiCardActionArea-focusHighlight" /> </button> </div> </div> </div> </body> ❯ Object.getElementError node_modules/@testing-library/dom/dist/config.js:37:19 ❯ node_modules/@testing-library/dom/dist/query-helpers.js:76:38 ❯ node_modules/@testing-library/dom/dist/query-helpers.js:52:17 ❯ node_modules/@testing-library/dom/dist/query-helpers.js:95:19 ❯ src/pages/news/News.test.tsx:122:28
const newsContent = screen.getByTestId("news-content");

expect(within(sidebar).getByText("Title One")).toBeInTheDocument();
Expand Down Expand Up @@ -154,7 +154,7 @@
render(<News />);

// asserts
const sidebar = screen.getByTestId("news-sidbar");

Check failure on line 157 in frontend/src/pages/news/News.test.tsx

View workflow job for this annotation

GitHub Actions / test

src/pages/news/News.test.tsx > News > calls scrollIntoView when sidebar item is clicked

TestingLibraryElementError: Unable to find an element by: [data-testid="news-sidbar"] Ignored nodes: comments, script, style <body> <div> <div class="MuiBox-root css-vjviqw" > <h4 class="MuiTypography-root MuiTypography-h4 MuiTypography-gutterBottom css-1it4a80-MuiTypography-root" > News and Announcements </h4> <div class="MuiPaper-root MuiPaper-outlined MuiPaper-rounded MuiCard-root css-1cp3svp-MuiPaper-root-MuiCard-root" > <button class="MuiButtonBase-root MuiCardActionArea-root css-coyn9m-MuiButtonBase-root-MuiCardActionArea-root" tabindex="0" type="button" > <div class="MuiCardContent-root css-1lt5qva-MuiCardContent-root" > <h2 class="MuiTypography-root MuiTypography-h6 css-1miy0lu-MuiTypography-root" > Title One </h2> <p class="MuiTypography-root MuiTypography-body2 css-tmy7vb-MuiTypography-root" > 2025-07-10 </p> <p class="MuiTypography-root MuiTypography-body2 css-2abux7-MuiTypography-root" > This is markdown content one. </p> </div> <span class="MuiCardActionArea-focusHighlight css-1h5un5t-MuiCardActionArea-focusHighlight" /> </button> </div> <div class="MuiPaper-root MuiPaper-outlined MuiPaper-rounded MuiCard-root css-1cp3svp-MuiPaper-root-MuiCard-root" > <button class="MuiButtonBase-root MuiCardActionArea-root css-coyn9m-MuiButtonBase-root-MuiCardActionArea-root" tabindex="0" type="button" > <div class="MuiCardContent-root css-1lt5qva-MuiCardContent-root" > <h2 class="MuiTypography-root MuiTypography-h6 css-1miy0lu-MuiTypography-root" > Title Two </h2> <p class="MuiTypography-root MuiTypography-body2 css-tmy7vb-MuiTypography-root" > 2025-07-09 </p> <p class="MuiTypography-root MuiTypography-body2 css-2abux7-MuiTypography-root" > Another markdown section. </p> </div> <span class="MuiCardActionArea-focusHighlight css-1h5un5t-MuiCardActionArea-focusHighlight" /> </button> </div> </div> </div> </body> ❯ Object.getElementError node_modules/@testing-library/dom/dist/config.js:37:19 ❯ node_modules/@testing-library/dom/dist/query-helpers.js:76:38 ❯ node_modules/@testing-library/dom/dist/query-helpers.js:52:17 ❯ node_modules/@testing-library/dom/dist/query-helpers.js:95:19 ❯ src/pages/news/News.test.tsx:157:28
const newsContent = screen.getByTestId("news-content");

const section = within(newsContent).getByText("Title Two").closest("div");
Expand All @@ -181,7 +181,6 @@
it("scrolls to section when slug is provided in URL", async () => {
// prepare
const scrollIntoViewMock = vi.fn();
const { useParams } = require("react-router-dom");
(useParams as ReturnType<typeof vi.fn>).mockReturnValue({ slug: "news-2" });

const mockHookReturn = {
Expand All @@ -199,7 +198,7 @@
// render
render(<News />);

const newsContent = screen.getByTestId("news-content");

Check failure on line 201 in frontend/src/pages/news/News.test.tsx

View workflow job for this annotation

GitHub Actions / test

src/pages/news/News.test.tsx > News > scrolls to section when slug is provided in URL

TestingLibraryElementError: Unable to find an element by: [data-testid="news-content"] Ignored nodes: comments, script, style <body> <div> <div class="MuiBox-root css-vjviqw" > <button class="MuiButtonBase-root MuiButton-root MuiButton-text MuiButton-textPrimary MuiButton-sizeMedium MuiButton-textSizeMedium MuiButton-colorPrimary MuiButton-root MuiButton-text MuiButton-textPrimary MuiButton-sizeMedium MuiButton-textSizeMedium MuiButton-colorPrimary css-i6nfjv-MuiButtonBase-root-MuiButton-root" tabindex="0" type="button" > <span class="MuiButton-icon MuiButton-startIcon MuiButton-iconSizeMedium css-1sh91j5-MuiButton-startIcon" > <svg aria-hidden="true" class="MuiSvgIcon-root MuiSvgIcon-fontSizeMedium css-1umw9bq-MuiSvgIcon-root" data-testid="ArrowBackIcon" focusable="false" viewBox="0 0 24 24" > <path d="M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.41-1.41L7.83 13H20z" /> </svg> </span> Back to all posts </button> <h4 class="MuiTypography-root MuiTypography-h4 MuiTypography-gutterBottom css-1it4a80-MuiTypography-root" > Title Two </h4> <p class="MuiTypography-root MuiTypography-body2 css-1jwb7kn-MuiTypography-root" > 2025-07-09 </p> <div> Loading content... </div> </div> </div> </body> ❯ Object.getElementError node_modules/@testing-library/dom/dist/config.js:37:19 ❯ node_modules/@testing-library/dom/dist/query-helpers.js:76:38 ❯ node_modules/@testing-library/dom/dist/query-helpers.js:52:17 ❯ node_modules/@testing-library/dom/dist/query-helpers.js:95:19 ❯ src/pages/news/News.test.tsx:201:32
const section = within(newsContent).getByText("Title Two").closest("div");
if (section) {
Object.defineProperty(section, "scrollIntoView", {
Expand Down
26 changes: 15 additions & 11 deletions kernelboard/__init__.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,26 @@
import http
import os
from re import L

from dotenv import load_dotenv
from flask import Flask, jsonify, redirect, session, g
from flask_login import LoginManager, current_user
from flask import Flask, make_response, redirect, send_from_directory
from flask_login import LoginManager
from flask_session import Session
from flask_talisman import Talisman
from kernelboard.api.auth import User, providers
from kernelboard.lib import db, env, time, score
from kernelboard import color, error, health, index, leaderboard, news

from kernelboard import color, health
from kernelboard import error as error
from kernelboard import index as index
from kernelboard import leaderboard as leaderboard
from kernelboard import news as news
from kernelboard.api import create_api_blueprint
from kernelboard.lib.redis_connection import create_redis_connection
from flask import send_from_directory, make_response
from kernelboard.api.auth import User, providers
from kernelboard.lib import db, env, score, time
from kernelboard.lib.logging import configure_logging
from kernelboard.og_tags import is_social_crawler, get_og_tags_for_path, inject_og_tags
from flask_limiter import Limiter
from kernelboard.lib.rate_limiter import limiter
from kernelboard.lib.redis_connection import create_redis_connection
from kernelboard.lib.status_code import http_error
from kernelboard.og_tags import get_og_tags_for_path, inject_og_tags, is_social_crawler


def create_app(test_config=None):
# Check if we're in development mode:
Expand Down Expand Up @@ -123,7 +127,7 @@ def redirect_v2(path=""):
return redirect(f"/{path}", code=301)

@app.errorhandler(401)
def unauthorized(_error):
def handle_401(_error):
return redirect("/401")

@app.errorhandler(404)
Expand Down
10 changes: 4 additions & 6 deletions kernelboard/api/__init__.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
from requests import auth
from flask import Blueprint
from werkzeug.exceptions import HTTPException
from kernelboard.api.submission import submission
from kernelboard.lib.status_code import http_error, http_success

from kernelboard.api.auth import auth_bp
from kernelboard.api.events import events_bp
from kernelboard.api.leaderboard import leaderboard_bp
from kernelboard.api.leaderboard_summaries import leaderboard_summaries_bp
from kernelboard.api.news import news_bp
from kernelboard.api.auth import auth_bp
from kernelboard.api.submission import submission_bp
from kernelboard.api.events import events_bp

from kernelboard.lib.status_code import http_error, http_success


def create_api_blueprint():
Expand Down
6 changes: 4 additions & 2 deletions kernelboard/api/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,17 @@
import requests
from flask import (
Blueprint,
current_app as app,
redirect,
request,
session,
url_for,
)
from flask import (
current_app as app,
)
from flask_login import UserMixin, current_user, login_user, logout_user
from kernelboard.lib.auth_utils import ensure_user_info_with_token, get_user_info_from_session

from kernelboard.lib.auth_utils import ensure_user_info_with_token, get_user_info_from_session
from kernelboard.lib.status_code import http_success

auth_bp = Blueprint("auth", __name__)
Expand Down
7 changes: 4 additions & 3 deletions kernelboard/api/events.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
from http import HTTPStatus
import logging
import os
import time
from http import HTTPStatus

import requests
from flask import Blueprint
from kernelboard.lib.status_code import http_error, http_success
import logging

from kernelboard.lib.status_code import http_error, http_success

logger = logging.getLogger(__name__)

Expand Down
Loading
Loading