diff --git a/scripts/langindex.json b/scripts/langindex.json index 5ca395007..e95c7ca1f 100644 --- a/scripts/langindex.json +++ b/scripts/langindex.json @@ -201,6 +201,7 @@ "addon.mod_assign.grade": "grades", "addon.mod_assign.graded": "assign", "addon.mod_assign.gradedby": "assign", + "addon.mod_assign.gradedfollowupsubmit": "assign", "addon.mod_assign.gradedon": "assign", "addon.mod_assign.gradelocked": "assign", "addon.mod_assign.gradenotsynced": "local_moodlemobileapp", diff --git a/src/addon/mod/assign/components/submission/submission.ts b/src/addon/mod/assign/components/submission/submission.ts index 477546059..3095a5212 100644 --- a/src/addon/mod/assign/components/submission/submission.ts +++ b/src/addon/mod/assign/components/submission/submission.ts @@ -555,6 +555,18 @@ export class AddonModAssignSubmissionComponent implements OnInit, OnDestroy { this.assignProvider.getSubmissionGradingStatusTranslationId(this.grade.gradingStatus); } + if (this.isGrading && this.lastAttempt.gradingstatus == 'graded' && !this.assign.markingworkflow) { + if (this.feedback.gradeddate < this.lastAttempt.submission.timemodified) { + this.lastAttempt.gradingstatus = AddonModAssignProvider.GRADED_FOLLOWUP_SUBMIT; + + // Get grading text and color. + this.gradingStatusTranslationId = this.assignProvider.getSubmissionGradingStatusTranslationId( + this.lastAttempt.gradingstatus); + this.gradingColor = this.assignProvider.getSubmissionGradingStatusColor(this.lastAttempt.gradingstatus); + + } + } + if (!this.feedback || !this.feedback.plugins) { // Feedback plugins not present, we have to use assign configs to detect the plugins used. this.feedback = {}; diff --git a/src/addon/mod/assign/lang/en.json b/src/addon/mod/assign/lang/en.json index e7e2840aa..9436ddd36 100644 --- a/src/addon/mod/assign/lang/en.json +++ b/src/addon/mod/assign/lang/en.json @@ -35,6 +35,7 @@ "grade": "Grade", "graded": "Graded", "gradedby": "Graded by", + "gradedfollowupsubmit": "Graded - follow up submission received", "gradenotsynced": "Grade not synced", "gradedon": "Graded on", "gradelocked": "This grade is locked or overridden in the gradebook.", diff --git a/src/addon/mod/assign/pages/submission-list/submission-list.ts b/src/addon/mod/assign/pages/submission-list/submission-list.ts index 504a41b8d..d5103ccf3 100644 --- a/src/addon/mod/assign/pages/submission-list/submission-list.ts +++ b/src/addon/mod/assign/pages/submission-list/submission-list.ts @@ -99,7 +99,8 @@ export class AddonModAssignSubmissionListPage implements OnInit, OnDestroy { */ protected fetchAssignment(): Promise { let participants, - submissionsData; + submissionsData, + grades; // Get assignment data. return this.assignProvider.getAssignment(this.courseId, this.moduleId).then((assign) => { @@ -124,6 +125,13 @@ export class AddonModAssignSubmissionListPage implements OnInit, OnDestroy { }).catch(() => { this.haveAllParticipants = false; }); + }).then(() => { + if (!this.assign.markingworkflow) { + // Get assignment grades only if workflow is not enabled to check grading date. + return this.assignProvider.getAssignmentGrades(this.assign.id).then((assignmentGrades) => { + grades = assignmentGrades; + }); + } }).then(() => { // We want to show the user data on each submission. return this.assignProvider.getSubmissionsUserData(submissionsData.submissions, this.courseId, this.assign.id, @@ -161,6 +169,18 @@ export class AddonModAssignSubmissionListPage implements OnInit, OnDestroy { return; } + if (submission.gradingstatus == 'graded' && !this.assign.markingworkflow) { + // Get the last grade of the submission. + const grade = grades.filter((grade) => { + return grade.userid == submission.userid; + }).reduce((a, b) => { + return ( a.timemodified > b.timemodified ? a : b ); + }); + + if (grade && grade.timemodified < submission.timemodified) { + submission.gradingstatus = AddonModAssignProvider.GRADED_FOLLOWUP_SUBMIT; + } + } submission.statusColor = this.assignProvider.getSubmissionStatusColor(submission.status); submission.gradingColor = this.assignProvider.getSubmissionGradingStatusColor(submission.gradingstatus); @@ -228,6 +248,7 @@ export class AddonModAssignSubmissionListPage implements OnInit, OnDestroy { if (this.assign) { promises.push(this.assignProvider.invalidateAllSubmissionData(this.assign.id)); promises.push(this.assignProvider.invalidateAssignmentUserMappingsData(this.assign.id)); + promises.push(this.assignProvider.invalidateAssignmentGradesData(this.assign.id)); promises.push(this.assignProvider.invalidateListParticipantsData(this.assign.id)); } diff --git a/src/addon/mod/assign/providers/assign.ts b/src/addon/mod/assign/providers/assign.ts index 14568639c..b30a7490d 100644 --- a/src/addon/mod/assign/providers/assign.ts +++ b/src/addon/mod/assign/providers/assign.ts @@ -52,6 +52,7 @@ export class AddonModAssignProvider { static GRADING_STATUS_NOT_GRADED = 'notgraded'; static MARKING_WORKFLOW_STATE_RELEASED = 'released'; static NEED_GRADING = 'needgrading'; + static GRADED_FOLLOWUP_SUBMIT = 'gradedfollowupsubmit'; // Events. static SUBMISSION_SAVED_EVENT = 'addon_mod_assign_submission_saved'; @@ -243,6 +244,49 @@ export class AddonModAssignProvider { return this.ROOT_CACHE_KEY + 'usermappings:' + assignId; } + /** + * Returns grade information from assign_grades for the requested assignment id + * + * @param {number} assignId Assignment Id. + * @param {string} [siteId] Site ID. If not defined, current site. + * @return {Promise} Resolved with requested info when done. + */ + getAssignmentGrades(assignId: number, siteId?: string): Promise { + return this.sitesProvider.getSite(siteId).then((site) => { + const params = { + assignmentids: [assignId] + }, + preSets = { + cacheKey: this.getAssignmentGradesCacheKey(assignId) + }; + + return site.read('mod_assign_get_grades', params, preSets).then((response) => { + // Search the assignment. + if (response.assignments && response.assignments.length) { + const assignment = response.assignments[0]; + + if (assignment.assignmentid == assignId) { + return assignment.grades; + } + } else if (response.warnings && response.warnings.length) { + return Promise.reject(response.warnings[0]); + } + + return Promise.reject(null); + }); + }); + } + + /** + * Get cache key for assignment grades data WS calls. + * + * @param {number} assignId Assignment ID. + * @return {string} Cache key. + */ + protected getAssignmentGradesCacheKey(assignId: number): string { + return this.ROOT_CACHE_KEY + 'assigngrades:' + assignId; + } + /** * Find participant on a list. * @@ -294,7 +338,8 @@ export class AddonModAssignProvider { return; } - if (status == AddonModAssignProvider.GRADING_STATUS_GRADED || status == AddonModAssignProvider.GRADING_STATUS_NOT_GRADED) { + if (status == AddonModAssignProvider.GRADING_STATUS_GRADED || status == AddonModAssignProvider.GRADING_STATUS_NOT_GRADED + || status == AddonModAssignProvider.GRADED_FOLLOWUP_SUBMIT) { return 'addon.mod_assign.' + status; } @@ -486,6 +531,7 @@ export class AddonModAssignProvider { case 'noattempt': case 'noonlinesubmissions': case 'nosubmission': + case 'gradedfollowupsubmit': return 'danger'; default: return 'light'; @@ -709,6 +755,19 @@ export class AddonModAssignProvider { }); } + /** + * Invalidates assignment grades data WS calls. + * + * @param {number} assignId Assignment ID. + * @param {string} [siteId] Site ID. If not defined, current site. + * @return {Promise} Promise resolved when the data is invalidated. + */ + invalidateAssignmentGradesData(assignId: number, siteId?: string): Promise { + return this.sitesProvider.getSite(siteId).then((site) => { + return site.invalidateWsCacheForKey(this.getAssignmentGradesCacheKey(assignId)); + }); + } + /** * Invalidate the prefetched content except files. * To invalidate files, use AddonModAssignProvider.invalidateFiles. @@ -727,6 +786,7 @@ export class AddonModAssignProvider { // Do not invalidate assignment data before getting assignment info, we need it! promises.push(this.invalidateAllSubmissionData(assign.id, siteId)); promises.push(this.invalidateAssignmentUserMappingsData(assign.id, siteId)); + promises.push(this.invalidateAssignmentGradesData(assign.id, siteId)); promises.push(this.invalidateListParticipantsData(assign.id, siteId)); promises.push(this.commentsProvider.invalidateCommentsByInstance('module', assign.id, siteId)); promises.push(this.invalidateAssignmentData(courseId, siteId)); diff --git a/src/addon/mod/assign/providers/prefetch-handler.ts b/src/addon/mod/assign/providers/prefetch-handler.ts index 23ad10b7f..e98bb8f9a 100644 --- a/src/addon/mod/assign/providers/prefetch-handler.ts +++ b/src/addon/mod/assign/providers/prefetch-handler.ts @@ -294,6 +294,11 @@ export class AddonModAssignPrefetchHandler extends CoreCourseActivityPrefetchHan })); }); + if (!assign.markingworkflow) { + // Get assignment grades only if workflow is not enabled to check grading date. + subPromises.push(this.assignProvider.getAssignmentGrades(assign.id, siteId)); + } + // Prefetch the submission of the current user even if it does not exist, this will be create it. if (!data.submissions || !data.submissions.find((subm) => subm.submitid == userId)) { subPromises.push(this.assignProvider.getSubmissionStatus(assign.id, userId, false, true, false, siteId) diff --git a/src/assets/lang/en.json b/src/assets/lang/en.json index cc1fa5926..28f33fa4f 100644 --- a/src/assets/lang/en.json +++ b/src/assets/lang/en.json @@ -201,6 +201,7 @@ "addon.mod_assign.grade": "Grade", "addon.mod_assign.graded": "Graded", "addon.mod_assign.gradedby": "Graded by", + "addon.mod_assign.gradedfollowupsubmit": "Graded - follow up submission received", "addon.mod_assign.gradedon": "Graded on", "addon.mod_assign.gradelocked": "This grade is locked or overridden in the gradebook.", "addon.mod_assign.gradenotsynced": "Grade not synced", @@ -1661,4 +1662,4 @@ "core.year": "year", "core.years": "years", "core.yes": "Yes" -} +} \ No newline at end of file