Skip to content

Commit db9a11e

Browse files
committed
refactor: remove progress page calculation logic and use data from backend
1 parent eacc04d commit db9a11e

File tree

8 files changed

+26
-182
lines changed

8 files changed

+26
-182
lines changed

src/course-home/data/api.js

Lines changed: 1 addition & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -3,114 +3,6 @@ import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
33
import { logInfo } from '@edx/frontend-platform/logging';
44
import { appendBrowserTimezoneToUrl } from '../../utils';
55

6-
const calculateAssignmentTypeGrades = (points, visibilities, assignmentWeight, numDroppable) => {
7-
let dropCount = numDroppable;
8-
// Drop the lowest grades
9-
while (dropCount && points.length >= dropCount) {
10-
const lowestScore = Math.min(...points);
11-
const lowestScoreIndex = points.indexOf(lowestScore);
12-
points.splice(lowestScoreIndex, 1);
13-
visibilities.splice(lowestScoreIndex, 1);
14-
dropCount--;
15-
}
16-
let averageGrade = 0;
17-
let weightedGrade = 0;
18-
let totalWeightedGrade = 0;
19-
20-
if (points.length) {
21-
// Scores for visible grades (exclude never_but_include_grade)
22-
const visibleScores = points.filter(
23-
(_, idx) => visibilities[idx] !== 'never_but_include_grade',
24-
);
25-
26-
// Average all scores (for totalWeightedGrade)
27-
const overallAverage = parseFloat(
28-
(points.reduce((a, b) => a + b, 0) / points.length).toFixed(4),
29-
);
30-
totalWeightedGrade = overallAverage * assignmentWeight;
31-
if (visibleScores.length) {
32-
const visibleAverage = parseFloat(
33-
(visibleScores.reduce((a, b) => a + b, 0) / points.length).toFixed(4),
34-
);
35-
averageGrade = visibleAverage;
36-
weightedGrade = averageGrade * assignmentWeight;
37-
}
38-
}
39-
return { averageGrade, weightedGrade, totalWeightedGrade };
40-
};
41-
42-
function normalizeAssignmentPolicies(assignmentPolicies, sectionScores) {
43-
const gradeByAssignmentType = {};
44-
assignmentPolicies.forEach(assignment => {
45-
// Create an array with the number of total assignments and set the scores to 0
46-
// as placeholders for assignments that have not yet been released
47-
gradeByAssignmentType[assignment.type] = {
48-
grades: Array(assignment.numTotal).fill(0),
49-
numAssignmentsCreated: 0,
50-
numTotalExpectedAssignments: assignment.numTotal,
51-
visibility: Array(assignment.numTotal),
52-
};
53-
});
54-
55-
sectionScores.forEach((chapter) => {
56-
chapter.subsections.forEach((subsection) => {
57-
if (!(subsection.hasGradedAssignment && subsection.showGrades && subsection.numPointsPossible)) {
58-
return;
59-
}
60-
const {
61-
assignmentType,
62-
numPointsEarned,
63-
numPointsPossible,
64-
showCorrectness,
65-
} = subsection;
66-
67-
// If a subsection's assignment type does not match an assignment policy in Studio,
68-
// we won't be able to include it in this accumulation of grades by assignment type.
69-
// This may happen if a course author has removed/renamed an assignment policy in Studio and
70-
// neglected to update the subsection's of that assignment type
71-
if (!gradeByAssignmentType[assignmentType]) {
72-
return;
73-
}
74-
75-
let {
76-
numAssignmentsCreated,
77-
} = gradeByAssignmentType[assignmentType];
78-
79-
numAssignmentsCreated++;
80-
if (numAssignmentsCreated <= gradeByAssignmentType[assignmentType].numTotalExpectedAssignments) {
81-
// Remove a placeholder grade so long as the number of recorded created assignments is less than the number
82-
// of expected assignments
83-
gradeByAssignmentType[assignmentType].grades.shift();
84-
gradeByAssignmentType[assignmentType].visibility.shift();
85-
}
86-
// Add the graded assignment to the list
87-
gradeByAssignmentType[assignmentType].grades.push(numPointsEarned ? numPointsEarned / numPointsPossible : 0);
88-
// Record the created assignment
89-
gradeByAssignmentType[assignmentType].numAssignmentsCreated = numAssignmentsCreated;
90-
gradeByAssignmentType[assignmentType].visibility.push(showCorrectness);
91-
});
92-
});
93-
94-
return assignmentPolicies.map((assignment) => {
95-
const { averageGrade, weightedGrade, totalWeightedGrade } = calculateAssignmentTypeGrades(
96-
gradeByAssignmentType[assignment.type].grades,
97-
gradeByAssignmentType[assignment.type].visibility,
98-
assignment.weight,
99-
assignment.numDroppable,
100-
);
101-
102-
return {
103-
averageGrade,
104-
numDroppable: assignment.numDroppable,
105-
shortLabel: assignment.shortLabel,
106-
type: assignment.type,
107-
weight: assignment.weight,
108-
weightedGrade,
109-
totalWeightedGrade,
110-
};
111-
});
112-
}
113-
1146
/**
1157
* Tweak the metadata for consistency
1168
* @param metadata the data to normalize
@@ -256,11 +148,7 @@ export async function getProgressTabData(courseId, targetUserId) {
256148
try {
257149
const { data } = await getAuthenticatedHttpClient().get(url);
258150
const camelCasedData = camelCaseObject(data);
259-
260-
camelCasedData.gradingPolicy.assignmentPolicies = normalizeAssignmentPolicies(
261-
camelCasedData.gradingPolicy.assignmentPolicies,
262-
camelCasedData.sectionScores,
263-
);
151+
camelCasedData.gradingPolicy.assignmentPolicies = camelCasedData.assignmentTypeGradeSummary;
264152

265153
// We replace gradingPolicy.gradeRange with the original data to preserve the intended casing for the grade.
266154
// For example, if a grade range key is "A", we do not want it to be camel cased (i.e. "A" would become "a")

src/course-home/progress-tab/grades/course-grade/CourseGradeFooter.jsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { useModel } from '../../../../generic/model-store';
99
import GradeRangeTooltip from './GradeRangeTooltip';
1010
import messages from '../messages';
1111
import { getLatestDueDateInFuture } from '../../utils';
12+
import { assert } from 'joi';
1213

1314
const ResponsiveText = ({
1415
wideScreen, children, hasLetterGrades, passingGrade,
@@ -48,12 +49,12 @@ const CourseGradeFooter = ({ passingGrade }) => {
4849
const courseId = useContextId();
4950

5051
const {
52+
assignmentTypeGradeSummary,
5153
courseGrade: { isPassing, letterGrade },
5254
gradingPolicy: { gradeRange },
53-
sectionScores,
5455
} = useModel('progress', courseId);
5556

56-
const latestDueDate = getLatestDueDateInFuture(sectionScores);
57+
const latestDueDate = getLatestDueDateInFuture(assignmentTypeGradeSummary);
5758
const wideScreen = useWindowSize().width >= breakpoints.medium.minWidth;
5859
const hasLetterGrades = Object.keys(gradeRange).length > 1;
5960

src/course-home/progress-tab/grades/course-grade/CurrentGradeTooltip.jsx

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import { OverlayTrigger, Popover } from '@openedx/paragon';
55
import { useContextId } from '../../../../data/hooks';
66

77
import { useModel } from '../../../../generic/model-store';
8-
import { areAllGradesHiddenForType, areSomeGradesHiddenForType } from '../../utils';
98

109
import messages from '../messages';
1110

@@ -14,14 +13,11 @@ const CurrentGradeTooltip = ({ tooltipClassName }) => {
1413
const courseId = useContextId();
1514

1615
const {
17-
gradingPolicy: {
18-
assignmentPolicies,
19-
},
16+
assignmentTypeGradeSummary,
2017
courseGrade: {
2118
isPassing,
2219
percent,
2320
},
24-
sectionScores,
2521
} = useModel('progress', courseId);
2622

2723
const currentGrade = Number((percent * 100).toFixed(0));
@@ -30,10 +26,7 @@ const CurrentGradeTooltip = ({ tooltipClassName }) => {
3026

3127
const isLocaleRtl = isRtl(getLocale());
3228

33-
const hasHiddenGrades = assignmentPolicies.some(
34-
(assignment) => areSomeGradesHiddenForType(assignment.type, sectionScores)
35-
|| areAllGradesHiddenForType(assignment.type, sectionScores),
36-
);
29+
const hasHiddenGrades = assignmentTypeGradeSummary.some((assignmentType) => assignmentType.hasHiddenContribution !== "none");
3730

3831
if (isLocaleRtl) {
3932
currentGradeDirection = currentGrade < 50 ? '-' : '';

src/course-home/progress-tab/grades/detailed-grades/DetailedGradesTable.jsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ const DetailedGradesTable = () => {
2222
(subsection) => !!(
2323
(showUngradedAssignments() || subsection.hasGradedAssignment)
2424
&& subsection.showGrades
25-
&& subsection.showCorrectness !== 'never_but_include_grade'
2625
&& (subsection.numPointsPossible > 0 || subsection.numPointsEarned > 0)
2726
),
2827
);

src/course-home/progress-tab/grades/grade-summary/GradeSummary.jsx

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,12 @@ const GradeSummary = () => {
1010
const courseId = useContextId();
1111

1212
const {
13-
gradingPolicy: {
14-
assignmentPolicies,
15-
},
13+
assignmentTypeGradeSummary,
1614
} = useModel('progress', courseId);
1715

1816
const [allOfSomeAssignmentTypeIsLocked, setAllOfSomeAssignmentTypeIsLocked] = useState(false);
1917

20-
if (assignmentPolicies.length === 0) {
18+
if (assignmentTypeGradeSummary.length === 0) {
2119
return null;
2220
}
2321

src/course-home/progress-tab/grades/grade-summary/GradeSummaryTable.jsx

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,13 @@ import DroppableAssignmentFootnote from './DroppableAssignmentFootnote';
1111
import GradeSummaryTableFooter from './GradeSummaryTableFooter';
1212

1313
import messages from '../messages';
14-
import { areAllGradesHiddenForType, areSomeGradesHiddenForType } from '../../utils';
1514

1615
const GradeSummaryTable = ({ setAllOfSomeAssignmentTypeIsLocked }) => {
1716
const intl = useIntl();
1817
const courseId = useContextId();
1918

2019
const {
21-
gradingPolicy: {
22-
assignmentPolicies,
23-
},
20+
assignmentTypeGradeSummary,
2421
gradesFeatureIsFullyLocked,
2522
sectionScores,
2623
} = useModel('progress', courseId);
@@ -57,7 +54,7 @@ const GradeSummaryTable = ({ setAllOfSomeAssignmentTypeIsLocked }) => {
5754
return false;
5855
};
5956

60-
const gradeSummaryData = assignmentPolicies.map((assignment) => {
57+
const gradeSummaryData = assignmentTypeGradeSummary.map((assignment) => {
6158
const {
6259
averageGrade,
6360
numDroppable,
@@ -85,10 +82,10 @@ const GradeSummaryTable = ({ setAllOfSomeAssignmentTypeIsLocked }) => {
8582
let weightedGradeDisplay = `${getGradePercent(weightedGrade)}${isLocaleRtl ? '\u200f' : ''}%`;
8683
let gradeDisplay = `${getGradePercent(averageGrade)}${isLocaleRtl ? '\u200f' : ''}%`;
8784

88-
if (areAllGradesHiddenForType(assignmentType, sectionScores)) {
85+
if (assignment.hasHiddenContribution === 'all') {
8986
gradeDisplay = <Lock data-testid="lock-icon" />;
9087
weightedGradeDisplay = <Lock data-testid="lock-icon" />;
91-
} else if (areSomeGradesHiddenForType(assignmentType, sectionScores)) {
88+
} else if (assignment.hasHiddenContribution === 'some') {
9289
gradeDisplay = `${getGradePercent(averageGrade)}${isLocaleRtl ? '\u200f' : ''}% + ${intl.formatMessage(messages.hiddenScoreLabel)}`;
9390
weightedGradeDisplay = `${getGradePercent(weightedGrade)}${isLocaleRtl ? '\u200f' : ''}% + ${intl.formatMessage(messages.hiddenScoreLabel)}`;
9491
}

src/course-home/progress-tab/grades/grade-summary/GradeSummaryTableFooter.jsx

Lines changed: 7 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -17,27 +17,19 @@ const GradeSummaryTableFooter = () => {
1717
const courseId = useContextId();
1818

1919
const {
20-
gradingPolicy: { assignmentPolicies },
20+
courseGrade: {
21+
isPassing,
22+
percent,
23+
},
24+
finalGrades,
2125
} = useModel('progress', courseId);
2226

2327
const getGradePercent = (grade) => {
2428
const percent = grade * 100;
2529
return Number.isInteger(percent) ? percent.toFixed(0) : percent.toFixed(2);
2630
};
2731

28-
let rawGrade = assignmentPolicies.reduce(
29-
(sum, { totalWeightedGrade }) => sum + totalWeightedGrade,
30-
0,
31-
);
32-
33-
rawGrade = getGradePercent(rawGrade);
34-
35-
const {
36-
courseGrade: {
37-
isPassing,
38-
percent,
39-
},
40-
} = useModel('progress', courseId);
32+
const rawGrade = getGradePercent(finalGrades);
4133

4234
const bgColor = isPassing ? 'bg-success-100' : 'bg-warning-100';
4335
const totalGrade = (percent * 100).toFixed(0);
@@ -57,7 +49,7 @@ const GradeSummaryTableFooter = () => {
5749
<Tooltip>
5850
{intl.formatMessage(
5951
messages.weightedGradeSummaryTooltip,
60-
{ roundedGrade: totalGrade, rawGrade },
52+
{ roundedGrade: totalGrade, rawGrade: rawGrade },
6153
)}
6254
</Tooltip>
6355
)}

src/course-home/progress-tab/utils.ts

Lines changed: 7 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -6,39 +6,15 @@ export const showUngradedAssignments = () => (
66
|| getConfig().SHOW_UNGRADED_ASSIGNMENT_PROGRESS === true
77
);
88

9-
// Returns the subsections for an assignment type
10-
const getSubsectionsOfType = (assignmentType, sectionScores) => (sectionScores || []).reduce((acc, chapter) => {
11-
const subs = (chapter.subsections || []).filter(
12-
(s) => s.assignmentType === assignmentType,
13-
);
14-
return acc.concat(subs);
15-
}, []);
169

17-
// Returns True if this subsection is "hidden"
18-
const isSubsectionHidden = (sub) => sub.showCorrectness === 'never_but_include_grade';
19-
20-
// Returns True if all grades are hidden for this assignment type
21-
export const areAllGradesHiddenForType = (assignmentType, sectionScores) => {
22-
const subs = getSubsectionsOfType(assignmentType, sectionScores);
23-
if (subs.length === 0) { return false; } // no subsections -> treat as not hidden
24-
return subs.every(isSubsectionHidden);
25-
};
26-
27-
// Returns True if some grades are hidden for this assignment type
28-
export const areSomeGradesHiddenForType = (assignmentType, sectionScores) => {
29-
const subs = getSubsectionsOfType(assignmentType, sectionScores);
30-
return subs.some(isSubsectionHidden) && !areAllGradesHiddenForType(assignmentType, sectionScores);
31-
};
32-
33-
export const getLatestDueDateInFuture = (sectionScores) => {
10+
export const getLatestDueDateInFuture = (assignmentTypeGradeSummary) => {
3411
let latest = null;
35-
sectionScores.forEach((chapter) => {
36-
chapter.subsections.forEach((subsection) => {
37-
if (subsection.due && (!latest || new Date(subsection.due) > new Date(latest))
38-
&& new Date(subsection.due) > new Date()) {
39-
latest = subsection.due;
40-
}
41-
});
12+
assignmentTypeGradeSummary.forEach((assignment) => {
13+
let assignmentLastGradePublishDate = assignment.lastGradePublishDate;
14+
if (assignmentLastGradePublishDate && (!latest || new Date(assignmentLastGradePublishDate) > new Date(latest))
15+
&& new Date(assignmentLastGradePublishDate) > new Date()) {
16+
latest = assignmentLastGradePublishDate;
17+
}
4218
});
4319
return latest;
4420
};

0 commit comments

Comments
 (0)