Skip to content

Commit f4ee0b6

Browse files
CheongYeeMingLuYiting0913RichDom2185
authored
Add Team Assessments (#2548)
* Migrate ground control table to tanstack, without filtering and sorting functionalities * Add filter to ground control table * Remove defualt filter for ground control table * Add team size column for ground control table * Add ID to ground control table * Update team size edit cell * Add team size edit column for ground control * Remove console logs * Replace row-wise update button with one update button * Fix bug of team size edit cell not responding for the first click * Remove unused logs and dependencies * Fix dependency warning for ground control * Establish connection with Backend * Modify payload for postTeams API Call * Refetch after successfully creating Teams * Fix page button in team formation * Add config file * Modify response payload for fetching students * Refetch after successfully updating Teams * Add Upload CSV API Call * Update assessment workspace * Remove console logs * Add team information page to assessment workspace * Display names in team info page * Add team flag to assessment dashboard * Fix assessment overview card * Modify request to handle 409 Conflict Response * Update Upload Teams CSV format * Change minTeamSize to 0 * Add disable Save button in Assessment Workspace * Add TeamFormationOverview to SessionState * Revert changes made by Yiting * Add assessment type indication: Team or Individual * Add TeamFormationOverview to AssessmentWorkspce * Add role checks to remove error 403 * Modify GradingQuestion for team submissions * Remove console logs * Remove unused code * Resolve warning == * Add lastModifiedAt field for Answer * Add Save-Safe * Update Jest Snapshots * Write tests for FE components * Write test for SessionActions * Remove commented code * Add comments for API calls * Retrieve create team error message from BE resp * Add Workspace for TeamFormation * Update Team Formation Table Filter * Add Student Name Column Filter * Fix TeamFormation table global student name filter * Fix TeamFormation table global student name filter * Retrieve student username to grading * Resolve yarn lock * Split student names in grading editor * Fix Team Submission for GradingOverview * Fix Grading Table Header * Bump Node.js to version 20, update documentation (#2712) * Create .node-version file * Add selective WebGL dependency resolution * Bump node version in CI workflows * Fix missing dependencies in CI workflow * Update CD workflow * Install apt dependencies before building * Use checkout v3 instead of master * Replace manual caching strategy with updated setup node action * Fix typo in CLI option * Fix insufficient permissions * Update actions to v4 * Fix resolution warning * Reorganize documentation * Create separate CONTRIBUTING.md file for developer-specific items * Reorganize and reword some sections and sentences * Update and simplify README instructions Include references to `.node-version` file and Python dependency, as well as simplifying some wording. * Add Python 3.11 disclaimer * Prettier formatting rules * Yarn Eslint * Update snapshot * Fix yarn run tsc * Update snapshot * Update failing snapshots * Remove package-log.json Done as the project uses Yarn. * Restore old environment visualizer snapshots * Bump follow-redirects from 1.15.2 to 1.15.4 (#2720) Bumps [follow-redirects](https://github.com/follow-redirects/follow-redirects) from 1.15.2 to 1.15.4. - [Release notes](https://github.com/follow-redirects/follow-redirects/releases) - [Commits](follow-redirects/follow-redirects@v1.15.2...v1.15.4) --- updated-dependencies: - dependency-name: follow-redirects dependency-type: indirect ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Update field to generate new date * Remove commented code * Change student fields to array * Separate names in Team Formation Table by comma * Add Team related fields to GradingOverview * Fix yarn run tsc * Fix formatting issues * Remove commented code * Clean up some code * Remove console.log statement * Remove unused stylesheet import * Remove more commented code * Update Individual Assignment to have maxTeamSize of 1 * Remove commented code * Correct API response for GradingOverviews * Update failing snapshots * Remove unused code * Update snapshots post-merge conflict * Use CSS modules for team formation * Fix incorrect minimum team size * Clean up `GroundControlEditTeamSizeCell` * Update imports * Update typings * Remove commented code * Remove unused type export * Refactor to use `useCallback` * Remove unnecessary divs, classes * Remove unnecessary styles The styles won't matter for a flex item, thus are removed. * Remove unnecessary function * Simplify variable name for readability * Revert "Remove commented code" This reverts commit 0cf1f60. * Fix data flow for editing max team size * Fix unnecessary API calls * Reran yarn install post-merge * Remove unnecessary stub types package * Fix format post-merge * Update snapshots post-merge * Remove TODO comment The default value of 1 is intentional. * Make last modified date optional * Revert "Make last modified date optional" This reverts commit 6bebb24. * Update snapshots post-merge * Migrate new action creators to RTK * Fix incorrect past merge conflict resolution * Improve codebase consistency * Update lockfile * Update snapshots post-merge * Format files post-merge * Fix errors post-merge * Restore comment * Fix incorrect condition check * Update lockfile post merge * Migrate team actions to RTK * Fix tests * Fix compile error post-merge * Fix lint --------- Co-authored-by: Lu Yiting <e0774189@u.nus.edu> Co-authored-by: Richard Dominick <34370238+RichDom2185@users.noreply.github.com>
1 parent e804445 commit f4ee0b6

File tree

56 files changed

+3606
-351
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

56 files changed

+3606
-351
lines changed

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@
6666
"react-copy-to-clipboard": "^5.1.0",
6767
"react-debounce-render": "^8.0.2",
6868
"react-dom": "^18.2.0",
69+
"react-drag-drop-files": "^2.3.10",
6970
"react-draggable": "^4.4.5",
7071
"react-dropzone": "^14.2.3",
7172
"react-hotkeys": "^2.0.0",
@@ -77,6 +78,7 @@
7778
"react-redux": "^8.1.1",
7879
"react-responsive": "^10.0.0",
7980
"react-router-dom": "^6.14.1",
81+
"react-select": "^5.7.3",
8082
"react-simple-keyboard": "^3.6.27",
8183
"react-sortable-hoc": "^2.0.0",
8284
"react-syntax-highlighter": "^15.5.0",
@@ -90,6 +92,7 @@
9092
"typesafe-actions": "^5.1.0",
9193
"unified": "^11.0.0",
9294
"uuid": "^9.0.0",
95+
"xlsx": "0.16.4",
9396
"xml2js": "^0.6.0",
9497
"yareco": "^0.1.5"
9598
},

src/commons/XMLParser/XMLParserHelper.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,8 @@ const makeAssessmentOverview = (result: any, maxXpVal: number): AssessmentOvervi
8484
status: AssessmentStatuses.attempting,
8585
story: rawOverview.story,
8686
xp: 0,
87-
gradingStatus: 'none' as GradingStatuses
87+
gradingStatus: 'none' as GradingStatuses,
88+
maxTeamSize: 1
8889
};
8990
};
9091

