Skip to content

Commit 4859269

Browse files
committed
Refactor leaderboard saga and actions for improved readability and maintainability; update ContestLeaderboard and OverallLeaderboard components for better state management and data retrieval.
1 parent 7c07b48 commit 4859269

File tree

5 files changed

+95
-48
lines changed

5 files changed

+95
-48
lines changed

src/commons/sagas/LeaderboardSaga.ts

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@ import { selectTokens } from './BackendSaga';
88
import {
99
getAllContests,
1010
getAllTotalXp,
11-
getPaginatedTotalXp,
1211
getContestPopularVoteLeaderboard,
13-
getContestScoreLeaderboard
12+
getContestScoreLeaderboard,
13+
getPaginatedTotalXp
1414
} from './RequestsSaga';
1515

1616
const LeaderboardSaga = combineSagaHandlers(LeaderboardActions, {
@@ -39,7 +39,12 @@ const LeaderboardSaga = combineSagaHandlers(LeaderboardActions, {
3939
const tokens: Tokens = yield selectTokens();
4040
const { assessmentId, visibleEntries } = action.payload;
4141

42-
const contestScores = yield call(getContestScoreLeaderboard, assessmentId, visibleEntries, tokens);
42+
const contestScores = yield call(
43+
getContestScoreLeaderboard,
44+
assessmentId,
45+
visibleEntries,
46+
tokens
47+
);
4348

4449
if (contestScores) {
4550
yield put(actions.saveAllContestScores(contestScores));
@@ -50,7 +55,12 @@ const LeaderboardSaga = combineSagaHandlers(LeaderboardActions, {
5055
const tokens: Tokens = yield selectTokens();
5156
const { assessmentId, visibleEntries } = action.payload;
5257

53-
const contestPopularVotes = yield call(getContestPopularVoteLeaderboard, assessmentId, visibleEntries, tokens);
58+
const contestPopularVotes = yield call(
59+
getContestPopularVoteLeaderboard,
60+
assessmentId,
61+
visibleEntries,
62+
tokens
63+
);
5464
if (contestPopularVotes) {
5565
yield put(actions.saveAllContestPopularVotes(contestPopularVotes));
5666
}

src/features/leaderboard/LeaderboardActions.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,15 @@ 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, visibleEntries: number) => ({ assessmentId, visibleEntries }),
14+
getAllContestScores: (assessmentId: number, visibleEntries: number) => ({
15+
assessmentId,
16+
visibleEntries
17+
}),
1518
saveAllContestScores: (contestScore: ContestLeaderboardRow[]) => contestScore,
16-
getAllContestPopularVotes: (assessmentId: number, visibleEntries: number) => ({ assessmentId, visibleEntries }),
19+
getAllContestPopularVotes: (assessmentId: number, visibleEntries: number) => ({
20+
assessmentId,
21+
visibleEntries
22+
}),
1723
saveAllContestPopularVotes: (contestPopularVote: ContestLeaderboardRow[]) => contestPopularVote,
1824
getCode: 0,
1925
saveCode: (code: string) => code,

src/pages/leaderboard/subcomponents/ContestLeaderboard.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,9 @@ 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);
30+
const visibleEntries = useTypedSelector(
31+
store => store.session?.topContestLeaderboardDisplay ?? 10
32+
);
3133
const dispatch = useDispatch();
3234

3335
// Retrieve Contest Score Data from store
@@ -164,7 +166,7 @@ const ContestLeaderboard: React.FC<Props> = ({ type, contestID }) => {
164166
<LeaderboardDropdown contests={contestDetails} />
165167

166168
{/* Export Button */}
167-
<LeaderboardExportButton type={type} contest={contestName} contestID={contestID}/>
169+
<LeaderboardExportButton type={type} contest={contestName} contestID={contestID} />
168170
</div>
169171

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

src/pages/leaderboard/subcomponents/LeaderboardExportButton.tsx

Lines changed: 55 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,57 @@
11
import 'src/styles/Leaderboard.scss';
22

3-
import React, { useEffect, useState } from 'react';
3+
import React, { useEffect, useMemo, useState } from 'react';
4+
import { useDispatch } from 'react-redux';
45
import { useTypedSelector } from 'src/commons/utils/Hooks';
6+
import LeaderboardActions from 'src/features/leaderboard/LeaderboardActions';
57
import { ContestLeaderboardRow, LeaderboardRow } from 'src/features/leaderboard/LeaderboardTypes';
68

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

1111
type Props = {
12-
type: string
13-
contest?: string
14-
contestID?: number
15-
}
12+
type: string;
13+
contest?: string;
14+
contestID?: number;
15+
};
1616

1717
const LeaderboardExportButton: React.FC<Props> = ({ type, contest, contestID }) => {
18-
1918
// Retrieve relevant leaderboard data
20-
const [ exportRequested, setExportRequest ] = useState(false);
19+
const [exportRequested, setExportRequest] = useState(false);
2120
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);
21+
const selectData = (type: string) => {
22+
switch (type) {
23+
case 'overall':
24+
return (store: { leaderboard: { userXp: any } }) => store.leaderboard.userXp;
25+
case 'score':
26+
return (store: { leaderboard: { contestScore: any } }) => store.leaderboard.contestScore;
27+
default:
28+
return (store: { leaderboard: { contestPopularVote: any } }) =>
29+
store.leaderboard.contestPopularVote;
30+
}
31+
};
32+
33+
const selector = useMemo(() => selectData(type), [type]);
34+
const data = useTypedSelector(selector);
2735

2836
const visibleEntries = Number.MAX_SAFE_INTEGER;
2937

3038
const onExportClick = () => {
3139
// 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-
}
40+
if (type == 'overall') dispatch(LeaderboardActions.getAllUsersXp());
41+
else if (type == 'score')
42+
dispatch(LeaderboardActions.getAllContestScores(contestID as number, visibleEntries));
43+
else
44+
dispatch(LeaderboardActions.getAllContestPopularVotes(contestID as number, visibleEntries));
45+
setExportRequest(true);
46+
};
3747

3848
// Return the CSV when requested and data is loaded
3949
useEffect(() => {
4050
if (exportRequested) {
4151
exportCSV();
4252
setExportRequest(false); // Clear request
4353
}
44-
}, [data])
54+
}, [data]);
4555

4656
const role = useTypedSelector(store => store.session.role);
4757
const exportCSV = () => {
@@ -52,18 +62,33 @@ const LeaderboardExportButton: React.FC<Props> = ({ type, contest, contestID })
5262
type === 'overall' ? 'XP' : 'Score',
5363
type === 'overall' ? 'Achievements' : 'Submission Id'
5464
];
55-
const rows = data?.map(player => [
56-
player.rank,
57-
player.name,
58-
player.username,
59-
type === 'overall' ? (player as LeaderboardRow).xp : (player as ContestLeaderboardRow).score,
60-
type === 'overall'
61-
? (player as LeaderboardRow).achievements
62-
: (player as ContestLeaderboardRow).submissionId
63-
]);
65+
const rows = data?.map(
66+
(player: {
67+
rank: any;
68+
name: any;
69+
username: any;
70+
xp?: number;
71+
avatar?: string;
72+
achievements?: string;
73+
score?: number;
74+
code?: string;
75+
submissionId?: number;
76+
votingId?: number;
77+
}) => [
78+
player.rank,
79+
player.name,
80+
player.username,
81+
type === 'overall'
82+
? (player as LeaderboardRow).xp
83+
: (player as ContestLeaderboardRow).score,
84+
type === 'overall'
85+
? (player as LeaderboardRow).achievements
86+
: (player as ContestLeaderboardRow).submissionId
87+
]
88+
);
6489

6590
// Combine headers and rows
66-
const csvContent = [headers.join(','), ...rows.map(row => row.join(','))].join('\n');
91+
const csvContent = [headers.join(','), ...rows.map((row: any[]) => row.join(','))].join('\n');
6792

6893
const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
6994
const link = document.createElement('a');

src/pages/leaderboard/subcomponents/OverallLeaderboard.tsx

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -92,13 +92,17 @@ const OverallLeaderboard: React.FC = () => {
9292
[]
9393
);
9494

95-
const paginatedLeaderboard: { rows: LeaderboardRow[]; userCount: number } = useTypedSelector(store => store.leaderboard.paginatedUserXp);
95+
const paginatedLeaderboard: { rows: LeaderboardRow[]; userCount: number } = useTypedSelector(
96+
store => store.leaderboard.paginatedUserXp
97+
);
9698
const pageSize = 25;
97-
const visibleEntries = useTypedSelector(store => store.session?.topLeaderboardDisplay ?? Number.MAX_SAFE_INTEGER);
99+
const visibleEntries = useTypedSelector(
100+
store => store.session?.topLeaderboardDisplay ?? Number.MAX_SAFE_INTEGER
101+
);
98102
const [top3Leaderboard, setTop3Leaderboard] = useState<LeaderboardRow[]>([]);
99103

100104
useEffect(() => {
101-
dispatch(LeaderboardActions.getPaginatedLeaderboardXp(1, pageSize))
105+
dispatch(LeaderboardActions.getPaginatedLeaderboardXp(1, pageSize));
102106
}, [dispatch]);
103107

104108
const latestParamsRef = useRef<any>(null);
@@ -108,27 +112,27 @@ const OverallLeaderboard: React.FC = () => {
108112
const endRow = params.endRow;
109113

110114
const pageSize = endRow - startRow;
111-
const page = startRow / pageSize + 1
115+
const page = startRow / pageSize + 1;
112116

113117
dispatch(LeaderboardActions.getPaginatedLeaderboardXp(page, pageSize));
114118

115119
// Params stored to prevent re-rendering
116120
latestParamsRef.current = params;
117-
},
121+
}
118122
});
119123

120124
useEffect(() => {
121-
if (
122-
latestParamsRef.current &&
123-
paginatedLeaderboard.rows.length > 0
124-
) {
125+
if (latestParamsRef.current && paginatedLeaderboard.rows.length > 0) {
125126
const { successCallback } = latestParamsRef.current;
126127

127128
if (latestParamsRef.current.startRow === 0) {
128129
setTop3Leaderboard(paginatedLeaderboard.rows.slice(0, 3));
129130
}
130131

131-
successCallback(paginatedLeaderboard.rows, Math.min(paginatedLeaderboard.userCount, visibleEntries));
132+
successCallback(
133+
paginatedLeaderboard.rows,
134+
Math.min(paginatedLeaderboard.userCount, visibleEntries)
135+
);
132136
latestParamsRef.current = null;
133137
}
134138
}, [paginatedLeaderboard]);

0 commit comments

Comments
 (0)