From f80fe8ff7ddb91cad4e933f302d2f3ca319260c9 Mon Sep 17 00:00:00 2001 From: emptygx Date: Sat, 8 Jul 2023 03:22:06 +0800 Subject: [PATCH 1/5] Create publish grading feature - Add new grading state 'Published' - Add UI component to grading dashboard to enable publishing feature - Update sagas to accept new backend boolean and allow publish requests - Update frontend logic for computing grading status - Add new notification - Update relavant badges and misc files for new field and grading state --- .../utils/InsertFakeAchievements.ts | 2 +- .../application/actions/SessionActions.ts | 12 ++++ src/commons/application/types/SessionTypes.ts | 2 + src/commons/assessment/Assessment.tsx | 11 +++- src/commons/assessment/AssessmentTypes.ts | 1 + src/commons/mocks/GradingMocks.ts | 9 ++- .../notificationBadge/NotificationBadge.tsx | 2 + .../NotificationBadgeTypes.ts | 1 + src/commons/profile/Profile.tsx | 2 +- src/commons/sagas/BackendSaga.ts | 42 ++++++++++++++ src/commons/sagas/RequestsSaga.ts | 55 ++++++++++++++++--- src/features/grading/GradingTypes.ts | 1 + .../grading/subcomponents/GradingActions.tsx | 46 +++++++++++++++- .../grading/subcomponents/GradingBadges.tsx | 7 ++- .../subcomponents/GradingDashboard.tsx | 2 +- .../subcomponents/GradingSubmissionsTable.tsx | 8 ++- .../grading/subcomponents/GradingSummary.tsx | 2 +- 17 files changed, 182 insertions(+), 23 deletions(-) diff --git a/src/commons/achievement/utils/InsertFakeAchievements.ts b/src/commons/achievement/utils/InsertFakeAchievements.ts index 81dbef4901..a570f650a2 100644 --- a/src/commons/achievement/utils/InsertFakeAchievements.ts +++ b/src/commons/achievement/utils/InsertFakeAchievements.ts @@ -69,7 +69,7 @@ function insertFakeAchievements( requiredCompletionFrac: 0 } }, - assessmentOverview.gradingStatus === 'graded' + assessmentOverview.gradingStatus === 'published' ); } diff --git a/src/commons/application/actions/SessionActions.ts b/src/commons/application/actions/SessionActions.ts index e2d842373c..7b60b3611a 100644 --- a/src/commons/application/actions/SessionActions.ts +++ b/src/commons/application/actions/SessionActions.ts @@ -38,6 +38,7 @@ import { LOGIN_GITHUB, LOGOUT_GITHUB, LOGOUT_GOOGLE, + PUBLISH_GRADES, REAUTOGRADE_ANSWER, REAUTOGRADE_SUBMISSION, REMOVE_GITHUB_OCTOKIT_OBJECT_AND_ACCESS_TOKEN, @@ -56,6 +57,7 @@ import { SUBMIT_GRADING, SUBMIT_GRADING_AND_CONTINUE, Tokens, + UNPUBLISH_GRADES, UNSUBMIT_SUBMISSION, UPDATE_ALL_USER_XP, UPDATE_ASSESSMENT, @@ -212,6 +214,16 @@ export const unsubmitSubmission = (submissionId: number) => submissionId }); +export const unpublishGrades = (submissionId: number) => + action(UNPUBLISH_GRADES, { + submissionId + }); + +export const publishGrades = (submissionId: number) => + action(PUBLISH_GRADES, { + submissionId + }); + /** * Notification actions */ diff --git a/src/commons/application/types/SessionTypes.ts b/src/commons/application/types/SessionTypes.ts index 5472173e89..684107aac2 100644 --- a/src/commons/application/types/SessionTypes.ts +++ b/src/commons/application/types/SessionTypes.ts @@ -27,6 +27,7 @@ export const LOGIN = 'LOGIN'; export const LOGOUT_GOOGLE = 'LOGOUT_GOOGLE'; export const LOGIN_GITHUB = 'LOGIN_GITHUB'; export const LOGOUT_GITHUB = 'LOGOUT_GITHUB'; +export const PUBLISH_GRADES = 'PUBLISH_GRADES'; export const SET_TOKENS = 'SET_TOKENS'; export const SET_USER = 'SET_USER'; export const SET_COURSE_CONFIGURATION = 'SET_COURSE_CONFIGURATION'; @@ -45,6 +46,7 @@ export const REAUTOGRADE_SUBMISSION = 'REAUTOGRADE_SUBMISSION'; export const REAUTOGRADE_ANSWER = 'REAUTOGRADE_ANSWER'; export const REMOVE_GITHUB_OCTOKIT_OBJECT_AND_ACCESS_TOKEN = 'REMOVE_GITHUB_OCTOKIT_OBJECT_AND_ACCESS_TOKEN'; +export const UNPUBLISH_GRADES = 'UNPUBLISH_GRADES'; export const UNSUBMIT_SUBMISSION = 'UNSUBMIT_SUBMISSION'; export const UPDATE_ASSESSMENT_OVERVIEWS = 'UPDATE_ASSESSMENT_OVERVIEWS'; export const UPDATE_TOTAL_XP = 'UPDATE_TOTAL_XP'; diff --git a/src/commons/assessment/Assessment.tsx b/src/commons/assessment/Assessment.tsx index 8f139b01a7..e9ac56024a 100644 --- a/src/commons/assessment/Assessment.tsx +++ b/src/commons/assessment/Assessment.tsx @@ -171,7 +171,7 @@ const Assessment: React.FC = props => { renderGradingStatus: boolean ) => { const showGrade = - overview.gradingStatus === 'graded' || !props.assessmentConfiguration.isManuallyGraded; + overview.gradingStatus === 'published' || !props.assessmentConfiguration.isManuallyGraded; const ratio = isMobileBreakpoint ? 5 : 3; return (
@@ -411,11 +411,16 @@ const makeGradingStatus = (gradingStatus: string) => { let tooltip: string; switch (gradingStatus) { - case GradingStatuses.graded: + case GradingStatuses.published: iconName = IconNames.TICK; intent = Intent.SUCCESS; - tooltip = 'Fully graded'; + tooltip = 'Grade published'; break; + case GradingStatuses.graded: + iconName = IconNames.TIME; + intent = Intent.WARNING; + tooltip = 'Grading in progress'; + break; case GradingStatuses.grading: iconName = IconNames.TIME; intent = Intent.WARNING; diff --git a/src/commons/assessment/AssessmentTypes.ts b/src/commons/assessment/AssessmentTypes.ts index 0169f52ad0..3ed7fcc174 100644 --- a/src/commons/assessment/AssessmentTypes.ts +++ b/src/commons/assessment/AssessmentTypes.ts @@ -20,6 +20,7 @@ export type AssessmentWorkspaceParams = { export enum GradingStatuses { excluded = 'excluded', + published = 'published', graded = 'graded', grading = 'grading', none = 'none' diff --git a/src/commons/mocks/GradingMocks.ts b/src/commons/mocks/GradingMocks.ts index 9f1849b858..2fd0776683 100644 --- a/src/commons/mocks/GradingMocks.ts +++ b/src/commons/mocks/GradingMocks.ts @@ -22,7 +22,8 @@ export const mockGradingOverviews: GradingOverview[] = [ groupName: '1D', gradingStatus: 'graded', questionCount: 6, - gradedCount: 6 + gradedCount: 6, + isGradingPublished: false }, { xpAdjustment: -2, @@ -40,7 +41,8 @@ export const mockGradingOverviews: GradingOverview[] = [ groupName: '1F', gradingStatus: 'grading', questionCount: 6, - gradedCount: 2 + gradedCount: 2, + isGradingPublished: false }, { xpAdjustment: 4, @@ -58,7 +60,8 @@ export const mockGradingOverviews: GradingOverview[] = [ groupName: '1F', gradingStatus: 'none', questionCount: 6, - gradedCount: 0 + gradedCount: 0, + isGradingPublished: false } ]; diff --git a/src/commons/notificationBadge/NotificationBadge.tsx b/src/commons/notificationBadge/NotificationBadge.tsx index 5955184aed..0075a4c8c6 100644 --- a/src/commons/notificationBadge/NotificationBadge.tsx +++ b/src/commons/notificationBadge/NotificationBadge.tsx @@ -85,6 +85,8 @@ const makeNotificationMessage = (type: NotificationType) => { return 'This submission is new.'; case NotificationTypes.unsubmitted: return 'This assessment has been unsubmitted.'; + case NotificationTypes.unpublishedGrading: + return 'Grade has been hidden.'; case NotificationTypes.graded: return 'This assessment has been manually graded.'; case NotificationTypes.new_message: diff --git a/src/commons/notificationBadge/NotificationBadgeTypes.ts b/src/commons/notificationBadge/NotificationBadgeTypes.ts index e7ed326036..c1f3e8b86f 100644 --- a/src/commons/notificationBadge/NotificationBadgeTypes.ts +++ b/src/commons/notificationBadge/NotificationBadgeTypes.ts @@ -14,6 +14,7 @@ export enum NotificationTypes { graded = 'graded', submitted = 'submitted', unsubmitted = 'unsubmitted', + unpublishedGrading = 'unpublishedGrading', new_message = 'new_message' } diff --git a/src/commons/profile/Profile.tsx b/src/commons/profile/Profile.tsx index cc0ffc9681..9403335414 100644 --- a/src/commons/profile/Profile.tsx +++ b/src/commons/profile/Profile.tsx @@ -135,7 +135,7 @@ const Profile: React.FC = props => { .filter( item => item.status === AssessmentStatuses.submitted && - (item.gradingStatus === GradingStatuses.graded || + (item.gradingStatus === GradingStatuses.published || item.gradingStatus === GradingStatuses.excluded) ) .map((assessment, index) => { diff --git a/src/commons/sagas/BackendSaga.ts b/src/commons/sagas/BackendSaga.ts index e63cd0cfc3..713881ada0 100644 --- a/src/commons/sagas/BackendSaga.ts +++ b/src/commons/sagas/BackendSaga.ts @@ -44,12 +44,14 @@ import { FETCH_TOTAL_XP, FETCH_TOTAL_XP_ADMIN, FETCH_USER_AND_COURSE, + PUBLISH_GRADES, REAUTOGRADE_ANSWER, REAUTOGRADE_SUBMISSION, SUBMIT_ANSWER, SUBMIT_GRADING, SUBMIT_GRADING_AND_CONTINUE, Tokens, + UNPUBLISH_GRADES, UNSUBMIT_SUBMISSION, UPDATE_ASSESSMENT_CONFIGS, UPDATE_COURSE_CONFIG, @@ -100,9 +102,11 @@ import { postAuth, postCreateCourse, postGrading, + postPublishGrades, postReautogradeAnswer, postReautogradeSubmission, postSourcecast, + postUnpublishGrades, postUnsubmit, putAssessmentConfigs, putCourseConfig, @@ -428,6 +432,44 @@ function* BackendSaga(): SagaIterator { } ); + /** + * Publishes the grades for the submission and refreshes the grading overviews to reflect backend + * changes to the grading published status. + */ + yield takeEvery( + PUBLISH_GRADES, + function* (action: ReturnType): any { + const tokens: Tokens = yield selectTokens(); + const { submissionId } = action.payload; + + const resp: Response | null = yield postPublishGrades(submissionId, tokens); + if (!resp || !resp.ok) { + return yield handleResponseError(resp); + } + + yield call(showSuccessMessage, 'Publish successful', 1000); + } + ); + + /** + * Unpublished the grades for the submission and refreshes the whole page automatically. + */ + yield takeEvery( + UNPUBLISH_GRADES, + function* (action: ReturnType): any { + const tokens: Tokens = yield selectTokens(); + const { submissionId } = action.payload; + + const resp: Response | null = yield postUnpublishGrades(submissionId, tokens); + if (!resp || !resp.ok) { + return yield handleResponseError(resp); + } + + yield call(showSuccessMessage, 'Unpublish successful', 1000); + } + ); + + const sendGrade = function* ( action: | ReturnType diff --git a/src/commons/sagas/RequestsSaga.ts b/src/commons/sagas/RequestsSaga.ts index 082ad3c324..2285cf7223 100644 --- a/src/commons/sagas/RequestsSaga.ts +++ b/src/commons/sagas/RequestsSaga.ts @@ -456,7 +456,8 @@ export const getAssessmentOverviews = async ( overview.isManuallyGraded, overview.status, overview.gradedCount, - overview.questionCount + overview.questionCount, + overview.isGradingPublished ); delete overview.gradedCount; delete overview.questionCount; @@ -526,7 +527,8 @@ export const getUserAssessmentOverviews = async ( overview.isManuallyGraded, overview.status, overview.gradedCount, - overview.questionCount + overview.questionCount, + overview.isGradingPublished ); delete overview.gradedCount; delete overview.questionCount; @@ -691,6 +693,7 @@ export const getGradingOverviews = async ( gradingStatus: 'none', questionCount: overview.assessment.questionCount, gradedCount: overview.gradedCount, + isGradingPublished: overview.isGradingPublished, // XP initialXp: overview.xp, xpAdjustment: overview.xpAdjustment, @@ -702,7 +705,8 @@ export const getGradingOverviews = async ( overview.assessment.isManuallyGraded, gradingOverview.submissionStatus, gradingOverview.gradedCount, - gradingOverview.questionCount + gradingOverview.questionCount, + gradingOverview.isGradingPublished ); return gradingOverview; }) @@ -846,6 +850,40 @@ export const postUnsubmit = async ( return resp; }; +/** + * POST /courses/{courseId}/admin/grading/{submissionId}/unpublish_grades + */ +export const postUnpublishGrades = async ( + submissionId: number, + tokens: Tokens +): Promise => { + const resp = await request(`${courseId()}/admin/grading/${submissionId}/unpublish_grades`, 'POST', { + ...tokens, + noHeaderAccept: true, + shouldAutoLogout: false, + shouldRefresh: true + }); + + return resp; +}; + +/** + * POST /courses/{courseId}/admin/grading/{submissionId}/publish_grades + */ +export const postPublishGrades = async ( + submissionId: number, + tokens: Tokens +): Promise => { + const resp = await request(`${courseId()}/admin/grading/${submissionId}/publish_grades`, 'POST', { + ...tokens, + noHeaderAccept: true, + shouldAutoLogout: false, + shouldRefresh: true + }); + + return resp; +}; + /** * GET /courses/{courseId}/notifications */ @@ -1413,16 +1451,19 @@ const computeGradingStatus = ( isManuallyGraded: boolean, submissionStatus: any, numGraded: number, - numQuestions: number + numQuestions: number, + isGradingPublished: boolean ): GradingStatus => // isGraded refers to whether the assessment type is graded or not, as specified in // the respective assessment configuration isManuallyGraded && submissionStatus === 'submitted' ? numGraded === 0 ? 'none' - : numGraded === numQuestions - ? 'graded' - : 'grading' + : numGraded < numQuestions + ? 'grading' + : isGradingPublished + ? 'published' + : 'graded' : 'excluded'; const courseId: () => string = () => { diff --git a/src/features/grading/GradingTypes.ts b/src/features/grading/GradingTypes.ts index 7154e58b58..1a3cd04cb5 100644 --- a/src/features/grading/GradingTypes.ts +++ b/src/features/grading/GradingTypes.ts @@ -30,6 +30,7 @@ export type GradingOverview = { gradingStatus: GradingStatus; questionCount: number; gradedCount: number; + isGradingPublished: boolean; }; export type GradingOverviewWithNotifications = { diff --git a/src/pages/academy/grading/subcomponents/GradingActions.tsx b/src/pages/academy/grading/subcomponents/GradingActions.tsx index 93e2400494..e5974b946b 100644 --- a/src/pages/academy/grading/subcomponents/GradingActions.tsx +++ b/src/pages/academy/grading/subcomponents/GradingActions.tsx @@ -4,19 +4,25 @@ import { Flex, Icon } from '@tremor/react'; import { useDispatch } from 'react-redux'; import { Link } from 'react-router-dom'; import { + publishGrades, reautogradeSubmission, + unpublishGrades, unsubmitSubmission } from 'src/commons/application/actions/SessionActions'; +import { GradingStatus } from 'src/commons/assessment/AssessmentTypes'; import { showSimpleConfirmDialog } from 'src/commons/utils/DialogHelper'; import { useTypedSelector } from 'src/commons/utils/Hooks'; type GradingActionsProps = { submissionId: number; + isGradingPublished: boolean; + gradingStatus: GradingStatus; }; -const GradingActions: React.FC = ({ submissionId }) => { +const GradingActions: React.FC = ({ submissionId, isGradingPublished, gradingStatus }) => { const dispatch = useDispatch(); const courseId = useTypedSelector(store => store.session.courseId); + const isFullyGraded = gradingStatus === 'graded'; const handleReautogradeClick = async () => { const confirm = await showSimpleConfirmDialog({ @@ -42,9 +48,45 @@ const GradingActions: React.FC = ({ submissionId }) => { }); if (confirm) { dispatch(unsubmitSubmission(submissionId)); + dispatch(unpublishGrades(submissionId)); } }; + const handlePublishClick = async () => { + const confirm = await showSimpleConfirmDialog({ + contents: 'Are you sure you want to publish? Student will be able to see their grade.', + positiveIntent: 'danger', + positiveLabel: 'Publish' + }); + if (confirm) { + dispatch(publishGrades(submissionId)); + } + }; + + const handleUnpublishClick = async () => { + const confirm = await showSimpleConfirmDialog({ + contents: 'Are you sure you want to unpublish? Student\'s grade will be hidden.', + positiveIntent: 'danger', + positiveLabel: 'Unpublish' + }); + if (confirm) { + dispatch(unpublishGrades(submissionId)); + } + }; + + let publishButton; + if(isGradingPublished) { + publishButton = ; + } + else { + publishButton = ; + } + return ( @@ -62,6 +104,8 @@ const GradingActions: React.FC = ({ submissionId }) => { + + {publishButton} ); }; diff --git a/src/pages/academy/grading/subcomponents/GradingBadges.tsx b/src/pages/academy/grading/subcomponents/GradingBadges.tsx index 31cdb6a161..82cbe42207 100644 --- a/src/pages/academy/grading/subcomponents/GradingBadges.tsx +++ b/src/pages/academy/grading/subcomponents/GradingBadges.tsx @@ -16,7 +16,8 @@ const BADGE_COLORS = { attempted: 'red', // grading status - graded: 'green', + published: 'green', + graded: 'cyan', grading: 'yellow', none: 'red' }; @@ -58,8 +59,10 @@ const GradingStatusBadge: React.FC = ({ status }) => { const badgeIcon = () => ( = ({ submissions, handle submission => !( submission.submissionStatus === 'submitted' && - submission.gradingStatus === GradingStatuses.graded + submission.gradingStatus === GradingStatuses.published ) ); const submissionsData = showGraded ? submissions : ungraded; diff --git a/src/pages/academy/grading/subcomponents/GradingSubmissionsTable.tsx b/src/pages/academy/grading/subcomponents/GradingSubmissionsTable.tsx index 3434cc84aa..bd8d804b69 100644 --- a/src/pages/academy/grading/subcomponents/GradingSubmissionsTable.tsx +++ b/src/pages/academy/grading/subcomponents/GradingSubmissionsTable.tsx @@ -90,12 +90,14 @@ const columns = [ ); } }), - columnHelper.accessor(({ submissionId }) => ({ submissionId }), { + columnHelper.accessor(({ submissionId, isGradingPublished, gradingStatus }) => + ({ submissionId, isGradingPublished, gradingStatus }), { header: 'Actions', enableColumnFilter: false, cell: info => { - const { submissionId } = info.getValue(); - return ; + const { submissionId, isGradingPublished, gradingStatus } = info.getValue(); + return ; } }) ]; diff --git a/src/pages/academy/grading/subcomponents/GradingSummary.tsx b/src/pages/academy/grading/subcomponents/GradingSummary.tsx index 66a14dbbc4..0eb1e5d0b5 100644 --- a/src/pages/academy/grading/subcomponents/GradingSummary.tsx +++ b/src/pages/academy/grading/subcomponents/GradingSummary.tsx @@ -33,7 +33,7 @@ const GradingSummary: React.FC = ({ group, submissions, ass ({ groupName }) => group === null || groupName === group ); const ungraded = groupSubmissions.filter( - ({ gradingStatus }) => gradingStatus !== GradingStatuses.graded + ({ gradingStatus }) => gradingStatus !== GradingStatuses.published ); const ungradedAssessments = [...new Set(ungraded.map(({ assessmentId }) => assessmentId))].reduce( (acc: AssessmentSummary[], assessmentId) => { From 46cb2597fe4994b9a4301a9bfa61c3670f2a0eb1 Mon Sep 17 00:00:00 2001 From: emptygx Date: Mon, 10 Jul 2023 14:53:02 +0800 Subject: [PATCH 2/5] Fix formatting --- src/commons/assessment/Assessment.tsx | 10 ++--- src/commons/sagas/BackendSaga.ts | 7 ++- src/commons/sagas/RequestsSaga.ts | 14 +++--- .../grading/subcomponents/GradingActions.tsx | 43 +++++++++++++------ .../subcomponents/GradingSubmissionsTable.tsx | 29 +++++++++---- 5 files changed, 68 insertions(+), 35 deletions(-) diff --git a/src/commons/assessment/Assessment.tsx b/src/commons/assessment/Assessment.tsx index 1885042160..06cf57da1d 100644 --- a/src/commons/assessment/Assessment.tsx +++ b/src/commons/assessment/Assessment.tsx @@ -419,11 +419,11 @@ const makeGradingStatus = (gradingStatus: string) => { intent = Intent.SUCCESS; tooltip = 'Grade published'; break; - case GradingStatuses.graded: - iconName = IconNames.TIME; - intent = Intent.WARNING; - tooltip = 'Grading in progress'; - break; + case GradingStatuses.graded: + iconName = IconNames.TIME; + intent = Intent.WARNING; + tooltip = 'Grading in progress'; + break; case GradingStatuses.grading: iconName = IconNames.TIME; intent = Intent.WARNING; diff --git a/src/commons/sagas/BackendSaga.ts b/src/commons/sagas/BackendSaga.ts index 12a7c4ffb2..ed42bbbf66 100644 --- a/src/commons/sagas/BackendSaga.ts +++ b/src/commons/sagas/BackendSaga.ts @@ -447,7 +447,7 @@ function* BackendSaga(): SagaIterator { ); /** - * Publishes the grades for the submission and refreshes the grading overviews to reflect backend + * Publishes the grades for the submission and refreshes the grading overviews to reflect backend * changes to the grading published status. */ yield takeEvery( @@ -460,7 +460,7 @@ function* BackendSaga(): SagaIterator { if (!resp || !resp.ok) { return yield handleResponseError(resp); } - + yield call(showSuccessMessage, 'Publish successful', 1000); } ); @@ -473,7 +473,7 @@ function* BackendSaga(): SagaIterator { function* (action: ReturnType): any { const tokens: Tokens = yield selectTokens(); const { submissionId } = action.payload; - + const resp: Response | null = yield postUnpublishGrades(submissionId, tokens); if (!resp || !resp.ok) { return yield handleResponseError(resp); @@ -483,7 +483,6 @@ function* BackendSaga(): SagaIterator { } ); - const sendGrade = function* ( action: | ReturnType diff --git a/src/commons/sagas/RequestsSaga.ts b/src/commons/sagas/RequestsSaga.ts index b84b0365ee..167d7acf51 100644 --- a/src/commons/sagas/RequestsSaga.ts +++ b/src/commons/sagas/RequestsSaga.ts @@ -788,10 +788,14 @@ export const postUnpublishGrades = async ( submissionId: number, tokens: Tokens ): Promise => { - const resp = await request(`${courseId()}/admin/grading/${submissionId}/unpublish_grades`, 'POST', { - ...tokens, - noHeaderAccept: true - }); + const resp = await request( + `${courseId()}/admin/grading/${submissionId}/unpublish_grades`, + 'POST', + { + ...tokens, + noHeaderAccept: true + } + ); return resp; }; @@ -807,7 +811,7 @@ export const postPublishGrades = async ( ...tokens, noHeaderAccept: true }); - + return resp; }; diff --git a/src/pages/academy/grading/subcomponents/GradingActions.tsx b/src/pages/academy/grading/subcomponents/GradingActions.tsx index e5974b946b..02f4211b6d 100644 --- a/src/pages/academy/grading/subcomponents/GradingActions.tsx +++ b/src/pages/academy/grading/subcomponents/GradingActions.tsx @@ -19,7 +19,11 @@ type GradingActionsProps = { gradingStatus: GradingStatus; }; -const GradingActions: React.FC = ({ submissionId, isGradingPublished, gradingStatus }) => { +const GradingActions: React.FC = ({ + submissionId, + isGradingPublished, + gradingStatus +}) => { const dispatch = useDispatch(); const courseId = useTypedSelector(store => store.session.courseId); const isFullyGraded = gradingStatus === 'graded'; @@ -65,7 +69,7 @@ const GradingActions: React.FC = ({ submissionId, isGrading const handleUnpublishClick = async () => { const confirm = await showSimpleConfirmDialog({ - contents: 'Are you sure you want to unpublish? Student\'s grade will be hidden.', + contents: "Are you sure you want to unpublish? Student's grade will be hidden.", positiveIntent: 'danger', positiveLabel: 'Unpublish' }); @@ -75,16 +79,31 @@ const GradingActions: React.FC = ({ submissionId, isGrading }; let publishButton; - if(isGradingPublished) { - publishButton = ; - } - else { - publishButton = ; + if (isGradingPublished) { + publishButton = ( + + ); + } else { + publishButton = ( + + ); } return ( diff --git a/src/pages/academy/grading/subcomponents/GradingSubmissionsTable.tsx b/src/pages/academy/grading/subcomponents/GradingSubmissionsTable.tsx index bd8d804b69..6a98b2cdee 100644 --- a/src/pages/academy/grading/subcomponents/GradingSubmissionsTable.tsx +++ b/src/pages/academy/grading/subcomponents/GradingSubmissionsTable.tsx @@ -90,16 +90,27 @@ const columns = [ ); } }), - columnHelper.accessor(({ submissionId, isGradingPublished, gradingStatus }) => - ({ submissionId, isGradingPublished, gradingStatus }), { - header: 'Actions', - enableColumnFilter: false, - cell: info => { - const { submissionId, isGradingPublished, gradingStatus } = info.getValue(); - return ; + columnHelper.accessor( + ({ submissionId, isGradingPublished, gradingStatus }) => ({ + submissionId, + isGradingPublished, + gradingStatus + }), + { + header: 'Actions', + enableColumnFilter: false, + cell: info => { + const { submissionId, isGradingPublished, gradingStatus } = info.getValue(); + return ( + + ); + } } - }) + ) ]; type GradingSubmissionTableProps = { From c1dc977bc0a18a039e1edaf67f5e4f8d63b892e5 Mon Sep 17 00:00:00 2001 From: emptygx Date: Wed, 12 Jul 2023 17:42:44 +0800 Subject: [PATCH 3/5] Update UI texts and components --- .../notificationBadge/NotificationBadge.tsx | 4 +- .../grading/subcomponents/GradingActions.tsx | 8 +++- .../subcomponents/GradingDashboard.tsx | 2 +- .../subcomponents/GradingSubmissionsTable.tsx | 9 +++-- .../grading/subcomponents/GradingSummary.tsx | 38 ++++++++++++++++++- 5 files changed, 52 insertions(+), 9 deletions(-) diff --git a/src/commons/notificationBadge/NotificationBadge.tsx b/src/commons/notificationBadge/NotificationBadge.tsx index 4789c0c949..0a80c2f069 100644 --- a/src/commons/notificationBadge/NotificationBadge.tsx +++ b/src/commons/notificationBadge/NotificationBadge.tsx @@ -86,9 +86,9 @@ const makeNotificationMessage = (type: NotificationType) => { case NotificationTypes.unsubmitted: return 'This assessment has been unsubmitted.'; case NotificationTypes.unpublishedGrading: - return 'Grade has been hidden.'; + return 'Grades have been hidden for this assessment.'; case NotificationTypes.graded: - return 'This assessment has been manually graded.'; + return 'Grades have been published for this assessment.'; case NotificationTypes.new_message: return 'There are new messages.'; default: diff --git a/src/pages/academy/grading/subcomponents/GradingActions.tsx b/src/pages/academy/grading/subcomponents/GradingActions.tsx index 02f4211b6d..1dcbc0d175 100644 --- a/src/pages/academy/grading/subcomponents/GradingActions.tsx +++ b/src/pages/academy/grading/subcomponents/GradingActions.tsx @@ -17,16 +17,20 @@ type GradingActionsProps = { submissionId: number; isGradingPublished: boolean; gradingStatus: GradingStatus; + submissionStatus: string; }; const GradingActions: React.FC = ({ submissionId, isGradingPublished, - gradingStatus + gradingStatus, + submissionStatus }) => { const dispatch = useDispatch(); const courseId = useTypedSelector(store => store.session.courseId); - const isFullyGraded = gradingStatus === 'graded'; + const isFullyGraded = + gradingStatus === 'graded' || + (gradingStatus === 'excluded' && submissionStatus === 'submitted'); const handleReautogradeClick = async () => { const confirm = await showSimpleConfirmDialog({ diff --git a/src/pages/academy/grading/subcomponents/GradingDashboard.tsx b/src/pages/academy/grading/subcomponents/GradingDashboard.tsx index f68621b9b1..02db48a0b6 100644 --- a/src/pages/academy/grading/subcomponents/GradingDashboard.tsx +++ b/src/pages/academy/grading/subcomponents/GradingDashboard.tsx @@ -53,7 +53,7 @@ const GradingDashboard: React.FC = ({ submissions, handle - + diff --git a/src/pages/academy/grading/subcomponents/GradingSubmissionsTable.tsx b/src/pages/academy/grading/subcomponents/GradingSubmissionsTable.tsx index 6a98b2cdee..11c5e2b223 100644 --- a/src/pages/academy/grading/subcomponents/GradingSubmissionsTable.tsx +++ b/src/pages/academy/grading/subcomponents/GradingSubmissionsTable.tsx @@ -91,21 +91,24 @@ const columns = [ } }), columnHelper.accessor( - ({ submissionId, isGradingPublished, gradingStatus }) => ({ + ({ submissionId, isGradingPublished, gradingStatus, submissionStatus }) => ({ submissionId, isGradingPublished, - gradingStatus + gradingStatus, + submissionStatus }), { header: 'Actions', enableColumnFilter: false, cell: info => { - const { submissionId, isGradingPublished, gradingStatus } = info.getValue(); + const { submissionId, isGradingPublished, gradingStatus, submissionStatus } = + info.getValue(); return ( ); } diff --git a/src/pages/academy/grading/subcomponents/GradingSummary.tsx b/src/pages/academy/grading/subcomponents/GradingSummary.tsx index 0eb1e5d0b5..8bf916765d 100644 --- a/src/pages/academy/grading/subcomponents/GradingSummary.tsx +++ b/src/pages/academy/grading/subcomponents/GradingSummary.tsx @@ -32,9 +32,13 @@ const GradingSummary: React.FC = ({ group, submissions, ass const groupSubmissions = submissions.filter( ({ groupName }) => group === null || groupName === group ); - const ungraded = groupSubmissions.filter( + const unpublished = groupSubmissions.filter( ({ gradingStatus }) => gradingStatus !== GradingStatuses.published ); + const ungraded = groupSubmissions.filter( + ({ gradingStatus }) => + gradingStatus !== GradingStatuses.graded && gradingStatus !== GradingStatuses.published + ); const ungradedAssessments = [...new Set(ungraded.map(({ assessmentId }) => assessmentId))].reduce( (acc: AssessmentSummary[], assessmentId) => { const assessment = assessments.find(assessment => assessment.id === assessmentId); @@ -53,7 +57,9 @@ const GradingSummary: React.FC = ({ group, submissions, ass const numSubmissions = groupSubmissions.length; const numGraded = numSubmissions - ungraded.length; + const numPublished = numSubmissions - unpublished.length; const percentGraded = Math.round((numGraded / numSubmissions) * 100); + const percentPublished = Math.round((numPublished / numSubmissions) * 100); const numUngradedByAssessment = (assessmentId: number) => { return ungraded.filter(({ assessmentId: id }) => id === assessmentId).length; @@ -62,6 +68,36 @@ const GradingSummary: React.FC = ({ group, submissions, ass return ( <> My gradings + + {numPublished} + / {numSubmissions} published + + + + + + Published + + {numPublished} ({percentPublished}%) + + + + Unpublished + + {numSubmissions - numPublished} ({100 - percentPublished}%) + + + + Date: Mon, 17 Jul 2023 17:30:39 +0800 Subject: [PATCH 4/5] Fix tests for SessionActions and SessionReducer --- src/commons/application/actions/__tests__/SessionActions.ts | 3 ++- .../application/reducers/__tests__/SessionReducer.ts | 6 ++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/commons/application/actions/__tests__/SessionActions.ts b/src/commons/application/actions/__tests__/SessionActions.ts index b4fa8cba57..ce7a505e3e 100644 --- a/src/commons/application/actions/__tests__/SessionActions.ts +++ b/src/commons/application/actions/__tests__/SessionActions.ts @@ -523,7 +523,8 @@ test('updateGradingOverviews generates correct action object', () => { groupName: 'group', gradingStatus: 'excluded', questionCount: 6, - gradedCount: 0 + gradedCount: 0, + isGradingPublished: true } ]; diff --git a/src/commons/application/reducers/__tests__/SessionReducer.ts b/src/commons/application/reducers/__tests__/SessionReducer.ts index 3cafa4cb5f..5f4cb700fd 100644 --- a/src/commons/application/reducers/__tests__/SessionReducer.ts +++ b/src/commons/application/reducers/__tests__/SessionReducer.ts @@ -487,7 +487,8 @@ const gradingOverviewTest1: GradingOverview[] = [ groupName: 'group', gradingStatus: 'excluded', questionCount: 0, - gradedCount: 6 + gradedCount: 6, + isGradingPublished: true } ]; @@ -508,7 +509,8 @@ const gradingOverviewTest2: GradingOverview[] = [ groupName: 'another group', gradingStatus: 'excluded', questionCount: 6, - gradedCount: 0 + gradedCount: 0, + isGradingPublished: false } ]; From fdcf7befcc77c49175df51f716d52ba7dff825d4 Mon Sep 17 00:00:00 2001 From: emptygx Date: Fri, 28 Jul 2023 18:17:45 +0800 Subject: [PATCH 5/5] Change casing type to match backend --- src/commons/notificationBadge/NotificationBadge.tsx | 2 +- src/commons/notificationBadge/NotificationBadgeTypes.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/commons/notificationBadge/NotificationBadge.tsx b/src/commons/notificationBadge/NotificationBadge.tsx index 0a80c2f069..45372129a0 100644 --- a/src/commons/notificationBadge/NotificationBadge.tsx +++ b/src/commons/notificationBadge/NotificationBadge.tsx @@ -85,7 +85,7 @@ const makeNotificationMessage = (type: NotificationType) => { return 'This submission is new.'; case NotificationTypes.unsubmitted: return 'This assessment has been unsubmitted.'; - case NotificationTypes.unpublishedGrading: + case NotificationTypes.unpublished_grading: return 'Grades have been hidden for this assessment.'; case NotificationTypes.graded: return 'Grades have been published for this assessment.'; diff --git a/src/commons/notificationBadge/NotificationBadgeTypes.ts b/src/commons/notificationBadge/NotificationBadgeTypes.ts index c1f3e8b86f..c24772b5e7 100644 --- a/src/commons/notificationBadge/NotificationBadgeTypes.ts +++ b/src/commons/notificationBadge/NotificationBadgeTypes.ts @@ -14,7 +14,7 @@ export enum NotificationTypes { graded = 'graded', submitted = 'submitted', unsubmitted = 'unsubmitted', - unpublishedGrading = 'unpublishedGrading', + unpublished_grading = 'unpublished_grading', new_message = 'new_message' }