@@ -202,6 +203,7 @@ const makeProgramming = (
202203
testcases: publicTestcases.map(testcase => makeTestcase(testcase)),
203204
testcasesPrivate: privateTestcases.map(testcase => makeTestcase(testcase)),
204205
answer: solution ? (solution[0] as string).trim() : '',
206+
lastModifiedAt: new Date().toISOString(),
205207
type: 'programming'
206208
};
207209
if (problem.SNIPPET[0].GRADER) {

src/commons/application/ApplicationTypes.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -405,6 +405,19 @@ export const defaultWorkspaceManager: WorkspaceManagerState = {
405405
currentQuestion: undefined,
406406
hasUnsavedChanges: false
407407
},
408+
teamFormation: {
409+
...createDefaultWorkspace('teamFormation'),
410+
teamFormationTableFilters: {
411+
columnFilters: [],
412+
globalFilter: null
413+
}
414+
},
415+
groundControl: {
416+
...createDefaultWorkspace('groundControl'),
417+
GroundControlTableFilters: {
418+
columnFilters: []
419+
}
420+
},
408421
playground: {
409422
...createDefaultWorkspace('playground'),
410423
usingSubst: false,
@@ -500,6 +513,8 @@ export const defaultSession: SessionState = {
500513
sessionId: Date.now(),
501514
githubOctokitObject: { octokit: undefined },
502515
gradingOverviews: undefined,
516+
students: undefined,
517+
teamFormationOverviews: undefined,
503518
gradings: new Map<number, GradingQuery>(),
504519
notifications: []
505520
};
@@ -539,6 +554,8 @@ export const defaultSideContentManager: SideContentManagerState = {
539554
assessment: defaultSideContent,
540555
grading: defaultSideContent,
541556
playground: defaultSideContent,
557+
groundControl: defaultSideContent,
558+
teamFormation: defaultSideContent,
542559
sicp: defaultSideContent,
543560
sourcecast: defaultSideContent,
544561
sourcereel: defaultSideContent,

src/commons/application/actions/SessionActions.ts

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@ import {
33
paginationToBackendParams,
44
ungradedToBackendParams
55
} from 'src/features/grading/GradingUtils';
6+
import { OptionType } from 'src/pages/academy/teamFormation/subcomponents/TeamFormationForm';
67

78
import { GradingOverviews, GradingQuery } from '../../../features/grading/GradingTypes';
9+
import { TeamFormationOverview } from '../../../features/teamFormation/TeamFormationTypes';
810
import {
911
Assessment,
1012
AssessmentConfiguration,
@@ -20,8 +22,12 @@ import { Role } from '../ApplicationTypes';
2022
import {
2123
ACKNOWLEDGE_NOTIFICATIONS,
2224
AdminPanelCourseRegistration,
25+
BULK_UPLOAD_TEAM,
26+
CHECK_ANSWER_LAST_MODIFIED_AT,
2327
CourseRegistration,
28+
CREATE_TEAM,
2429
DELETE_ASSESSMENT_CONFIG,
30+
DELETE_TEAM,
2531
DELETE_TIME_OPTIONS,
2632
DELETE_USER_COURSE_REGISTRATION,
2733
FETCH_ADMIN_PANEL_COURSE_REGISTRATIONS,
@@ -36,6 +42,9 @@ import {
3642
FETCH_GRADING_OVERVIEWS,
3743
FETCH_NOTIFICATION_CONFIGS,
3844
FETCH_NOTIFICATIONS,
45+
FETCH_STUDENTS,
46+
FETCH_TEAM_FORMATION_OVERVIEW,
47+
FETCH_TEAM_FORMATION_OVERVIEWS,
3948
FETCH_TOTAL_XP,
4049
FETCH_TOTAL_XP_ADMIN,
4150
FETCH_USER_AND_COURSE,
@@ -77,6 +86,10 @@ import {
7786
UPDATE_NOTIFICATION_CONFIG,
7887
UPDATE_NOTIFICATION_PREFERENCES,
7988
UPDATE_NOTIFICATIONS,
89+
UPDATE_STUDENTS,
90+
UPDATE_TEAM,
91+
UPDATE_TEAM_FORMATION_OVERVIEW,
92+
UPDATE_TEAM_FORMATION_OVERVIEWS,
8093
UPDATE_TIME_OPTIONS,
8194
UPDATE_TOTAL_XP,
8295
UPDATE_USER_ROLE,
@@ -137,6 +150,13 @@ export const fetchGradingOverviews = createAction(
137150
) => ({ payload: { filterToGroup, gradedFilter, pageParams, filterParams } })
138151
);
139152

153+
export const fetchTeamFormationOverviews = createAction(
154+
FETCH_TEAM_FORMATION_OVERVIEWS,
155+
(filterToGroup = true) => ({ payload: filterToGroup })
156+
);
157+
158+
export const fetchStudents = createAction(FETCH_STUDENTS, () => ({ payload: {} }));
159+
140160
export const login = createAction(LOGIN, (providerId: string) => ({ payload: providerId }));
141161

142162
export const logoutGoogle = createAction(LOGOUT_GOOGLE, () => ({ payload: {} }));
@@ -202,6 +222,13 @@ export const submitAnswer = createAction(
202222
(id: number, answer: string | number | ContestEntry[]) => ({ payload: { id, answer } })
203223
);
204224

225+
export const checkAnswerLastModifiedAt = createAction(
226+
CHECK_ANSWER_LAST_MODIFIED_AT,
227+
(id: number, lastModifiedAt: string, saveAnswer: Function) => ({
228+
payload: { id, lastModifiedAt, saveAnswer }
229+
})
230+
);
231+
205232
export const submitAssessment = createAction(SUBMIT_ASSESSMENT, (id: number) => ({ payload: id }));
206233

207234
export const submitGrading = createAction(
@@ -246,6 +273,46 @@ export const updateGradingOverviews = createAction(
246273
(overviews: GradingOverviews) => ({ payload: overviews })
247274
);
248275

276+
export const fetchTeamFormationOverview = createAction(
277+
FETCH_TEAM_FORMATION_OVERVIEW,
278+
(assessmentId: number) => ({ payload: { assessmentId } })
279+
);
280+
281+
export const createTeam = createAction(
282+
CREATE_TEAM,
283+
(assessment: AssessmentOverview, teams: OptionType[][]) => ({ payload: { assessment, teams } })
284+
);
285+
286+
export const updateTeam = createAction(
287+
UPDATE_TEAM,
288+
(teamId: number, assessment: AssessmentOverview, teams: OptionType[][]) => ({
289+
payload: { teamId, assessment, teams }
290+
})
291+
);
292+
293+
export const deleteTeam = createAction(DELETE_TEAM, (teamId: number) => ({ payload: { teamId } }));
294+
295+
export const bulkUploadTeam = createAction(
296+
BULK_UPLOAD_TEAM,
297+
(assessment: AssessmentOverview, file: File, students: User[] | undefined) => ({
298+
payload: { assessment, file, students }
299+
})
300+
);
301+
302+
export const updateTeamFormationOverviews = createAction(
303+
UPDATE_TEAM_FORMATION_OVERVIEWS,
304+
(overviews: TeamFormationOverview[]) => ({ payload: overviews })
305+
);
306+
307+
export const updateTeamFormationOverview = createAction(
308+
UPDATE_TEAM_FORMATION_OVERVIEW,
309+
(overview: TeamFormationOverview) => ({ payload: overview })
310+
);
311+
312+
export const updateStudents = createAction(UPDATE_STUDENTS, (students: User[]) => ({
313+
payload: students
314+
}));
315+
249316
/**
250317
* An extra id parameter is included here because of
251318
* no id for Grading.

src/commons/application/actions/__tests__/SessionActions.ts

Lines changed: 90 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import { Chapter, Variant } from 'js-slang/dist/types';
2+
import { mockStudents } from 'src/commons/mocks/UserMocks';
23
import {
34
paginationToBackendParams,
45
ungradedToBackendParams
56
} from 'src/features/grading/GradingUtils';
67

78
import { GradingOverviews, GradingQuery } from '../../../../features/grading/GradingTypes';
9+
import { TeamFormationOverview } from '../../../../features/teamFormation/TeamFormationTypes';
810
import { Assessment, AssessmentOverview } from '../../../assessment/AssessmentTypes';
911
import { Notification } from '../../../notificationBadge/NotificationBadgeTypes';
1012
import { GameState, Role, Story } from '../../ApplicationTypes';
@@ -21,6 +23,8 @@ import {
2123
FETCH_GRADING,
2224
FETCH_GRADING_OVERVIEWS,
2325
FETCH_NOTIFICATIONS,
26+
FETCH_STUDENTS,
27+
FETCH_TEAM_FORMATION_OVERVIEWS,
2428
FETCH_USER_AND_COURSE,
2529
LOGIN,
2630
REAUTOGRADE_ANSWER,
@@ -47,7 +51,11 @@ import {
4751
UPDATE_GRADING_OVERVIEWS,
4852
UPDATE_LATEST_VIEWED_COURSE,
4953
UPDATE_NOTIFICATIONS,
50-
UPDATE_USER_ROLE
54+
UPDATE_STUDENTS,
55+
UPDATE_TEAM_FORMATION_OVERVIEW,
56+
UPDATE_TEAM_FORMATION_OVERVIEWS,
57+
UPDATE_USER_ROLE,
58+
User
5159
} from '../../types/SessionTypes';
5260
import {
5361
acknowledgeNotifications,
@@ -62,6 +70,8 @@ import {
6270
fetchGrading,
6371
fetchGradingOverviews,
6472
fetchNotifications,
73+
fetchStudents,
74+
fetchTeamFormationOverviews,
6575
fetchUserAndCourse,
6676
login,
6777
reautogradeAnswer,
@@ -88,6 +98,9 @@ import {
8898
updateGradingOverviews,
8999
updateLatestViewedCourse,
90100
updateNotifications,
101+
updateStudents,
102+
updateTeamFormationOverview,
103+
updateTeamFormationOverviews,
91104
updateUserRole
92105
} from '../SessionActions';
93106

@@ -183,6 +196,31 @@ test('fetchGradingOverviews generates correct action object', () => {
183196
});
184197
});
185198

199+
test('fetchTeamFormationOverviews generates correct default action object', () => {
200+
const action = fetchTeamFormationOverviews();
201+
expect(action).toEqual({
202+
type: FETCH_TEAM_FORMATION_OVERVIEWS,
203+
payload: true
204+
});
205+
});
206+
207+
test('fetchTeamFormationOverviews generates correct action object', () => {
208+
const filterToGroup = false;
209+
const action = fetchTeamFormationOverviews(filterToGroup);
210+
expect(action).toEqual({
211+
type: FETCH_TEAM_FORMATION_OVERVIEWS,
212+
payload: filterToGroup
213+
});
214+
});
215+
216+
test('fetchStudents generates correct action object', () => {
217+
const action = fetchStudents();
218+
expect(action).toEqual({
219+
type: FETCH_STUDENTS,
220+
payload: {}
221+
});
222+
});
223+
186224
test('fetchNotifications generates correct action object', () => {
187225
const action = fetchNotifications();
188226

@@ -217,6 +255,7 @@ test('setUser generates correct action object', () => {
217255
const user = {
218256
userId: 123,
219257
name: 'test student',
258+
username: 'test student',
220259
courses: [
221260
{
222261
courseId: 1,
@@ -501,7 +540,8 @@ test('updateAssessmentOverviews generates correct action object', () => {
501540
status: 'not_attempted',
502541
story: null,
503542
xp: 0,
504-
gradingStatus: 'none'
543+
gradingStatus: 'none',
544+
maxTeamSize: 1
505545
}
506546
];
507547
const action = updateAssessmentOverviews(overviews);
@@ -546,7 +586,9 @@ test('updateGradingOverviews generates correct action object', () => {
546586
maxXp: 500,
547587
studentId: 100,
548588
studentName: 'test student',
589+
studentNames: [],
549590
studentUsername: 'E0123456',
591+
studentUsernames: [],
550592
submissionId: 1,
551593
submissionStatus: 'attempting',
552594
groupName: 'group',
@@ -564,6 +606,52 @@ test('updateGradingOverviews generates correct action object', () => {
564606
});
565607
});
566608

609+
test('updateStudents generates correct action object', () => {
610+
const students: User[] = mockStudents;
611+
612+
const action = updateStudents(students);
613+
expect(action).toEqual({
614+
type: UPDATE_STUDENTS,
615+
payload: students
616+
});
617+
});
618+
619+
test('updateTeamFormationOverview generates correct action object', () => {
620+
const overview: TeamFormationOverview = {
621+
teamId: 0,
622+
assessmentId: 1,
623+
assessmentName: 'Mission 1',
624+
assessmentType: 'Missions',
625+
studentIds: [0],
626+
studentNames: ['Mark Henry']
627+
};
628+
629+
const action = updateTeamFormationOverview(overview);
630+
expect(action).toEqual({
631+
type: UPDATE_TEAM_FORMATION_OVERVIEW,
632+
payload: overview
633+
});
634+
});
635+
636+
test('updateTeamFormationOverviews generates correct action object', () => {
637+
const overviews: TeamFormationOverview[] = [
638+
{
639+
teamId: 0,
640+
assessmentId: 0,
641+
assessmentName: 'Mission 2',
642+
assessmentType: 'Missions',
643+
studentIds: [0],
644+
studentNames: ['Mark Henry']
645+
}
646+
];
647+
648+
const action = updateTeamFormationOverviews(overviews);
649+
expect(action).toEqual({
650+
type: UPDATE_TEAM_FORMATION_OVERVIEWS,
651+
payload: overviews
652+
});
653+
});
654+
567655
test('updateGrading generates correct action object', () => {
568656
const submissionId = 3;
569657
const grading: GradingQuery = {

src/commons/application/reducers/SessionsReducer.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ import {
2626
UPDATE_GRADING,
2727
UPDATE_GRADING_OVERVIEWS,
2828
UPDATE_NOTIFICATIONS,
29+
UPDATE_STUDENTS,
30+
UPDATE_TEAM_FORMATION_OVERVIEW,
31+
UPDATE_TEAM_FORMATION_OVERVIEWS,
2932
UPDATE_TOTAL_XP
3033
} from '../types/SessionTypes';
3134

@@ -122,6 +125,21 @@ export const SessionsReducer: Reducer<SessionState, SourceActionType> = (
122125
...state,
123126
notifications: action.payload
124127
};
128+
case UPDATE_STUDENTS:
129+
return {
130+
...state,
131+
students: action.payload
132+
};
133+
case UPDATE_TEAM_FORMATION_OVERVIEWS:
134+
return {
135+
...state,
136+
teamFormationOverviews: action.payload
137+
};
138+
case UPDATE_TEAM_FORMATION_OVERVIEW:
139+
return {
140+
...state,
141+
teamFormationOverview: action.payload
142+
};
125143
case REMOTE_EXEC_UPDATE_DEVICES:
126144
return {
127145
...state,

0 commit comments

Comments
 (0)