From ae994780dad99b7b06952e9f4a1f3f81f1c44241 Mon Sep 17 00:00:00 2001 From: KristinAoki Date: Wed, 18 Dec 2024 15:17:19 -0500 Subject: [PATCH 1/3] feat: updae grade summary to show floating point grades --- .../grade-summary/GradeSummaryHeader.jsx | 31 +++++-------- .../grade-summary/GradeSummaryTable.jsx | 15 +++---- .../grade-summary/GradeSummaryTableFooter.jsx | 44 ++++++++++++++----- .../progress-tab/grades/messages.ts | 6 ++- 4 files changed, 53 insertions(+), 43 deletions(-) diff --git a/src/course-home/progress-tab/grades/grade-summary/GradeSummaryHeader.jsx b/src/course-home/progress-tab/grades/grade-summary/GradeSummaryHeader.jsx index fc860c10fa..99eb6e82c1 100644 --- a/src/course-home/progress-tab/grades/grade-summary/GradeSummaryHeader.jsx +++ b/src/course-home/progress-tab/grades/grade-summary/GradeSummaryHeader.jsx @@ -1,49 +1,39 @@ -import React, { useState } from 'react'; import { useSelector } from 'react-redux'; import PropTypes from 'prop-types'; -import { injectIntl, intlShape } from '@edx/frontend-platform/i18n'; -import { - Icon, IconButton, OverlayTrigger, Popover, -} from '@openedx/paragon'; +import { useIntl } from '@edx/frontend-platform/i18n'; +import { Icon, OverlayTrigger, Tooltip } from '@openedx/paragon'; import { Blocked, InfoOutline } from '@openedx/paragon/icons'; import messages from '../messages'; import { useModel } from '../../../../generic/model-store'; -const GradeSummaryHeader = ({ intl, allOfSomeAssignmentTypeIsLocked }) => { +const GradeSummaryHeader = ({ allOfSomeAssignmentTypeIsLocked }) => { + const intl = useIntl(); const { courseId, } = useSelector(state => state.courseHome); const { gradesFeatureIsFullyLocked, } = useModel('progress', courseId); - const [showTooltip, setShowTooltip] = useState(false); return (

{intl.formatMessage(messages.gradeSummary)}

- - {intl.formatMessage(messages.gradeSummaryTooltipBody)} - - + + {intl.formatMessage(messages.gradeSummaryTooltipBody)} + )} > - { setShowTooltip(!showTooltip); }} - onBlur={() => { setShowTooltip(false); }} + {!gradesFeatureIsFullyLocked && allOfSomeAssignmentTypeIsLocked && ( @@ -57,8 +47,7 @@ const GradeSummaryHeader = ({ intl, allOfSomeAssignmentTypeIsLocked }) => { }; GradeSummaryHeader.propTypes = { - intl: intlShape.isRequired, allOfSomeAssignmentTypeIsLocked: PropTypes.bool.isRequired, }; -export default injectIntl(GradeSummaryHeader); +export default GradeSummaryHeader; diff --git a/src/course-home/progress-tab/grades/grade-summary/GradeSummaryTable.jsx b/src/course-home/progress-tab/grades/grade-summary/GradeSummaryTable.jsx index 628a65e24a..fa1b4775c5 100644 --- a/src/course-home/progress-tab/grades/grade-summary/GradeSummaryTable.jsx +++ b/src/course-home/progress-tab/grades/grade-summary/GradeSummaryTable.jsx @@ -1,10 +1,7 @@ -import React from 'react'; import PropTypes from 'prop-types'; import { useSelector } from 'react-redux'; -import { - getLocale, injectIntl, intlShape, isRtl, -} from '@edx/frontend-platform/i18n'; +import { getLocale, isRtl, useIntl } from '@edx/frontend-platform/i18n'; import { DataTable } from '@openedx/paragon'; import { useModel } from '../../../../generic/model-store'; @@ -14,7 +11,8 @@ import GradeSummaryTableFooter from './GradeSummaryTableFooter'; import messages from '../messages'; -const GradeSummaryTable = ({ intl, setAllOfSomeAssignmentTypeIsLocked }) => { +const GradeSummaryTable = ({ setAllOfSomeAssignmentTypeIsLocked }) => { + const intl = useIntl(); const { courseId, } = useSelector(state => state.courseHome); @@ -75,8 +73,8 @@ const GradeSummaryTable = ({ intl, setAllOfSomeAssignmentTypeIsLocked }) => { footnoteId, footnoteMarker, type: assignment.type, locked, }, weight: { weight: `${(assignment.weight * 100).toFixed(0)}${isLocaleRtl ? '\u200f' : ''}%`, locked }, - grade: { grade: `${(assignment.averageGrade * 100).toFixed(0)}${isLocaleRtl ? '\u200f' : ''}%`, locked }, - weightedGrade: { weightedGrade: `${(assignment.weightedGrade * 100).toFixed(0)}${isLocaleRtl ? '\u200f' : ''}%`, locked }, + grade: { grade: `${(assignment.averageGrade * 100).toFixed(2)}${isLocaleRtl ? '\u200f' : ''}%`, locked }, + weightedGrade: { weightedGrade: `${(assignment.weightedGrade * 100).toFixed(2)}${isLocaleRtl ? '\u200f' : ''}%`, locked }, }; }); const getAssignmentTypeCell = (value) => ( @@ -137,8 +135,7 @@ const GradeSummaryTable = ({ intl, setAllOfSomeAssignmentTypeIsLocked }) => { }; GradeSummaryTable.propTypes = { - intl: intlShape.isRequired, setAllOfSomeAssignmentTypeIsLocked: PropTypes.func.isRequired, }; -export default injectIntl(GradeSummaryTable); +export default GradeSummaryTable; diff --git a/src/course-home/progress-tab/grades/grade-summary/GradeSummaryTableFooter.jsx b/src/course-home/progress-tab/grades/grade-summary/GradeSummaryTableFooter.jsx index 2c3235be86..a99c278aa9 100644 --- a/src/course-home/progress-tab/grades/grade-summary/GradeSummaryTableFooter.jsx +++ b/src/course-home/progress-tab/grades/grade-summary/GradeSummaryTableFooter.jsx @@ -1,15 +1,20 @@ -import React from 'react'; import { useSelector } from 'react-redux'; +import { getLocale, isRtl, useIntl } from '@edx/frontend-platform/i18n'; import { - getLocale, injectIntl, intlShape, isRtl, -} from '@edx/frontend-platform/i18n'; -import { DataTable } from '@openedx/paragon'; -import { useModel } from '../../../../generic/model-store'; + DataTable, + Icon, + OverlayTrigger, + Stack, + Tooltip, +} from '@openedx/paragon'; +import { InfoOutline } from '@openedx/paragon/icons'; +import { useModel } from '../../../../generic/model-store'; import messages from '../messages'; -const GradeSummaryTableFooter = ({ intl }) => { +const GradeSummaryTableFooter = () => { + const intl = useIntl(); const { courseId, } = useSelector(state => state.courseHome); @@ -29,15 +34,30 @@ const GradeSummaryTableFooter = ({ intl }) => { return (
-
{intl.formatMessage(messages.weightedGradeSummary)}
+
+ + {intl.formatMessage(messages.weightedGradeSummary)} + + {intl.formatMessage(messages.weightedGradeSummaryTooltip)} + + )} + > + + + +
{totalGrade}{isLocaleRtl && '\u200f'}%
); }; -GradeSummaryTableFooter.propTypes = { - intl: intlShape.isRequired, -}; - -export default injectIntl(GradeSummaryTableFooter); +export default GradeSummaryTableFooter; diff --git a/src/course-home/progress-tab/grades/messages.ts b/src/course-home/progress-tab/grades/messages.ts index fb57809852..5b19e0b41c 100644 --- a/src/course-home/progress-tab/grades/messages.ts +++ b/src/course-home/progress-tab/grades/messages.ts @@ -203,7 +203,11 @@ const messages = defineMessages({ defaultMessage: 'Your current weighted grade summary', description: 'It the text precede the sum of weighted grades of all the assignment', }, - + weightedGradeSummaryTooltip: { + id: 'progress.weightedGradeSummary', + defaultMessage: 'The weighted grade for individual assignment types is raw and the weighted grade summary is rounded.', + description: 'Tooltip content that explains the rounding of the summary versus individual assignments', + }, }); export default messages; From 46da7f8472c74c346277bba74f3612bcba09f197 Mon Sep 17 00:00:00 2001 From: KristinAoki Date: Wed, 18 Dec 2024 16:17:28 -0500 Subject: [PATCH 2/3] fix: tooltip message --- .../grade-summary/GradeSummaryTableFooter.jsx | 19 ++++++++++++++++++- .../progress-tab/grades/messages.ts | 2 +- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/src/course-home/progress-tab/grades/grade-summary/GradeSummaryTableFooter.jsx b/src/course-home/progress-tab/grades/grade-summary/GradeSummaryTableFooter.jsx index a99c278aa9..19299a4ef8 100644 --- a/src/course-home/progress-tab/grades/grade-summary/GradeSummaryTableFooter.jsx +++ b/src/course-home/progress-tab/grades/grade-summary/GradeSummaryTableFooter.jsx @@ -1,8 +1,10 @@ +import { useContext } from 'react'; import { useSelector } from 'react-redux'; import { getLocale, isRtl, useIntl } from '@edx/frontend-platform/i18n'; import { DataTable, + DataTableContext, Icon, OverlayTrigger, Stack, @@ -15,6 +17,18 @@ import messages from '../messages'; const GradeSummaryTableFooter = () => { const intl = useIntl(); + + const { data } = useContext(DataTableContext); + + const rawGrade = data.reduce( + (grade, currentValue) => { + const { weightedGrade } = currentValue.weightedGrade; + const percent = weightedGrade.replace(/%/g, '').trim(); + return grade + parseFloat(percent); + }, + 0, + ).toFixed(2); + const { courseId, } = useSelector(state => state.courseHome); @@ -42,7 +56,10 @@ const GradeSummaryTableFooter = () => { placement="bottom" overlay={( - {intl.formatMessage(messages.weightedGradeSummaryTooltip)} + {intl.formatMessage( + messages.weightedGradeSummaryTooltip, + { roundedGrade: totalGrade, rawGrade }, + )} )} > diff --git a/src/course-home/progress-tab/grades/messages.ts b/src/course-home/progress-tab/grades/messages.ts index 5b19e0b41c..24475b98fd 100644 --- a/src/course-home/progress-tab/grades/messages.ts +++ b/src/course-home/progress-tab/grades/messages.ts @@ -205,7 +205,7 @@ const messages = defineMessages({ }, weightedGradeSummaryTooltip: { id: 'progress.weightedGradeSummary', - defaultMessage: 'The weighted grade for individual assignment types is raw and the weighted grade summary is rounded.', + defaultMessage: 'Your raw weighted grade summary is {rawGrade} and rounds to {roundedGrade}.', description: 'Tooltip content that explains the rounding of the summary versus individual assignments', }, }); From b128f1674398e99afb65149795621424c3d0a58c Mon Sep 17 00:00:00 2001 From: KristinAoki Date: Thu, 19 Dec 2024 11:44:44 -0500 Subject: [PATCH 3/3] fix: failing test --- .../grade-summary/GradeSummaryTable.jsx | 32 +++++++++++++------ 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/src/course-home/progress-tab/grades/grade-summary/GradeSummaryTable.jsx b/src/course-home/progress-tab/grades/grade-summary/GradeSummaryTable.jsx index fa1b4775c5..bd805242d0 100644 --- a/src/course-home/progress-tab/grades/grade-summary/GradeSummaryTable.jsx +++ b/src/course-home/progress-tab/grades/grade-summary/GradeSummaryTable.jsx @@ -32,6 +32,14 @@ const GradeSummaryTable = ({ setAllOfSomeAssignmentTypeIsLocked }) => { return footnoteId.replace(/[^A-Za-z0-9.-_]+/g, '-'); }; + const getGradePercent = (grade) => { + if (Number.isInteger(grade * 100)) { + return (grade * 100).toFixed(0); + } + + return (grade * 100).toFixed(2); + }; + const hasNoAccessToAssignmentsOfType = (assignmentType) => { const subsectionAssignmentsOfType = sectionScores.map((chapter) => chapter.subsections.filter((subsection) => ( subsection.assignmentType === assignmentType && subsection.hasGradedAssignment @@ -50,31 +58,37 @@ const GradeSummaryTable = ({ setAllOfSomeAssignmentTypeIsLocked }) => { }; const gradeSummaryData = assignmentPolicies.map((assignment) => { + const { + averageGrade, + numDroppable, + type: assignmentType, + weight, + weightedGrade, + } = assignment; let footnoteId = ''; let footnoteMarker; - if (assignment.numDroppable > 0) { + if (numDroppable > 0) { footnoteId = getFootnoteId(assignment); footnotes.push({ id: footnoteId, - numDroppable: assignment.numDroppable, - assignmentType: assignment.type, + numDroppable, + assignmentType, }); footnoteMarker = footnotes.length; } - const locked = !gradesFeatureIsFullyLocked && hasNoAccessToAssignmentsOfType(assignment.type); - + const locked = !gradesFeatureIsFullyLocked && hasNoAccessToAssignmentsOfType(assignmentType); const isLocaleRtl = isRtl(getLocale()); return { type: { - footnoteId, footnoteMarker, type: assignment.type, locked, + footnoteId, footnoteMarker, type: assignmentType, locked, }, - weight: { weight: `${(assignment.weight * 100).toFixed(0)}${isLocaleRtl ? '\u200f' : ''}%`, locked }, - grade: { grade: `${(assignment.averageGrade * 100).toFixed(2)}${isLocaleRtl ? '\u200f' : ''}%`, locked }, - weightedGrade: { weightedGrade: `${(assignment.weightedGrade * 100).toFixed(2)}${isLocaleRtl ? '\u200f' : ''}%`, locked }, + weight: { weight: `${(weight * 100).toFixed(0)}${isLocaleRtl ? '\u200f' : ''}%`, locked }, + grade: { grade: `${getGradePercent(averageGrade)}${isLocaleRtl ? '\u200f' : ''}%`, locked }, + weightedGrade: { weightedGrade: `${getGradePercent(weightedGrade)}${isLocaleRtl ? '\u200f' : ''}%`, locked }, }; }); const getAssignmentTypeCell = (value) => (