Skip to content

Commit 2ea8cf0

Browse files
committed
Enhance leaderboard functionality to support visible entries in contest scores and popular votes, update API endpoints, and refactor export button logic.
1 parent e08c26c commit 2ea8cf0

File tree

6 files changed

+85
-40
lines changed

6 files changed

+85
-40
lines changed

src/commons/sagas/LeaderboardSaga.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,9 @@ const LeaderboardSaga = combineSagaHandlers(LeaderboardActions, {
3737

3838
getAllContestScores: function* (action) {
3939
const tokens: Tokens = yield selectTokens();
40-
const assessmentId = action.payload;
40+
const { assessmentId, visibleEntries } = action.payload;
4141

42-
const contestScores = yield call(getContestScoreLeaderboard, assessmentId, tokens);
42+
const contestScores = yield call(getContestScoreLeaderboard, assessmentId, visibleEntries, tokens);
4343

4444
if (contestScores) {
4545
yield put(actions.saveAllContestScores(contestScores));
@@ -48,9 +48,9 @@ const LeaderboardSaga = combineSagaHandlers(LeaderboardActions, {
4848

4949
getAllContestPopularVotes: function* (action) {
5050
const tokens: Tokens = yield selectTokens();
51-
const assessmentId = action.payload;
51+
const { assessmentId, visibleEntries } = action.payload;
5252

53-
const contestPopularVotes = yield call(getContestPopularVoteLeaderboard, assessmentId, tokens);
53+
const contestPopularVotes = yield call(getContestPopularVoteLeaderboard, assessmentId, visibleEntries, tokens);
5454
if (contestPopularVotes) {
5555
yield put(actions.saveAllContestPopularVotes(contestPopularVotes));
5656
}

src/commons/sagas/RequestsSaga.ts

+35-16
Original file line numberDiff line numberDiff line change
@@ -521,14 +521,15 @@ export const getPaginatedTotalXp = async (
521521
};
522522

523523
/**
524-
* GET /courses/{courseId}/leaderboard/contests/{assessment_id}/get_score_leaderboard
524+
* GET /courses/{courseId}/assessments/{assessmentid}/{visibleentries}/scoreLeaderboard
525525
*/
526526
export const getContestScoreLeaderboard = async (
527527
assessmentId: number,
528+
visibleEntries: number,
528529
tokens: Tokens
529530
): Promise<ContestLeaderboardRow[] | null> => {
530531
const resp = await request(
531-
`${courseId()}/leaderboard/contests/${assessmentId}/get_score_leaderboard`,
532+
`${courseId()}/assessments/${assessmentId}/${visibleEntries}/scoreLeaderboard`,
532533
'GET',
533534
{
534535
...tokens
@@ -541,29 +542,30 @@ export const getContestScoreLeaderboard = async (
541542

542543
const rows = await resp.json();
543544

544-
return rows.contest_score.map(
545+
return rows.leaderboard.map(
545546
(row: any): ContestLeaderboardRow => ({
546547
rank: row.rank,
547-
name: row.name,
548-
username: row.username,
549-
score: row.score,
548+
name: row.student_name,
549+
username: row.student_username,
550+
score: row.final_score,
550551
avatar: '',
551-
code: row.code,
552+
code: row.answer,
552553
submissionId: row.submission_id,
553554
votingId: rows.voting_id
554555
})
555556
);
556557
};
557558

558559
/**
559-
* GET /courses/{courseId}/leaderboard/contests/{assessment_id}/get_popular_vote_leaderboard
560+
* GET /courses/{courseId}/assessments/{assessmentid}/{visibleentries}/popularVoteLeaderboard
560561
*/
561562
export const getContestPopularVoteLeaderboard = async (
562563
assessmentId: number,
564+
visibleEntries: number,
563565
tokens: Tokens
564566
): Promise<ContestLeaderboardRow[] | null> => {
565567
const resp = await request(
566-
`${courseId()}/leaderboard/contests/${assessmentId}/get_popular_vote_leaderboard`,
568+
`${courseId()}/assessments/${assessmentId}/${visibleEntries}/popularVoteLeaderboard`,
567569
'GET',
568570
{
569571
...tokens
@@ -576,14 +578,14 @@ export const getContestPopularVoteLeaderboard = async (
576578

577579
const rows = await resp.json();
578580

579-
return rows.contest_popular.map(
581+
return rows.leaderboard.map(
580582
(row: any): ContestLeaderboardRow => ({
581583
rank: row.rank,
582-
name: row.name,
583-
username: row.username,
584-
score: row.score,
584+
name: row.student_name,
585+
username: row.student_username,
586+
score: row.final_score,
585587
avatar: '',
586-
code: row.code,
588+
code: row.answer,
587589
submissionId: row.submission_id,
588590
votingId: rows.voting_id
589591
})
@@ -1348,7 +1350,16 @@ export const getScoreLeaderboard = async (
13481350
return null; // invalid accessToken _and_ refreshToken
13491351
}
13501352
const scoreLeaderboard = await resp.json();
1351-
return scoreLeaderboard as ContestEntry[];
1353+
1354+
return scoreLeaderboard.leaderboard.map(
1355+
(row: any): ContestEntry => ({
1356+
rank: row.rank,
1357+
student_name: row.student_name,
1358+
final_score: row.final_score,
1359+
answer: row.answer,
1360+
submission_id: row.submission_id
1361+
})
1362+
);
13521363
};
13531364

13541365
/**
@@ -1370,7 +1381,15 @@ export const getPopularVoteLeaderboard = async (
13701381
return null; // invalid accessToken _and_ refreshToken
13711382
}
13721383
const popularVoteLeaderboard = await resp.json();
1373-
return popularVoteLeaderboard as ContestEntry[];
1384+
return popularVoteLeaderboard.leaderboard.map(
1385+
(row: any): ContestEntry => ({
1386+
rank: row.rank,
1387+
student_name: row.student_name,
1388+
final_score: row.final_score,
1389+
answer: row.answer,
1390+
submission_id: row.submission_id
1391+
})
1392+
);
13741393
};
13751394

13761395
/**

src/features/leaderboard/LeaderboardActions.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@ const LeaderboardActions = createActions('leaderboard', {
1111
saveAllUsersXp: (userXp: LeaderboardRow[]) => userXp,
1212
getPaginatedLeaderboardXp: (page: number, pageSize: number) => ({ page, pageSize }),
1313
savePaginatedLeaderboardXp: (payload: { rows: LeaderboardRow[]; userCount: number }) => payload,
14-
getAllContestScores: (assessmentId: number) => assessmentId,
14+
getAllContestScores: (assessmentId: number, visibleEntries: number) => ({ assessmentId, visibleEntries }),
1515
saveAllContestScores: (contestScore: ContestLeaderboardRow[]) => contestScore,
16-
getAllContestPopularVotes: (assessmentId: number) => assessmentId,
16+
getAllContestPopularVotes: (assessmentId: number, visibleEntries: number) => ({ assessmentId, visibleEntries }),
1717
saveAllContestPopularVotes: (contestPopularVote: ContestLeaderboardRow[]) => contestPopularVote,
1818
getCode: 0,
1919
saveCode: (code: string) => code,

src/pages/leaderboard/subcomponents/ContestLeaderboard.tsx

+4-8
Original file line numberDiff line numberDiff line change
@@ -27,20 +27,19 @@ type Props = {
2727

2828
const ContestLeaderboard: React.FC<Props> = ({ type, contestID }) => {
2929
const courseID = useTypedSelector(store => store.session.courseId);
30+
const visibleEntries = useTypedSelector(store => store.session?.topContestLeaderboardDisplay ?? 10);
3031
const dispatch = useDispatch();
3132

32-
// TODO: Only display rows when contest voting counterpart has voting published
33-
3433
// Retrieve Contest Score Data from store
3534
const rankedLeaderboard: ContestLeaderboardRow[] = useTypedSelector(store =>
3635
type === 'score' ? store.leaderboard.contestScore : store.leaderboard.contestPopularVote
3736
);
3837

3938
useEffect(() => {
4039
if (type === 'score') {
41-
dispatch(LeaderboardActions.getAllContestScores(contestID));
40+
dispatch(LeaderboardActions.getAllContestScores(contestID, visibleEntries));
4241
} else {
43-
dispatch(LeaderboardActions.getAllContestPopularVotes(contestID));
42+
dispatch(LeaderboardActions.getAllContestPopularVotes(contestID, visibleEntries));
4443
}
4544
}, [dispatch, contestID, type]);
4645

@@ -65,9 +64,6 @@ const ContestLeaderboard: React.FC<Props> = ({ type, contestID }) => {
6564
}, []);
6665

6766
// Display constants
68-
const visibleEntries = useTypedSelector(store => store.session.topContestLeaderboardDisplay);
69-
// const top3 = rankedLeaderboard.slice(0, 3);
70-
// const rest = rankedLeaderboard.slice(3, Number(visibleEntries));
7167
const top3 = rankedLeaderboard.filter(row => row.rank <= 3);
7268
const rest = rankedLeaderboard
7369
.filter(row => row.rank <= Number(visibleEntries))
@@ -168,7 +164,7 @@ const ContestLeaderboard: React.FC<Props> = ({ type, contestID }) => {
168164
<LeaderboardDropdown contests={contestDetails} />
169165

170166
{/* Export Button */}
171-
<LeaderboardExportButton type={type} contest={contestName} data={rankedLeaderboard} />
167+
<LeaderboardExportButton type={type} contest={contestName} contestID={contestID}/>
172168
</div>
173169

174170
{/* Leaderboard Table (Top 3) */}

src/pages/leaderboard/subcomponents/LeaderboardExportButton.tsx

+38-8
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,48 @@
11
import 'src/styles/Leaderboard.scss';
22

3-
import React from 'react';
3+
import React, { useEffect, useState } from 'react';
44
import { useTypedSelector } from 'src/commons/utils/Hooks';
55
import { ContestLeaderboardRow, LeaderboardRow } from 'src/features/leaderboard/LeaderboardTypes';
66

77
import { Role } from '../../../commons/application/ApplicationTypes';
8+
import { useDispatch } from 'react-redux';
9+
import LeaderboardActions from 'src/features/leaderboard/LeaderboardActions';
810

9-
type Props =
10-
| { type: string; contest: string | undefined; data: ContestLeaderboardRow[] | undefined }
11-
| { type: string; contest: string | undefined; data: LeaderboardRow[] };
11+
type Props = {
12+
type: string
13+
contest?: string
14+
contestID?: number
15+
}
16+
17+
const LeaderboardExportButton: React.FC<Props> = ({ type, contest, contestID }) => {
18+
19+
// Retrieve relevant leaderboard data
20+
const [ exportRequested, setExportRequest ] = useState(false);
21+
const dispatch = useDispatch();
22+
const data = (type == "overall")
23+
? useTypedSelector(store => store.leaderboard.userXp)
24+
: (type == "score")
25+
? useTypedSelector(store => store.leaderboard.contestScore)
26+
: useTypedSelector(store => store.leaderboard.contestPopularVote);
27+
28+
const visibleEntries = useTypedSelector(store => store.session?.topContestLeaderboardDisplay ?? 10);
29+
30+
const onExportClick = () => {
31+
// Dispatch relevant request
32+
if (type == "overall") dispatch(LeaderboardActions.getAllUsersXp());
33+
else if (type == "score") dispatch(LeaderboardActions.getAllContestScores(contestID as number, visibleEntries));
34+
else dispatch(LeaderboardActions.getAllContestPopularVotes(contestID as number, visibleEntries));
35+
setExportRequest(true)
36+
}
37+
38+
// Return the CSV when requested and data is loaded
39+
useEffect(() => {
40+
if (exportRequested) {
41+
exportCSV();
42+
setExportRequest(false); // Clear request
43+
}
44+
}, [data])
1245

13-
const LeaderboardExportButton: React.FC<Props> = ({ type, contest, data }) => {
14-
// pls remove this
15-
if (!data) return;
1646
const role = useTypedSelector(store => store.session.role);
1747
const exportCSV = () => {
1848
const headers = [
@@ -48,7 +78,7 @@ const LeaderboardExportButton: React.FC<Props> = ({ type, contest, data }) => {
4878
};
4979

5080
return role === Role.Admin || role === Role.Staff ? (
51-
<button onClick={exportCSV} className="export-button">
81+
<button onClick={onExportClick} className="export-button">
5282
Export as .csv
5383
</button>
5484
) : (

src/pages/leaderboard/subcomponents/OverallLeaderboard.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ const OverallLeaderboard: React.FC = () => {
9494

9595
const paginatedLeaderboard: { rows: LeaderboardRow[]; userCount: number } = useTypedSelector(store => store.leaderboard.paginatedUserXp);
9696
const pageSize = 25;
97-
const visibleEntries = useTypedSelector(store => store.session.topLeaderboardDisplay) ?? Number.MAX_SAFE_INTEGER;
97+
const visibleEntries = useTypedSelector(store => store.session?.topLeaderboardDisplay ?? Number.MAX_SAFE_INTEGER);
9898
const [top3Leaderboard, setTop3Leaderboard] = useState<LeaderboardRow[]>([]);
9999

100100
useEffect(() => {
@@ -158,7 +158,7 @@ const OverallLeaderboard: React.FC = () => {
158158
<LeaderboardDropdown contests={contestDetails} />
159159

160160
{/* Export Button */}
161-
<LeaderboardExportButton type="overall" contest={undefined} data={undefined} />
161+
<LeaderboardExportButton type="overall" />
162162
</div>
163163

164164
{/* Leaderboard Table (Replaced with ag-Grid) */}

0 commit comments

Comments
 (0)