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/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( { 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
- - 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) - }) - } - /> - -
=> { - 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 }); @@ -489,16 +491,16 @@ export const getAllTotalXp = async (tokens: Tokens): Promise => { }; /** - * 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 }); @@ -523,16 +525,16 @@ export const getPaginatedTotalXp = async ( }; /** - * GET /courses/{courseId}/assessments/{assessmentid}/scoreLeaderboard + * GET /courses/{courseId}/assessments/{assessmentid}/contest_score_leaderboard */ 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}/scoreLeaderboard?${params.toString()}`, + `${courseId()}/assessments/${assessmentId}/contest_score_leaderboard?${params.toString()}`, 'GET', { ...tokens @@ -560,16 +562,16 @@ export const getContestScoreLeaderboard = async ( }; /** - * GET /courses/{courseId}/assessments/{assessmentid}/popularVoteLeaderboard + * GET /courses/{courseId}/assessments/{assessmentid}/contest_popular_leaderboard */ 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}/popularVoteLeaderboard?${params.toString()}`, + `${courseId()}/assessments/${assessmentId}/contest_popular_leaderboard?${params.toString()}`, 'GET', { ...tokens @@ -1300,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 @@ -1318,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 @@ -1336,16 +1338,16 @@ export const dispatchContestXp = async ( }; /** - * GET /courses/{courseId}/assessments/{assessmentId}/scoreLeaderboard + * GET /courses/{courseId}/assessments/{assessmentId}/contest_score_leaderboard */ 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}/scoreLeaderboard?${params.toString()}`, + `${courseId()}/assessments/${assessmentId}/contest_score_leaderboard?${params.toString()}`, 'GET', { ...tokens @@ -1368,16 +1370,16 @@ export const getScoreLeaderboard = async ( }; /** - * GET /courses/{courseId}/assessments/{assessmentId}/popularVoteLeaderboard + * GET /courses/{courseId}/assessments/{assessmentId}/contest_popular_leaderboard */ 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}/popularVoteLeaderboard?${params.toString()}`, + `${courseId()}/assessments/${assessmentId}/contest_popular_leaderboard?${params.toString()}`, 'GET', { ...tokens diff --git a/src/features/leaderboard/LeaderboardActions.ts b/src/features/leaderboard/LeaderboardActions.ts index 493f89a706..38a1014725 100644 --- a/src/features/leaderboard/LeaderboardActions.ts +++ b/src/features/leaderboard/LeaderboardActions.ts @@ -7,10 +7,8 @@ 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, + 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 afe542dcf9..6ff1813373 100644 --- a/src/features/leaderboard/LeaderboardReducer.ts +++ b/src/features/leaderboard/LeaderboardReducer.ts @@ -9,10 +9,7 @@ export const LeaderboardReducer: Reducer = c defaultLeaderboard, builder => { builder - .addCase(LeaderboardActions.saveAllUsersXp, (state, action) => { - state.userXp = action.payload; - }) - .addCase(LeaderboardActions.savePaginatedLeaderboardXp, (state, action) => { + .addCase(LeaderboardActions.saveOverallLeaderboardXP, (state, action) => { state.paginatedUserXp = { rows: action.payload.rows || [], userCount: action.payload.userCount || 0 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[]; diff --git a/src/pages/leaderboard/subcomponents/LeaderboardExportButton.tsx b/src/pages/leaderboard/subcomponents/LeaderboardExportButton.tsx index 5e69749353..0181b67b69 100644 --- a/src/pages/leaderboard/subcomponents/LeaderboardExportButton.tsx +++ b/src/pages/leaderboard/subcomponents/LeaderboardExportButton.tsx @@ -1,13 +1,15 @@ import 'src/styles/Leaderboard.scss'; -import React, { useEffect, useMemo, useState } from 'react'; -import { useDispatch } from 'react-redux'; -import { useTypedSelector } from 'src/commons/utils/Hooks'; -import LeaderboardActions from 'src/features/leaderboard/LeaderboardActions'; +import React from 'react'; +import { Role } from 'src/commons/application/ApplicationTypes'; +import { + getAllOverallLeaderboardXP, + getContestPopularVoteLeaderboard, + getContestScoreLeaderboard +} from 'src/commons/sagas/RequestsSaga'; +import { useSession } from 'src/commons/utils/Hooks'; import { ContestLeaderboardRow, LeaderboardRow } from 'src/features/leaderboard/LeaderboardTypes'; -import { Role } from '../../../commons/application/ApplicationTypes'; - type Props = { type: string; contest?: string; @@ -15,43 +17,32 @@ 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 { role, accessToken, refreshToken } = useSession(); - const visibleEntries = Number.MAX_SAFE_INTEGER; + const onExportClick = async () => { + const tokens = { accessToken: accessToken!, refreshToken: refreshToken! }; - 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); - }; - - // 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 +50,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 +58,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 +100,7 @@ const LeaderboardExportButton: React.FC = ({ type, contest, contestID }) return role === Role.Admin || role === Role.Staff ? ( ) : ( '' 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;