fix import grade of zero from ungraded should work

A student not graded for an assignment should be able to be graded
with a zero during gradebook import if the imported CSV contains a
zero in that column. Previously, the import would think there was
no change and the student would stay ungraded.

fixes GRADE-1184

Test Plan
 - Create an assignment.
 - Make sure not to grade a student for that assignment.
 - Export the gradebook.
 - Edit the CSV so that the assignment name is sufficiently malformed
   such that the import process later will ask you to match an
   assignment to an existing one.
 - Still editing the CSV, give the student a score of zero.
 - Import the CSV to the gradebook.
 - Match the malformed assignment name to the actual assignment.
 - Verify that the ungraded student now has a grade of zero after
   importing and matching. Save the changes.
 - Verify in the Gradebook that the student now has a grade of zero.

Change-Id: I2ef79dbc2b66e9f1a41fce2a5ffc59dbceadae26
Reviewed-on: https://gerrit.instructure.com/155475
Tested-by: Jenkins
Reviewed-by: Adrian Packel <apackel@instructure.com>
Reviewed-by: Keith T. Garner <kgarner@instructure.com>
QA-Review: Anju Reddy <areddy@instructure.com>
Product-Review: Keith T. Garner <kgarner@instructure.com>
This commit is contained in:
Gary Mei 2018-06-26 13:24:16 -05:00
parent 9ccd0e2e0e
commit a91002e0bd
5 changed files with 122 additions and 93 deletions

View File

@ -30,7 +30,7 @@ class GradebookUploadsController < ApplicationController
if previous_upload
if previous_upload.stale?
previous_upload.destroy
elsif previous_upload
else
# let them continue on with their old upload
redirect_to course_gradebook_upload_path(@context)
return

View File

@ -1,46 +0,0 @@
/*
* Copyright (C) 2015 - present Instructure, Inc.
*
* This file is part of Canvas.
*
* Canvas is free software: you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License as published by the Free
* Software Foundation, version 3 of the License.
*
* Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
* A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
* details.
*
* You should have received a copy of the GNU Affero General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import $ from 'jquery'
import I18n from 'i18n!gradebook_uploads'
import 'spin.js/jquery.spin'
function waitForProcessing(progress) {
var dfd = $.Deferred();
var spinner = $("#spinner").spin();
var amIDoneYet = (progress) => {
if (progress.workflow_state == "completed") {
$.ajaxJSON(ENV.uploaded_gradebook_data_path, "GET").then((uploadedGradebook) => {
spinner.hide();
dfd.resolve(uploadedGradebook)
});
} else if (progress.workflow_state == "failed") {
dfd.reject(I18n.t("Invalid CSV file. Grades could not be updated."));
} else {
setTimeout(function() {
$.ajaxJSON(`/api/v1/progress/${progress.id}`, "GET")
.then(amIDoneYet);
}, 2000);
}
}
amIDoneYet(progress);
return dfd;
}
export default waitForProcessing

View File

@ -19,28 +19,27 @@
import $ from 'jquery'
import I18n from 'i18n!gradezilla_uploads'
import 'spin.js/jquery.spin'
function waitForProcessing(progress) {
var dfd = $.Deferred();
var spinner = $("#spinner").spin();
var amIDoneYet = (progress) => {
if (progress.workflow_state == "completed") {
$.ajaxJSON(ENV.uploaded_gradebook_data_path, "GET").then((uploadedGradebook) => {
spinner.hide();
dfd.resolve(uploadedGradebook)
});
} else if (progress.workflow_state == "failed") {
dfd.reject(I18n.t("Invalid CSV file. Grades could not be updated."));
} else {
setTimeout(function() {
$.ajaxJSON(`/api/v1/progress/${progress.id}`, "GET")
export function waitForProcessing(progress) {
const dfd = $.Deferred();
const spinner = $("#spinner").spin();
const amIDoneYet = (currentProgress) => {
if (currentProgress.workflow_state === "completed") {
$.ajaxJSON(ENV.uploaded_gradebook_data_path, "GET").then((uploadedGradebook) => {
spinner.hide();
dfd.resolve(uploadedGradebook)
});
} else if (currentProgress.workflow_state === "failed") {
dfd.reject(I18n.t("Invalid CSV file. Grades could not be updated."));
} else {
setTimeout(() => {
$.ajaxJSON(`/api/v1/progress/${currentProgress.id}`, "GET")
.then(amIDoneYet);
}, 2000);
}
}, 2000);
}
amIDoneYet(progress);
return dfd;
}
amIDoneYet(progress);
export default waitForProcessing
return dfd;
}

View File

@ -20,7 +20,7 @@ import $ from 'jquery'
import _ from 'underscore'
import htmlEscape from './str/htmlEscape'
import numberHelper from 'jsx/shared/helpers/numberHelper'
import waitForProcessing from 'jsx/gradebook/uploads/wait_for_processing'
import {waitForProcessing} from 'jsx/gradezilla/uploads/wait_for_processing'
import ProcessGradebookUpload from 'jsx/gradebook/uploads/process_gradebook_upload'
import GradeFormatHelper from 'jsx/gradebook/shared/helpers/GradeFormatHelper'
import './vendor/slickgrid' /* global Slick */
@ -328,7 +328,7 @@ import './jquery.templateData' /* fillTemplateData */
return sub.user_id == student.id && sub.assignment_id == val;
});
if (original_submission) {
submission.original_grade = I18n.n(original_submission.score);
submission.original_grade = original_submission.score !== '' ? I18n.n(original_submission.score) : '';
}
});
} else if (thing === 'student') {
@ -339,7 +339,7 @@ import './jquery.templateData' /* fillTemplateData */
return sub.user_id == obj.id && sub.assignment_id == submission.assignment_id;
});
if (original_submission) {
submission.original_grade = I18n.n(original_submission.score);
submission.original_grade = original_submission.score !== '' ? I18n.n(original_submission.score) : '';
}
});
}

