Skip to content

Commit bcdf3be

Browse files
authored
Export Leaderboards to CSV (#2892)
* Create export functions for leaderboards * Add scoreleaderboard attribute to assessment overviews * Fix imports * Create new route for fetching assessment leaderboard * Remove ground control actions and types * Abstract out export buttons * Create new request for leaderboard * Bypass saga to fetch leaderboards * Edit route to be part of admin endpoint
1 parent ff4206f commit bcdf3be

File tree

4 files changed

+126
-14
lines changed

4 files changed

+126
-14
lines changed

src/commons/sagas/RequestsSaga.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1128,6 +1128,48 @@ export const deleteSourcecastEntry = async (
11281128
return resp;
11291129
};
11301130

1131+
/**
1132+
* GET /courses/{courseId}/admin/assessments/{assessmentId}/scoreLeaderboard
1133+
*/
1134+
export const getScoreLeaderboard = async (
1135+
assessmentId: number,
1136+
tokens: Tokens
1137+
): Promise<ContestEntry[] | null> => {
1138+
const resp = await request(
1139+
`${courseId()}/admin/assessments/${assessmentId}/scoreLeaderboard`,
1140+
'GET',
1141+
{
1142+
...tokens
1143+
}
1144+
);
1145+
if (!resp || !resp.ok) {
1146+
return null; // invalid accessToken _and_ refreshToken
1147+
}
1148+
const scoreLeaderboard = await resp.json();
1149+
return scoreLeaderboard as ContestEntry[];
1150+
};
1151+
1152+
/**
1153+
* GET /courses/{courseId}/admin/assessments/{assessmentId}/popularVoteLeaderboard
1154+
*/
1155+
export const getPopularVoteLeaderboard = async (
1156+
assessmentId: number,
1157+
tokens: Tokens
1158+
): Promise<ContestEntry[] | null> => {
1159+
const resp = await request(
1160+
`${courseId()}/admin/assessments/${assessmentId}/popularVoteLeaderboard`,
1161+
'GET',
1162+
{
1163+
...tokens
1164+
}
1165+
);
1166+
if (!resp || !resp.ok) {
1167+
return null; // invalid accessToken _and_ refreshToken
1168+
}
1169+
const popularVoteLeaderboard = await resp.json();
1170+
return popularVoteLeaderboard as ContestEntry[];
1171+
};
1172+
11311173
/**
11321174
* POST /courses/{courseId}/admin/assessments/{assessmentId}
11331175
*/
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { IconNames } from '@blueprintjs/icons';
2+
import { createGrid, GridOptions } from 'ag-grid-community';
3+
import { Tokens } from 'src/commons/application/types/SessionTypes';
4+
import ControlButton from 'src/commons/ControlButton';
5+
import { getScoreLeaderboard } from 'src/commons/sagas/RequestsSaga';
6+
import { useSession } from 'src/commons/utils/Hooks';
7+
8+
type Props = {
9+
assessmentId: number;
10+
};
11+
12+
const ExportScoreLeaderboardButton: React.FC<Props> = ({ assessmentId }) => {
13+
const { accessToken, refreshToken } = useSession();
14+
const tokens = { accessToken, refreshToken } as Tokens;
15+
16+
// onClick handler for fetching score leaderboard, putting it into a grid and exporting data
17+
const exportScoreLeaderboardToCsv = async () => {
18+
const scoreLeaderbaord = await getScoreLeaderboard(assessmentId, tokens);
19+
const gridContainer = document.createElement('div');
20+
const gridOptions: GridOptions = {
21+
rowData: scoreLeaderbaord,
22+
columnDefs: [{ field: 'student_name' }, { field: 'answer' }, { field: 'final_score' }]
23+
};
24+
const api = createGrid(gridContainer, gridOptions);
25+
api.exportDataAsCsv();
26+
api.destroy();
27+
};
28+
29+
return (
30+
<div className="control-button-container">
31+
<ControlButton
32+
icon={IconNames.PEOPLE}
33+
onClick={exportScoreLeaderboardToCsv}
34+
label="Export Score Leaderboard"
35+
/>
36+
</div>
37+
);
38+
};
39+
40+
export default ExportScoreLeaderboardButton;
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { IconNames } from '@blueprintjs/icons';
2+
import { createGrid, GridOptions } from 'ag-grid-community';
3+
import { Tokens } from 'src/commons/application/types/SessionTypes';
4+
import ControlButton from 'src/commons/ControlButton';
5+
import { getPopularVoteLeaderboard } from 'src/commons/sagas/RequestsSaga';
6+
import { useSession } from 'src/commons/utils/Hooks';
7+
8+
type Props = {
9+
assessmentId: number;
10+
};
11+
12+
const ExportVoteLeaderboardButton: React.FC<Props> = ({ assessmentId }) => {
13+
const { accessToken, refreshToken } = useSession();
14+
const tokens = { accessToken, refreshToken } as Tokens;
15+
16+
// onClick handler for fetching popular vote leaderboard, putting it into a grid and exporting data
17+
const exportPopularVoteLeaderboardToCsv = async () => {
18+
const popularVoteLeaderboard = await getPopularVoteLeaderboard(assessmentId, tokens);
19+
const gridContainer = document.createElement('div');
20+
const gridOptions: GridOptions = {
21+
rowData: popularVoteLeaderboard,
22+
columnDefs: [{ field: 'student_name' }, { field: 'answer' }, { field: 'final_score' }]
23+
};
24+
const api = createGrid(gridContainer, gridOptions);
25+
api.exportDataAsCsv();
26+
api.destroy();
27+
};
28+
29+
return (
30+
<div className="control-button-container">
31+
<ControlButton
32+
icon={IconNames.PEOPLE}
33+
onClick={exportPopularVoteLeaderboardToCsv}
34+
label="Export Popular Vote Leaderboard"
35+
/>
36+
</div>
37+
);
38+
};
39+
40+
export default ExportVoteLeaderboardButton;

src/pages/academy/groundControl/subcomponents/GroundControlConfigureCell.tsx

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ import React, { useCallback, useState } from 'react';
1313

1414
import { AssessmentOverview } from '../../../../commons/assessment/AssessmentTypes';
1515
import ControlButton from '../../../../commons/ControlButton';
16+
import ExportScoreLeaderboardButton from '../configureControls/ExportScoreLeaderboardButton';
17+
import ExportVoteLeaderboardButton from '../configureControls/ExportVoteLeaderboardButton';
1618
import AssignEntriesButton from './configureControls/AssignEntriesButton';
1719

1820
type Props = {
@@ -112,20 +114,8 @@ const ConfigureCell: React.FC<Props> = ({
112114
/>
113115
<Collapse isOpen={hasVotingFeatures}>
114116
<div className="voting-related-controls">
115-
<div className="control-button-container">
116-
<ControlButton
117-
icon={IconNames.PEOPLE}
118-
isDisabled={true}
119-
label="Export Popular Vote Leaderboard (Coming soon!)"
120-
/>
121-
</div>
122-
<div className="control-button-container">
123-
<ControlButton
124-
icon={IconNames.CROWN}
125-
isDisabled={true}
126-
label="Export Score Leaderboard (Coming soon!)"
127-
/>
128-
</div>
117+
<ExportScoreLeaderboardButton assessmentId={data.id} />
118+
<ExportVoteLeaderboardButton assessmentId={data.id} />
129119
<AssignEntriesButton
130120
handleAssignEntriesForVoting={handleAssignEntriesForVoting}
131121
assessmentId={data.id}

0 commit comments

Comments
 (0)