From 4ea93157b11db7b20e4b27f0654a6bcb600eb445 Mon Sep 17 00:00:00 2001 From: sayomaki Date: Thu, 7 Aug 2025 16:59:50 +0800 Subject: [PATCH 01/12] Remove unused comments --- .../leaderboard/subcomponents/OverallLeaderboard.tsx | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/src/pages/leaderboard/subcomponents/OverallLeaderboard.tsx b/src/pages/leaderboard/subcomponents/OverallLeaderboard.tsx index 86a8205ba7..f96fd1e0e1 100644 --- a/src/pages/leaderboard/subcomponents/OverallLeaderboard.tsx +++ b/src/pages/leaderboard/subcomponents/OverallLeaderboard.tsx @@ -79,15 +79,7 @@ const OverallLeaderboard: React.FC = () => { headerName: 'XP', width: 414 /*154*/, sortable: true - } /*, - { - field: 'achievements', - suppressMovable: true, - sortable: false, - headerName: 'Achievements', - width: 260 } - */ ], [] ); @@ -102,7 +94,7 @@ const OverallLeaderboard: React.FC = () => { const [top3Leaderboard, setTop3Leaderboard] = useState([]); useEffect(() => { - dispatch(LeaderboardActions.getPaginatedLeaderboardXp(1, pageSize)); + dispatch(LeaderboardActions.getOverallLeaderboardXP(1, pageSize)); }, [dispatch]); const latestParamsRef = useRef(null); @@ -114,7 +106,7 @@ const OverallLeaderboard: React.FC = () => { const pageSize = endRow - startRow; const page = startRow / pageSize + 1; - dispatch(LeaderboardActions.getPaginatedLeaderboardXp(page, pageSize)); + dispatch(LeaderboardActions.getOverallLeaderboardXP(page, pageSize)); // Params stored to prevent re-rendering latestParamsRef.current = params; From ea1fdfdd1f63bc474c6e70dba161b178db909cc3 Mon Sep 17 00:00:00 2001 From: sayomaki Date: Thu, 7 Aug 2025 17:03:13 +0800 Subject: [PATCH 02/12] Make direct requests for exporting leaderboard instead of caching in store --- .../subcomponents/LeaderboardExportButton.tsx | 79 +++++++++---------- 1 file changed, 36 insertions(+), 43 deletions(-) diff --git a/src/pages/leaderboard/subcomponents/LeaderboardExportButton.tsx b/src/pages/leaderboard/subcomponents/LeaderboardExportButton.tsx index 5e69749353..c6680c4557 100644 --- a/src/pages/leaderboard/subcomponents/LeaderboardExportButton.tsx +++ b/src/pages/leaderboard/subcomponents/LeaderboardExportButton.tsx @@ -1,12 +1,15 @@ import 'src/styles/Leaderboard.scss'; -import React, { useEffect, useMemo, useState } from 'react'; -import { useDispatch } from 'react-redux'; +import React from 'react'; +import { Role } from 'src/commons/application/ApplicationTypes'; +import { + getAllOverallLeaderboardXP, + getContestPopularVoteLeaderboard, + getContestScoreLeaderboard +} from 'src/commons/sagas/RequestsSaga'; import { useTypedSelector } from 'src/commons/utils/Hooks'; -import LeaderboardActions from 'src/features/leaderboard/LeaderboardActions'; import { ContestLeaderboardRow, LeaderboardRow } from 'src/features/leaderboard/LeaderboardTypes'; - -import { Role } from '../../../commons/application/ApplicationTypes'; +import { store } from 'src/pages/createStore'; type Props = { type: string; @@ -15,43 +18,34 @@ type Props = { }; const LeaderboardExportButton: React.FC = ({ type, contest, contestID }) => { - // Retrieve relevant leaderboard data - const [exportRequested, setExportRequest] = useState(false); - const dispatch = useDispatch(); - const selectData = (type: string) => { - switch (type) { - case 'overall': - return (store: { leaderboard: { userXp: any } }) => store.leaderboard.userXp; - case 'score': - return (store: { leaderboard: { contestScore: any } }) => store.leaderboard.contestScore; - default: - return (store: { leaderboard: { contestPopularVote: any } }) => - store.leaderboard.contestPopularVote; - } - }; - - const selector = useMemo(() => selectData(type), [type]); - const data = useTypedSelector(selector); - - const visibleEntries = Number.MAX_SAFE_INTEGER; + const role = useTypedSelector(store => store.session.role); - const onExportClick = () => { - // Dispatch relevant request - if (type == 'overall') dispatch(LeaderboardActions.getAllUsersXp()); - else if (type == 'score') - dispatch(LeaderboardActions.getAllContestScores(contestID as number, visibleEntries)); - else - dispatch(LeaderboardActions.getAllContestPopularVotes(contestID as number, visibleEntries)); - setExportRequest(true); - }; + const onExportClick = async () => { + const accessToken = store.getState().session.accessToken!; + const refreshToken = store.getState().session.refreshToken!; + const tokens = { accessToken, refreshToken }; - // Return the CSV when requested and data is loaded - useEffect(() => { - if (exportRequested) { - exportCSV(); - setExportRequest(false); // Clear request + if (type === 'overall') { + const resp = await getAllOverallLeaderboardXP(tokens); + if (resp) { + exportCSV(resp); + } + } else if (type === 'score') { + const resp = await getContestScoreLeaderboard(contestID!, Number.MAX_SAFE_INTEGER, tokens); + if (resp) { + exportCSV(resp); + } + } else if (type === 'popularvote') { + const resp = await getContestPopularVoteLeaderboard( + contestID!, + Number.MAX_SAFE_INTEGER, + tokens + ); + if (resp) { + exportCSV(resp); + } } - }, [data]); + }; const escapeCodeField = (value: any) => { const str = value?.toString() ?? ''; @@ -59,8 +53,7 @@ const LeaderboardExportButton: React.FC = ({ type, contest, contestID }) return `"${escaped}"`; }; - const role = useTypedSelector(store => store.session.role); - const exportCSV = () => { + const exportCSV = (data: any[]) => { const headers = [ 'Rank', 'Name', @@ -68,7 +61,7 @@ const LeaderboardExportButton: React.FC = ({ type, contest, contestID }) type === 'overall' ? 'XP' : 'Score', type === 'overall' ? 'Achievements' : 'Code' ]; - const rows = data?.map( + const rows = data.map( (player: { rank: any; name: any; @@ -110,7 +103,7 @@ const LeaderboardExportButton: React.FC = ({ type, contest, contestID }) return role === Role.Admin || role === Role.Staff ? ( ) : ( '' From cc9ddac2f89bc2ae2b49656d4eb272e8338ec573 Mon Sep 17 00:00:00 2001 From: sayomaki Date: Thu, 7 Aug 2025 17:06:01 +0800 Subject: [PATCH 03/12] Remove action and reducer to get all users for overall leaderboards --- src/commons/sagas/LeaderboardSaga.ts | 11 ----------- src/commons/sagas/RequestsSaga.ts | 8 +++++--- src/features/leaderboard/LeaderboardActions.ts | 2 -- src/features/leaderboard/LeaderboardReducer.ts | 3 --- 4 files changed, 5 insertions(+), 19 deletions(-) diff --git a/src/commons/sagas/LeaderboardSaga.ts b/src/commons/sagas/LeaderboardSaga.ts index 6e4214336d..b9402e31a0 100644 --- a/src/commons/sagas/LeaderboardSaga.ts +++ b/src/commons/sagas/LeaderboardSaga.ts @@ -7,23 +7,12 @@ import { actions } from '../utils/ActionsHelper'; import { selectTokens } from './BackendSaga'; import { getAllContests, - getAllTotalXp, getContestPopularVoteLeaderboard, getContestScoreLeaderboard, getPaginatedTotalXp } from './RequestsSaga'; const LeaderboardSaga = combineSagaHandlers({ - [LeaderboardActions.getAllUsersXp.type]: function* () { - const tokens: Tokens = yield selectTokens(); - - const usersXp = yield call(getAllTotalXp, tokens); - - if (usersXp) { - yield put(actions.saveAllUsersXp(usersXp)); - } - }, - [LeaderboardActions.getPaginatedLeaderboardXp.type]: function* (action) { const tokens: Tokens = yield selectTokens(); const { page, pageSize } = action.payload; diff --git a/src/commons/sagas/RequestsSaga.ts b/src/commons/sagas/RequestsSaga.ts index d127f93a82..dd66c20b59 100644 --- a/src/commons/sagas/RequestsSaga.ts +++ b/src/commons/sagas/RequestsSaga.ts @@ -463,10 +463,12 @@ export const getTotalXp = async (tokens: Tokens, courseRegId?: number): Promise< }; /** - * GET /courses/{courseId}/all_user_xp + * GET /courses/{courseId}/leaderboards/xp_all */ -export const getAllTotalXp = async (tokens: Tokens): Promise => { - const resp = await request(`${courseId()}/all_users_xp`, 'GET', { +export const getAllOverallLeaderboardXP = async ( + tokens: Tokens +): Promise => { + const resp = await request(`${courseId()}/leaderboards/xp_all`, 'GET', { ...tokens }); diff --git a/src/features/leaderboard/LeaderboardActions.ts b/src/features/leaderboard/LeaderboardActions.ts index 493f89a706..35f2e4493e 100644 --- a/src/features/leaderboard/LeaderboardActions.ts +++ b/src/features/leaderboard/LeaderboardActions.ts @@ -7,8 +7,6 @@ import { } from './LeaderboardTypes'; const LeaderboardActions = createActions('leaderboard', { - getAllUsersXp: 0, - saveAllUsersXp: (userXp: LeaderboardRow[]) => userXp, getPaginatedLeaderboardXp: (page: number, pageSize: number) => ({ page, pageSize }), savePaginatedLeaderboardXp: (payload: { rows: LeaderboardRow[]; userCount: number }) => payload, getAllContestScores: (assessmentId: number, visibleEntries: number) => ({ diff --git a/src/features/leaderboard/LeaderboardReducer.ts b/src/features/leaderboard/LeaderboardReducer.ts index afe542dcf9..a6b694752a 100644 --- a/src/features/leaderboard/LeaderboardReducer.ts +++ b/src/features/leaderboard/LeaderboardReducer.ts @@ -9,9 +9,6 @@ export const LeaderboardReducer: Reducer = c defaultLeaderboard, builder => { builder - .addCase(LeaderboardActions.saveAllUsersXp, (state, action) => { - state.userXp = action.payload; - }) .addCase(LeaderboardActions.savePaginatedLeaderboardXp, (state, action) => { state.paginatedUserXp = { rows: action.payload.rows || [], From 653954a2e5ab0a534aa883c6eedaa3c767fdc8cc Mon Sep 17 00:00:00 2001 From: sayomaki Date: Thu, 7 Aug 2025 17:07:40 +0800 Subject: [PATCH 04/12] Rename paginated overall leaderboard XP to just overall leaderboard XP --- src/commons/sagas/LeaderboardSaga.ts | 8 ++++---- src/commons/sagas/RequestsSaga.ts | 6 +++--- src/features/leaderboard/LeaderboardActions.ts | 4 ++-- src/features/leaderboard/LeaderboardReducer.ts | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/commons/sagas/LeaderboardSaga.ts b/src/commons/sagas/LeaderboardSaga.ts index b9402e31a0..5e5e93117c 100644 --- a/src/commons/sagas/LeaderboardSaga.ts +++ b/src/commons/sagas/LeaderboardSaga.ts @@ -9,18 +9,18 @@ import { getAllContests, getContestPopularVoteLeaderboard, getContestScoreLeaderboard, - getPaginatedTotalXp + getOverallLeaderboardXP } from './RequestsSaga'; const LeaderboardSaga = combineSagaHandlers({ - [LeaderboardActions.getPaginatedLeaderboardXp.type]: function* (action) { + [LeaderboardActions.getOverallLeaderboardXP.type]: function* (action) { const tokens: Tokens = yield selectTokens(); const { page, pageSize } = action.payload; - const paginatedUsersXp = yield call(getPaginatedTotalXp, page, pageSize, tokens); + const paginatedUsersXp = yield call(getOverallLeaderboardXP, page, pageSize, tokens); if (paginatedUsersXp) { - yield put(actions.savePaginatedLeaderboardXp(paginatedUsersXp)); + yield put(actions.saveOverallLeaderboardXP(paginatedUsersXp)); } }, diff --git a/src/commons/sagas/RequestsSaga.ts b/src/commons/sagas/RequestsSaga.ts index dd66c20b59..d3b5f3ad74 100644 --- a/src/commons/sagas/RequestsSaga.ts +++ b/src/commons/sagas/RequestsSaga.ts @@ -491,16 +491,16 @@ export const getAllOverallLeaderboardXP = async ( }; /** - * GET /courses/{courseId}/get_paginated_display + * GET /courses/{courseId}/leaderboards/xp */ -export const getPaginatedTotalXp = async ( +export const getOverallLeaderboardXP = async ( page: number, pageSize: number, tokens: Tokens ): Promise<{ rows: LeaderboardRow[]; userCount: number } | null> => { const offset = (page - 1) * pageSize; const params = new URLSearchParams({ offset: `${offset}`, page_size: `${pageSize}` }); - const resp = await request(`${courseId()}/get_paginated_display?${params.toString()}`, 'GET', { + const resp = await request(`${courseId()}/leaderboards/xp?${params.toString()}`, 'GET', { ...tokens }); diff --git a/src/features/leaderboard/LeaderboardActions.ts b/src/features/leaderboard/LeaderboardActions.ts index 35f2e4493e..38a1014725 100644 --- a/src/features/leaderboard/LeaderboardActions.ts +++ b/src/features/leaderboard/LeaderboardActions.ts @@ -7,8 +7,8 @@ import { } from './LeaderboardTypes'; const LeaderboardActions = createActions('leaderboard', { - getPaginatedLeaderboardXp: (page: number, pageSize: number) => ({ page, pageSize }), - savePaginatedLeaderboardXp: (payload: { rows: LeaderboardRow[]; userCount: number }) => payload, + getOverallLeaderboardXP: (page: number, pageSize: number) => ({ page, pageSize }), + saveOverallLeaderboardXP: (payload: { rows: LeaderboardRow[]; userCount: number }) => payload, getAllContestScores: (assessmentId: number, visibleEntries: number) => ({ assessmentId, visibleEntries diff --git a/src/features/leaderboard/LeaderboardReducer.ts b/src/features/leaderboard/LeaderboardReducer.ts index a6b694752a..6ff1813373 100644 --- a/src/features/leaderboard/LeaderboardReducer.ts +++ b/src/features/leaderboard/LeaderboardReducer.ts @@ -9,7 +9,7 @@ export const LeaderboardReducer: Reducer = c defaultLeaderboard, builder => { builder - .addCase(LeaderboardActions.savePaginatedLeaderboardXp, (state, action) => { + .addCase(LeaderboardActions.saveOverallLeaderboardXP, (state, action) => { state.paginatedUserXp = { rows: action.payload.rows || [], userCount: action.payload.userCount || 0 From 923551d7493b2633fcecf64621475f1bcf532bd6 Mon Sep 17 00:00:00 2001 From: sayomaki Date: Thu, 7 Aug 2025 17:08:27 +0800 Subject: [PATCH 05/12] Rename endpoints for contest leaderboards --- src/commons/sagas/RequestsSaga.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/commons/sagas/RequestsSaga.ts b/src/commons/sagas/RequestsSaga.ts index d3b5f3ad74..aae566ba9e 100644 --- a/src/commons/sagas/RequestsSaga.ts +++ b/src/commons/sagas/RequestsSaga.ts @@ -525,7 +525,7 @@ export const getOverallLeaderboardXP = async ( }; /** - * GET /courses/{courseId}/assessments/{assessmentid}/scoreLeaderboard + * GET /courses/{courseId}/assessments/{assessmentid}/contest_score_leaderboard */ export const getContestScoreLeaderboard = async ( assessmentId: number, @@ -534,7 +534,7 @@ export const getContestScoreLeaderboard = async ( ): Promise => { const params = new URLSearchParams({ visible_entries: `${visibleEntries}` }); const resp = await request( - `${courseId()}/assessments/${assessmentId}/scoreLeaderboard?${params.toString()}`, + `${courseId()}/assessments/${assessmentId}/contest_score_leaderboard?${params.toString()}`, 'GET', { ...tokens @@ -562,7 +562,7 @@ export const getContestScoreLeaderboard = async ( }; /** - * GET /courses/{courseId}/assessments/{assessmentid}/popularVoteLeaderboard + * GET /courses/{courseId}/assessments/{assessmentid}/contest_popular_leaderboard */ export const getContestPopularVoteLeaderboard = async ( assessmentId: number, @@ -571,7 +571,7 @@ export const getContestPopularVoteLeaderboard = async ( ): Promise => { const params = new URLSearchParams({ visible_entries: `${visibleEntries}` }); const resp = await request( - `${courseId()}/assessments/${assessmentId}/popularVoteLeaderboard?${params.toString()}`, + `${courseId()}/assessments/${assessmentId}/contest_popular_leaderboard?${params.toString()}`, 'GET', { ...tokens From a9db428dc4b5f4363ef3ad4425ed0a8ccdf98682 Mon Sep 17 00:00:00 2001 From: sayomaki Date: Thu, 7 Aug 2025 17:11:57 +0800 Subject: [PATCH 06/12] Remove unused leaderboard state fields --- src/commons/application/ApplicationTypes.ts | 1 - src/features/leaderboard/LeaderboardTypes.ts | 1 - 2 files changed, 2 deletions(-) diff --git a/src/commons/application/ApplicationTypes.ts b/src/commons/application/ApplicationTypes.ts index fb56e1d6a2..f2c6740a32 100644 --- a/src/commons/application/ApplicationTypes.ts +++ b/src/commons/application/ApplicationTypes.ts @@ -348,7 +348,6 @@ export const defaultAchievement: AchievementState = { }; export const defaultLeaderboard: LeaderboardState = { - userXp: [], paginatedUserXp: { rows: [], userCount: 0 }, contestScore: [], contestPopularVote: [], diff --git a/src/features/leaderboard/LeaderboardTypes.ts b/src/features/leaderboard/LeaderboardTypes.ts index bac5d8188f..ae6caf5f8b 100644 --- a/src/features/leaderboard/LeaderboardTypes.ts +++ b/src/features/leaderboard/LeaderboardTypes.ts @@ -19,7 +19,6 @@ export type ContestLeaderboardRow = { }; export type LeaderboardState = { - userXp: LeaderboardRow[]; paginatedUserXp: { rows: LeaderboardRow[]; userCount: number }; contestScore: ContestLeaderboardRow[]; contestPopularVote: ContestLeaderboardRow[]; From cb66c4258acec46bcfe60f1ce047ddef5349d93e Mon Sep 17 00:00:00 2001 From: sayomaki Date: Thu, 7 Aug 2025 17:22:37 +0800 Subject: [PATCH 07/12] Rename request endpoints for contest-related operations --- src/commons/sagas/RequestsSaga.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/commons/sagas/RequestsSaga.ts b/src/commons/sagas/RequestsSaga.ts index aae566ba9e..764fa59012 100644 --- a/src/commons/sagas/RequestsSaga.ts +++ b/src/commons/sagas/RequestsSaga.ts @@ -1302,14 +1302,14 @@ export const deleteSourcecastEntry = async ( }; /** - * POST /courses/{courseId}/admin/assessments/{assessmentId}/calculateContestScore + * POST /courses/{courseId}/admin/assessments/{assessmentId}/contest_calculate_score */ export const calculateContestScore = async ( assessmentId: number, tokens: Tokens ): Promise => { const resp = await request( - `${courseId()}/admin/assessments/${assessmentId}/calculateContestScore`, + `${courseId()}/admin/assessments/${assessmentId}/contest_calculate_score`, 'POST', { ...tokens @@ -1320,14 +1320,14 @@ export const calculateContestScore = async ( }; /** - * POST /courses/{courseId}/admin/assessments/{assessmentId}/dispatchContestXp + * POST /courses/{courseId}/admin/assessments/{assessmentId}/contest_dispatch_xp */ export const dispatchContestXp = async ( assessmentId: number, tokens: Tokens ): Promise => { const resp = await request( - `${courseId()}/admin/assessments/${assessmentId}/dispatchContestXp`, + `${courseId()}/admin/assessments/${assessmentId}/contest_dispatch_xp`, 'POST', { ...tokens @@ -1338,7 +1338,7 @@ export const dispatchContestXp = async ( }; /** - * GET /courses/{courseId}/assessments/{assessmentId}/scoreLeaderboard + * GET /courses/{courseId}/assessments/{assessmentId}/contest_score_leaderboard */ export const getScoreLeaderboard = async ( assessmentId: number, @@ -1347,7 +1347,7 @@ export const getScoreLeaderboard = async ( ): Promise => { const params = new URLSearchParams({ visible_entries: `${visibleEntries}` }); const resp = await request( - `${courseId()}/assessments/${assessmentId}/scoreLeaderboard?${params.toString()}`, + `${courseId()}/assessments/${assessmentId}/contest_score_leaderboard?${params.toString()}`, 'GET', { ...tokens @@ -1370,7 +1370,7 @@ export const getScoreLeaderboard = async ( }; /** - * GET /courses/{courseId}/assessments/{assessmentId}/popularVoteLeaderboard + * GET /courses/{courseId}/assessments/{assessmentId}/contest_popular_leaderboard */ export const getPopularVoteLeaderboard = async ( assessmentId: number, @@ -1379,7 +1379,7 @@ export const getPopularVoteLeaderboard = async ( ): Promise => { const params = new URLSearchParams({ visible_entries: `${visibleEntries}` }); const resp = await request( - `${courseId()}/assessments/${assessmentId}/popularVoteLeaderboard?${params.toString()}`, + `${courseId()}/assessments/${assessmentId}/contest_popular_leaderboard?${params.toString()}`, 'GET', { ...tokens From 61a65c716f89c091f91d87c88359005fa32907a3 Mon Sep 17 00:00:00 2001 From: sayomaki Date: Thu, 7 Aug 2025 17:23:01 +0800 Subject: [PATCH 08/12] Remove toggles and inputs for leaderboard during course creation --- src/commons/dropdown/DropdownCreateCourse.tsx | 62 ------------------- 1 file changed, 62 deletions(-) diff --git a/src/commons/dropdown/DropdownCreateCourse.tsx b/src/commons/dropdown/DropdownCreateCourse.tsx index 668e595a25..2d37ce7eb9 100644 --- a/src/commons/dropdown/DropdownCreateCourse.tsx +++ b/src/commons/dropdown/DropdownCreateCourse.tsx @@ -40,10 +40,6 @@ const DropdownCreateCourse: React.FC = props => { enableAchievements: true, enableSourcecast: true, enableStories: false, - enableOverallLeaderboard: true, - enableContestLeaderboard: true, - topLeaderboardDisplay: 100, - topContestLeaderboardDisplay: 10, sourceChapter: Chapter.SOURCE_1, sourceVariant: Variant.DEFAULT, moduleHelpText: '' @@ -201,28 +197,6 @@ const DropdownCreateCourse: React.FC = props => { }) } /> - - setCourseConfig({ - ...courseConfig, - enableOverallLeaderboard: (e.target as HTMLInputElement).checked - }) - } - /> - - setCourseConfig({ - ...courseConfig, - enableContestLeaderboard: (e.target as HTMLInputElement).checked - }) - } - />
= props => { />
-
- - - setCourseConfig({ - ...courseConfig, - topLeaderboardDisplay: Number(e.target.value) - }) - } - /> - -
-
- - - setCourseConfig({ - ...courseConfig, - topContestLeaderboardDisplay: Number(e.target.value) - }) - } - /> - -
Date: Thu, 7 Aug 2025 17:25:18 +0800 Subject: [PATCH 09/12] Rename visibleEntries to count --- src/commons/sagas/RequestsSaga.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/commons/sagas/RequestsSaga.ts b/src/commons/sagas/RequestsSaga.ts index 764fa59012..245d155cf0 100644 --- a/src/commons/sagas/RequestsSaga.ts +++ b/src/commons/sagas/RequestsSaga.ts @@ -529,10 +529,10 @@ export const getOverallLeaderboardXP = async ( */ export const getContestScoreLeaderboard = async ( assessmentId: number, - visibleEntries: number, + count: number, tokens: Tokens ): Promise => { - const params = new URLSearchParams({ visible_entries: `${visibleEntries}` }); + const params = new URLSearchParams({ count: `${count}` }); const resp = await request( `${courseId()}/assessments/${assessmentId}/contest_score_leaderboard?${params.toString()}`, 'GET', @@ -566,10 +566,10 @@ export const getContestScoreLeaderboard = async ( */ export const getContestPopularVoteLeaderboard = async ( assessmentId: number, - visibleEntries: number, + count: number, tokens: Tokens ): Promise => { - const params = new URLSearchParams({ visible_entries: `${visibleEntries}` }); + const params = new URLSearchParams({ count: `${count}` }); const resp = await request( `${courseId()}/assessments/${assessmentId}/contest_popular_leaderboard?${params.toString()}`, 'GET', @@ -1342,10 +1342,10 @@ export const dispatchContestXp = async ( */ export const getScoreLeaderboard = async ( assessmentId: number, - visibleEntries: number | undefined, + count: number | undefined, tokens: Tokens ): Promise => { - const params = new URLSearchParams({ visible_entries: `${visibleEntries}` }); + const params = new URLSearchParams({ count: `${count}` }); const resp = await request( `${courseId()}/assessments/${assessmentId}/contest_score_leaderboard?${params.toString()}`, 'GET', @@ -1374,10 +1374,10 @@ export const getScoreLeaderboard = async ( */ export const getPopularVoteLeaderboard = async ( assessmentId: number, - visibleEntries: number | undefined, + count: number | undefined, tokens: Tokens ): Promise => { - const params = new URLSearchParams({ visible_entries: `${visibleEntries}` }); + const params = new URLSearchParams({ count: `${count}` }); const resp = await request( `${courseId()}/assessments/${assessmentId}/contest_popular_leaderboard?${params.toString()}`, 'GET', From e415676b7eab3dcad43c4288850d06c9fa9b4637 Mon Sep 17 00:00:00 2001 From: sayomaki Date: Thu, 7 Aug 2025 18:24:24 +0800 Subject: [PATCH 10/12] Show leaderboards only after assessment has been published --- .../AssessmentWorkspace.tsx | 57 ++++++++++--------- 1 file changed, 31 insertions(+), 26 deletions(-) diff --git a/src/commons/assessmentWorkspace/AssessmentWorkspace.tsx b/src/commons/assessmentWorkspace/AssessmentWorkspace.tsx index 1348535ddc..7f0229a897 100644 --- a/src/commons/assessmentWorkspace/AssessmentWorkspace.tsx +++ b/src/commons/assessmentWorkspace/AssessmentWorkspace.tsx @@ -464,6 +464,7 @@ const AssessmentWorkspace: React.FC = props => { const isTeamAssessment = assessmentOverview !== undefined ? assessmentOverview.maxTeamSize > 1 : false; const isContestVoting = question?.type === QuestionTypes.voting; + const isPublished = assessmentOverview?.isPublished; const handleContestEntryClick = (_submissionId: number, answer: string) => { // TODO: Hardcoded to make use of the first editor tab. Refactoring is needed for this workspace to enable Folder mode. handleEditorValueChange(0, answer); @@ -538,34 +539,38 @@ const AssessmentWorkspace: React.FC = props => { /> ), id: SideContentType.contestVoting - }, - { - label: 'Score Leaderboard', - iconName: IconNames.CROWN, - body: ( - - ), - id: SideContentType.scoreLeaderboard - }, - { - label: 'Popular Vote Leaderboard', - iconName: IconNames.PEOPLE, - body: ( - - ), - id: SideContentType.popularVoteLeaderboard } ); + if (isPublished) { + tabs.push( + { + label: 'Score Leaderboard', + iconName: IconNames.CROWN, + body: ( + + ), + id: SideContentType.scoreLeaderboard + }, + { + label: 'Popular Vote Leaderboard', + iconName: IconNames.PEOPLE, + body: ( + + ), + id: SideContentType.popularVoteLeaderboard + } + ); + } } else { tabs.push( { From d6eafea02c836ee8c3b6bb44414ebfc72c02f1c6 Mon Sep 17 00:00:00 2001 From: sayomaki Date: Thu, 7 Aug 2025 18:39:18 +0800 Subject: [PATCH 11/12] Update tests after hiding leaderboards in contest --- .../AssessmentWorkspace.test.tsx.snap | 468 ------------------ 1 file changed, 468 deletions(-) diff --git a/src/commons/assessmentWorkspace/__tests__/__snapshots__/AssessmentWorkspace.test.tsx.snap b/src/commons/assessmentWorkspace/__tests__/__snapshots__/AssessmentWorkspace.test.tsx.snap index d77ea5e3a6..6a309933cd 100644 --- a/src/commons/assessmentWorkspace/__tests__/__snapshots__/AssessmentWorkspace.test.tsx.snap +++ b/src/commons/assessmentWorkspace/__tests__/__snapshots__/AssessmentWorkspace.test.tsx.snap @@ -499,82 +499,6 @@ exports[`AssessmentWorkspace > AssessmentWorkspace page with ContestVoting quest
- -
AssessmentWorkspace page with ContestVoting quest
- - From c87956e532da6f750d8e0567409550f6ae5c0bdf Mon Sep 17 00:00:00 2001 From: sayomaki Date: Fri, 8 Aug 2025 12:49:15 +0800 Subject: [PATCH 12/12] Use session hook instead of accessing individually --- .../subcomponents/LeaderboardExportButton.tsx | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/pages/leaderboard/subcomponents/LeaderboardExportButton.tsx b/src/pages/leaderboard/subcomponents/LeaderboardExportButton.tsx index c6680c4557..0181b67b69 100644 --- a/src/pages/leaderboard/subcomponents/LeaderboardExportButton.tsx +++ b/src/pages/leaderboard/subcomponents/LeaderboardExportButton.tsx @@ -7,9 +7,8 @@ import { getContestPopularVoteLeaderboard, getContestScoreLeaderboard } from 'src/commons/sagas/RequestsSaga'; -import { useTypedSelector } from 'src/commons/utils/Hooks'; +import { useSession } from 'src/commons/utils/Hooks'; import { ContestLeaderboardRow, LeaderboardRow } from 'src/features/leaderboard/LeaderboardTypes'; -import { store } from 'src/pages/createStore'; type Props = { type: string; @@ -18,12 +17,10 @@ type Props = { }; const LeaderboardExportButton: React.FC = ({ type, contest, contestID }) => { - const role = useTypedSelector(store => store.session.role); + const { role, accessToken, refreshToken } = useSession(); const onExportClick = async () => { - const accessToken = store.getState().session.accessToken!; - const refreshToken = store.getState().session.refreshToken!; - const tokens = { accessToken, refreshToken }; + const tokens = { accessToken: accessToken!, refreshToken: refreshToken! }; if (type === 'overall') { const resp = await getAllOverallLeaderboardXP(tokens);