From b2dbfa076b1afddd7d159ad5ba1407c5d28f60ab Mon Sep 17 00:00:00 2001 From: "rohan.chugh" Date: Tue, 29 Aug 2023 10:19:52 -0500 Subject: [PATCH] add custom statuses to gradebook imports - FE 1/2 allows the front end importer to recognize that there are custom statuses for overrides that have changed from the current import, showing which rows/columns changed. this is part 1/2, as the next commit will add the last part which is saving those changes closes EVAL-3441 flag=custom_gradebook_statuses test plan: - CANNOT BE QA'd UNTIL g/326378 IS MERGED - go to a course with grading periods and for the gradebook filter, select "All Grading Periods" - add a custom status to a final grade override using the tray - export the entire gradebook - open a CSV editor and change the custom status you added to something else and add another status for another student - MAKE SURE THAT BOTH STUDENTS WHO WERE CHANGED ARE USERS WITH LOGINS (otherwise they are not gradable students and the importer does not recognize them) - use the importer to import the CSV you just edited - it should recognize the changes and show the columns and rows that changed - now do the same but for a single grading period, using the option to export the current gradebook view instead of entire gradebook - it should recognize the changes and show the columns and rows that changed - note: "applying" the changes or saving them will not work until the next commit is merged Change-Id: I081b05d63ac0a75d08ae23acb79388fcbf58ca4b Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/326452 Tested-by: Service Cloud Jenkins Reviewed-by: Christopher Soto Reviewed-by: Spencer Olson QA-Review: Christopher Soto Product-Review: Ravi Koll --- spec/javascripts/jsx/gradebook_uploadsSpec.js | 208 ++++++++++++++++++ ui/features/gradebook_uploads/jquery/index.js | 65 ++++++ 2 files changed, 273 insertions(+) diff --git a/spec/javascripts/jsx/gradebook_uploadsSpec.js b/spec/javascripts/jsx/gradebook_uploadsSpec.js index 10cbab0c352..d8ffc20072a 100644 --- a/spec/javascripts/jsx/gradebook_uploadsSpec.js +++ b/spec/javascripts/jsx/gradebook_uploadsSpec.js @@ -191,6 +191,14 @@ QUnit.module('override score changes', hooks => { ], includes_course_scores: false, }, + override_statuses: { + grading_periods: [ + {id: 1, title: 'first GP'}, + {id: 2, title: 'second GP'}, + {id: 3, title: 'third GP'}, + ], + includes_course_score_status: false, + }, students: [ { custom_column_data: [], @@ -213,6 +221,26 @@ QUnit.module('override score changes', hooks => { new_score: null, }, ], + override_statuses: [ + { + grading_period_id: '1', + student_id: '1', + current_grade_status: 'CARROT', + new_grade_status: 'POTATO', + }, + { + grading_period_id: '2', + student_id: '1', + current_grade_status: null, + new_grade_status: 'CARROT', + }, + { + grading_period_id: '3', + student_id: '1', + current_grade_status: 'POTATO', + new_grade_status: null, + }, + ], previous_id: '1', submissions: [{assignment_id: '-1', grade: '0.0', gradeable: true, original_grade: null}], }, @@ -317,6 +345,70 @@ QUnit.module('override score changes', hooks => { ) notOk(gradingPeriodColumn) }) + + test('creates a pair of columns for each grading period in the grading_periods hash of override status', () => { + initGradebook() + + const columnIds = mainGridArgs.columns + .map(column => column.id) + .filter(id => id.includes('override_status')) + + deepEqual(columnIds, [ + 'override_status_1_conflicting', + 'override_status_1', + 'override_status_2_conflicting', + 'override_status_2', + 'override_status_3_conflicting', + 'override_status_3', + ]) + }) + + test('adds a header for each grading period including the title of the grading period of override status', () => { + initGradebook() + + const headers = headerGridArgs.columns + .map(column => column.name) + .filter(name => name.includes('Override Status')) + + deepEqual(headers, [ + 'Override Status (first GP)', + 'Override Status (second GP)', + 'Override Status (third GP)', + ]) + }) + + test('creates a column for course status if includes_course_score_status is true', () => { + defaultUploadedGradebook.override_statuses.includes_course_score_status = true + defaultUploadedGradebook.override_statuses.grading_periods = [] + + initGradebook() + + const gradingPeriodColumn = mainGridArgs.columns.find( + column => column.id === 'override_status_course' + ) + ok(gradingPeriodColumn) + }) + + test('adds a header for course status with the label of plain old "Override Status"', () => { + defaultUploadedGradebook.override_statuses.includes_course_score_status = true + defaultUploadedGradebook.override_statuses.grading_periods = [] + + initGradebook() + + const gradingPeriodColumn = headerGridArgs.columns.find( + column => column.name === 'Override Status' + ) + ok(gradingPeriodColumn) + }) + + test('does not create a column for course status if includes_course_score_status is false', () => { + initGradebook() + + const gradingPeriodColumn = mainGridArgs.columns.find( + column => column.id === 'override_status_course' + ) + notOk(gradingPeriodColumn) + }) }) QUnit.module('value population', () => { @@ -389,6 +481,7 @@ QUnit.module('override score changes', hooks => { new_score: '70', }, ] + defaultUploadedGradebook.students[0].override_statuses = [] initGradebook() // setCellCssStyles should be called with an empty hash since nothing to @@ -404,11 +497,126 @@ QUnit.module('override score changes', hooks => { new_score: '70', }, ] + defaultUploadedGradebook.students[0].override_statuses = [] initGradebook() // setCellCssStyles should be called with an empty hash since nothing to // highlight deepEqual(gradeReviewRow, {}, 'no highlightable changes') }) + + test('populates the grid data with course override statuses for each student', () => { + defaultUploadedGradebook.override_statuses.includes_course_score_status = true + defaultUploadedGradebook.override_statuses.grading_periods = [] + defaultUploadedGradebook.students[0].override_statuses = [ + { + grading_period_id: null, + student_id: '1', + current_grade_status: 'BROCCOLI', + new_grade_status: 'POTATO', + }, + ] + initGradebook() + + const dataForStudent = mainGridArgs.data.find(datum => datum.id === '1') + deepEqual(dataForStudent.override_status_course, { + current_grade_status: 'BROCCOLI', + new_grade_status: 'POTATO', + grading_period_id: null, + student_id: '1', + }) + }) + + test('populates the grid data with grading period override statuses for each student', () => { + initGradebook() + + const dataForStudent = mainGridArgs.data.find(datum => datum.id === '1') + deepEqual(dataForStudent.override_status_1, { + grading_period_id: '1', + student_id: '1', + current_grade_status: 'CARROT', + new_grade_status: 'POTATO', + }) + }) + + test('highlights cells if the override status is changed', () => { + initGradebook() + const firstStudentRow = gradeReviewRow[0] + strictEqual( + firstStudentRow.override_status_1_conflicting, + 'left-highlight', + 'current status is highlighted' + ) + strictEqual( + firstStudentRow.override_status_1, + 'right-highlight', + 'updated status is highlighted' + ) + }) + + test('highlights cells if the override status has been removed', () => { + initGradebook() + + const firstStudentRow = gradeReviewRow[0] + strictEqual( + firstStudentRow.override_status_3_conflicting, + 'left-highlight', + 'current status is highlighted' + ) + strictEqual( + firstStudentRow.override_status_3, + 'right-highlight', + 'updated (removed) status is highlighted' + ) + }) + }) + + test('does not highlight the cells if the override status has been newly added', () => { + defaultUploadedGradebook.students[0].override_scores = [] + defaultUploadedGradebook.students[0].override_statuses = [ + { + grading_period_id: '1', + student_id: '1', + current_grade_status: null, + new_grade_status: 'POTATO', + }, + ] + initGradebook() + + deepEqual(gradeReviewRow, {}, 'no highlightable changes') + }) + + test('does not highlight cells if the override status has not changed', () => { + defaultUploadedGradebook.students[0].override_scores = [] + defaultUploadedGradebook.students[0].override_statuses = [ + { + grading_period_id: '1', + student_id: '1', + current_grade_status: 'POTATO', + new_grade_status: 'POTATO', + }, + ] + initGradebook() + + // setCellCssStyles should be called with an empty hash since nothing to + // highlight + deepEqual(gradeReviewRow, {}, 'no highlightable changes') + }) + + test('does not highlight cells if the override status case changed', () => { + defaultUploadedGradebook.students[0].override_scores = [] + defaultUploadedGradebook.students[0].override_statuses = [ + { + grading_period_id: '1', + student_id: '1', + current_grade_status: 'POTATO', + new_grade_status: 'potato', + }, + ] + initGradebook() + + // setCellCssStyles should be called with an empty hash since nothing to + // highlight + deepEqual(gradeReviewRow, {}, 'no highlightable changes') }) }) diff --git a/ui/features/gradebook_uploads/jquery/index.js b/ui/features/gradebook_uploads/jquery/index.js index b9c3f9da84e..de03bc7020a 100644 --- a/ui/features/gradebook_uploads/jquery/index.js +++ b/ui/features/gradebook_uploads/jquery/index.js @@ -197,6 +197,16 @@ const GradebookUploader = { this.addOverrideScoreChangeColumn(labelData, gridData, gradingPeriod) }) } + if (uploadedGradebook.override_statuses != null) { + const overrideStatuses = uploadedGradebook.override_statuses + if (overrideStatuses.includes_course_score_status) { + this.addOverrideStatusChangeColumn(labelData, gridData) + } + + overrideStatuses.grading_periods.forEach(gradingPeriod => { + this.addOverrideStatusChangeColumn(labelData, gridData, gradingPeriod) + }) + } $.each(uploadedGradebook.students, function (index) { const row = { @@ -235,6 +245,21 @@ const GradebookUploader = { row[`${columnId}_conflicting`] = overrideScore }) + currentStudent.override_statuses?.forEach(overrideStatus => { + const id = overrideStatus.grading_period_id || 'course' + const columnId = `override_status_${id}` + + if ( + overrideStatus.current_grade_status !== null && + overrideStatus.current_grade_status?.toLowerCase() !== + overrideStatus.new_grade_status?.toLowerCase() + ) { + rowsToHighlight.push({rowIndex: index, id: columnId}) + } + row[columnId] = overrideStatus + row[`${columnId}_conflicting`] = overrideStatus + }) + gridData.data.push(row) row.active = true }) @@ -530,6 +555,46 @@ const GradebookUploader = { } labelData.columns.push(overrideScoreHeaderColumn) }, + + addOverrideStatusChangeColumn(labelData, gridData, gradingPeriod = null) { + // A null grading period means these changes are for override grades for the course + const id = gradingPeriod?.id || 'course' + const title = gradingPeriod?.title + ? I18n.t('Override Status (%{gradingPeriod})', {gradingPeriod: gradingPeriod.title}) + : I18n.t('Override Status') + + const newOverrideStatusColumn = { + id: `override_status_${id}`, + type: 'assignment', + name: htmlEscape(I18n.t('To')), + field: `override_status_${id}`, + width: 125, + editor: Slick.Editors.UploadGradeCellEditor, + editorFormatter: 'override_status', + editorParser: 'override_status', + formatter: GradebookUploader.createGeneralFormatter('new_grade_status'), + active: true, + cssClass: 'new-grade', + } + + const conflictingOverrideScoreColumn = { + id: `override_status_${id}_conflicting`, + width: 125, + formatter: GradebookUploader.createGeneralFormatter('current_grade_status'), + field: `override_status_${id}_conflicting`, + name: htmlEscape(I18n.t('From')), + cssClass: 'conflicting-grade', + } + gridData.columns.push(conflictingOverrideScoreColumn, newOverrideStatusColumn) + + const overrideScoreHeaderColumn = { + id: `override_status_${id}`, + width: 250, + name: htmlEscape(title), + headerCssClass: 'assignment', + } + labelData.columns.push(overrideScoreHeaderColumn) + }, } export default GradebookUploader