Skip to content

Commit

Permalink
Add very simple display of challenges solved by team
Browse files Browse the repository at this point in the history
  • Loading branch information
J12934 committed Nov 14, 2024
1 parent 8a384d1 commit db12088
Show file tree
Hide file tree
Showing 7 changed files with 100 additions and 12 deletions.
12 changes: 6 additions & 6 deletions balancer/routes/individualScore.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ import (
)

type IndividualScore struct {
Name string `json:"name"`
Score int `json:"score"`
SolvedChallenges int `json:"solvedChallenges"`
Position int `json:"position"`
TotalTeams int `json:"totalTeams"`
Name string `json:"name"`
Score int `json:"score"`
SolvedChallenges []string `json:"solvedChallenges"`
Position int `json:"position"`
TotalTeams int `json:"totalTeams"`
}

func handleIndividualScore(bundle *b.Bundle) http.Handler {
Expand Down Expand Up @@ -45,7 +45,7 @@ func handleIndividualScore(bundle *b.Bundle) http.Handler {
Score: teamScore.Score,
Position: teamScore.Position,
TotalTeams: teamCount,
SolvedChallenges: len(teamScore.Challenges),
SolvedChallenges: teamScore.Challenges,
}

responseBytes, err := json.Marshal(response)
Expand Down
2 changes: 1 addition & 1 deletion balancer/routes/individualScore_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ func TestIndividualScoreHandler(t *testing.T) {
server.ServeHTTP(rr, req)

assert.Equal(t, http.StatusOK, rr.Code)
assert.JSONEq(t, `{"name":"foobar","score":10,"position":1,"solvedChallenges":1,"totalTeams":1}`, rr.Body.String())
assert.JSONEq(t, `{"name":"foobar","score":10,"position":1,"solvedChallenges":["scoreBoardChallenge"],"totalTeams":1}`, rr.Body.String())
})

t.Run("returns a 404 if the scores haven't been calculated yet", func(t *testing.T) {
Expand Down
1 change: 1 addition & 0 deletions balancer/routes/staticFiles.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ func handleStaticFiles(bundle *bundle.Bundle) http.Handler {
regexp.MustCompile("/balancer/teams/" + teamNamePatternString + "/status"),
regexp.MustCompile("/balancer/teams/" + teamNamePatternString + "/joined"),
regexp.MustCompile("/balancer/score-board"),
regexp.MustCompile("/balancer/score-board/teams/" + teamNamePatternString + "/score"),
}

return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
Expand Down
1 change: 1 addition & 0 deletions balancer/routes/staticFiles_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ func TestStaticFileHandler(t *testing.T) {
"/balancer/teams/foo-bar-123/status/",
"/balancer/teams/abc/joined/",
"/balancer/score-board/",
"/balancer/score-board/teams/abc/score/",
}

server := http.NewServeMux()
Expand Down
7 changes: 6 additions & 1 deletion balancer/ui/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@ import { BrowserRouter, Routes, Route } from "react-router-dom";
import { IntlProvider } from "react-intl";

import { JoinPage } from "./pages/JoinPage";
import { JoiningPage } from "./pages/JoiningPage";
import { ScoreBoard } from "./pages/ScoreBoard";
import { JoiningPage } from "./pages/JoiningPage";
import { TeamStatusPage } from "./pages/TeamStatusPage";
import { IndividualScorePage } from "./pages/IndividualScorePage";

import { Layout } from "./Layout";
import { Spinner } from "./components/Spinner";
Expand Down Expand Up @@ -74,6 +75,10 @@ function App() {
element={<JoiningPage setActiveTeam={setActiveTeam} />}
/>
<Route path="/score-board/" element={<ScoreBoard />} />
<Route
path="/score-board/teams/:team"
element={<IndividualScorePage />}
/>
</Routes>
</Suspense>
</Layout>
Expand Down
77 changes: 77 additions & 0 deletions balancer/ui/src/pages/IndividualScorePage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { useState, useEffect } from "react";
import { useParams } from "react-router-dom";
import { Spinner } from "../components/Spinner";

interface IndividualTeamScore {
name: string;
score: string;
position: number;
totalTeams: number;
solvedChallenges: string[];
}

async function fetchScore(team: string): Promise<IndividualTeamScore> {
const response = await fetch(`/balancer/api/score-board/teams/${team}/score`);
return await response.json();
}

export function IndividualScorePage() {
const { team } = useParams();

if (!team) {
return <div>Team not found</div>;
}

const [score, setScore] = useState<IndividualTeamScore | null>(null);
useEffect(() => {
fetchScore(team).then(setScore);

const timer = setInterval(() => {
fetchScore(team).then(setScore);
}, 5000);

return () => {
clearInterval(timer);
};
}, []);

if (score === null) {
return <Spinner />;
}

return (
<>
<div className="p-0 overflow-hidden w-full max-w-2xl rounded-lg bg-gradient-to-b from-gray-100 via-gray-100 to-gray-500">
<h1 className="text-gray-500 px-4 pt-4 font-bold tracking-wide">
Solved Challenges for <strong>{team}</strong>
</h1>
<table className="w-full text-left border-collapse">
<thead className="w-full border-none bg-gray-100 text-gray-800">
<tr className="w-full">
<th
scope="col"
className="p-3 text-gray-500 text-xs font-medium uppercase"
>
Name
</th>
</tr>
</thead>
<tbody className="w-full dark:bg-gray-800">
{score.solvedChallenges.length === 0 && (
<tr className="border-t border-gray-600">
<td className="p-2">No challenges solved yet</td>
</tr>
)}
{score.solvedChallenges.map((challenge) => {
return (
<tr className="border-t border-gray-600" key={challenge}>
<td className="p-2">{challenge}</td>
</tr>
);
})}
</tbody>
</table>
</div>
</>
);
}
12 changes: 8 additions & 4 deletions balancer/ui/src/pages/ScoreBoard.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useState, useEffect } from "react";
import { injectIntl } from "react-intl";
import { Link } from "react-router-dom";

function FirstPlace({ ...props }) {
return <img src="/balancer/icons/first-place.svg" {...props} />;
Expand Down Expand Up @@ -44,7 +44,7 @@ async function fetchTeams(): Promise<Team[]> {
return teams;
}

export const ScoreBoard = injectIntl(() => {
export function ScoreBoard() {
const [teams, setTeams] = useState<Team[]>([]);
useEffect(() => {
fetchTeams().then(setTeams);
Expand Down Expand Up @@ -94,7 +94,11 @@ export const ScoreBoard = injectIntl(() => {
<td className="text-center p-2">
<PositionDisplay place={team.position} />
</td>
<td className="p-2">{team.name}</td>
<td className="p-2">
<Link to={`/score-board/teams/${team.name}`}>
{team.name}
</Link>
</td>
<td className="text-right p-2">
{team.score} points
<p className="text-gray-500 m-1">
Expand All @@ -109,4 +113,4 @@ export const ScoreBoard = injectIntl(() => {
</div>
</>
);
});
}

0 comments on commit db12088

Please sign in to comment.