View File

@ -16,36 +16,112 @@
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
define([
'gradebook_uploads',
'jsx/gradebook/shared/helpers/GradeFormatHelper'
], (gradebook_uploads, GradeFormatHelper) => { // eslint-disable-line camelcase
QUnit.module('gradebook_uploads#createGeneralFormatter');
import $ from 'jquery';
import gradebook_uploads from 'gradebook_uploads';
import GradeFormatHelper from 'jsx/gradebook/shared/helpers/GradeFormatHelper';
import * as waitForProcessing from 'jsx/gradezilla/uploads/wait_for_processing';
test('formatter returns expected lookup value', function () {
const formatter = gradebook_uploads.createGeneralFormatter('foo');
const formatted = formatter(null, null, {foo: 'bar'});
equal(formatted, 'bar');
const fixtures = document.getElementById('fixtures');
QUnit.module('gradebook_uploads#createGeneralFormatter');
test('formatter returns expected lookup value', function () {
const formatter = gradebook_uploads.createGeneralFormatter('foo');
const formatted = formatter(null, null, {foo: 'bar'});
equal(formatted, 'bar');
});
test('formatter returns empty string when lookup value missing', function () {
const formatter = gradebook_uploads.createGeneralFormatter('foo');
const formatted = formatter(null, null, null);
equal(formatted, '');
});
QUnit.module('gradebook_uploads#handleThingsNeedingToBeResolved', (hooks) => {
let defaultUploadedGradebook;
hooks.beforeEach(() => {
fixtures.innerHTML = `
<form id='gradebook_importer_resolution_section'>
<select name='assignment_-1'>
<option>73</option>
</select>
</form>
<div id='gradebook_grid'>
<div id='gradebook_grid_header'></div>
</div>
<div id='no_changes_detected' style='display:none;'></div>
`;
defaultUploadedGradebook = {
assignments: [{grading_type: null, id: '-1', points_possible: 10, previous_id: null, title: 'imported'}],
missing_objects: {
assignments: [{grading_type: 'points', id: '73', points_possible: 10, previous_id: null, title: 'existing'}],
students: []
},
original_submissions: [{assignment_id: '73', gradeable: true, score: '', user_id: '1'}],
students: [{
id: '1',
last_name_first: 'Efron, Zac',
name: 'Zac Efron',
previous_id: '1',
submissions: [{assignment_id: '-1', grade: '0.0', gradeable: true, original_grade: null}]
}],
warning_messages: {
prevented_grading_ungradeable_submission: false,
prevented_new_assignment_creation_in_closed_period: false
}
};
});
test('formatter returns empty string when lookup value missing', function () {
const formatter = gradebook_uploads.createGeneralFormatter('foo');
const formatted = formatter(null, null, null);
equal(formatted, '');
hooks.afterEach(() => {
fixtures.innerHTML = '';
});
QUnit.module('grade_summary#createNumberFormatter');
test('recognizes that there are no changed assignments when the grades are the same', () => {
const uploadedGradebook = {
...defaultUploadedGradebook,
original_submissions: [{assignment_id: '73', gradeable: true, score: '0.0', user_id: '1'}]
};
const waitForProcessingStub = sinon.stub(waitForProcessing, 'waitForProcessing').returns(
$.Deferred().resolve(uploadedGradebook)
);
test('number formatter returns empty string when value missing', function () {
const formatter = gradebook_uploads.createNumberFormatter('foo');
const formatted = formatter(null, null, null);
equal(formatted, '');
gradebook_uploads.handleThingsNeedingToBeResolved();
$('#gradebook_importer_resolution_section').submit();
strictEqual($('#no_changes_detected:visible').length, 1);
waitForProcessingStub.restore();
});
test('number formatter delegates to GradeFormatHelper#formatGrade', function () {
const formatGradeSpy = this.spy(GradeFormatHelper, 'formatGrade');
const formatter = gradebook_uploads.createNumberFormatter('foo');
formatter(null, null, {});
ok(formatGradeSpy.calledOnce);
test('recognizes that there are changed assignments when original grade was ungraded', () => {
const uploadedGradebook = {
...defaultUploadedGradebook,
original_submissions: [{assignment_id: '73', gradeable: true, score: '', user_id: '1'}]
};
const waitForProcessingStub = sinon.stub(waitForProcessing, 'waitForProcessing').returns(
$.Deferred().resolve(uploadedGradebook)
);
gradebook_uploads.handleThingsNeedingToBeResolved();
$('#gradebook_importer_resolution_section').submit();
strictEqual($('#no_changes_detected:visible').length, 0);
waitForProcessingStub.restore();
});
});
QUnit.module('grade_summary#createNumberFormatter');
test('number formatter returns empty string when value missing', function () {
const formatter = gradebook_uploads.createNumberFormatter('foo');
const formatted = formatter(null, null, null);
equal(formatted, '');
});
test('number formatter delegates to GradeFormatHelper#formatGrade', function () {
const formatGradeSpy = this.spy(GradeFormatHelper, 'formatGrade');
const formatter = gradebook_uploads.createNumberFormatter('foo');
formatter(null, null, {});
ok(formatGradeSpy.calledOnce);
});