prettify old gradebook
refs GRADE-1934 test plan: * Verify Jenkins passes Change-Id: Iadefa6fca5706f2e0bb67f448ada6367e71899f9 Reviewed-on: https://gerrit.instructure.com/177537 Tested-by: Jenkins Reviewed-by: Keith Garner <kgarner@instructure.com> QA-Review: Jeremy Neander <jneander@instructure.com> Product-Review: Jeremy Neander <jneander@instructure.com>
This commit is contained in:
parent
69218d0f8b
commit
270f95443d
|
@ -32,7 +32,7 @@ const GradebookRouter = Backbone.Router.extend({
|
|||
'tab-:viewName': 'tab'
|
||||
},
|
||||
|
||||
initialize () {
|
||||
initialize() {
|
||||
this.isLoaded = false
|
||||
this.views = {}
|
||||
this.views.assignment = new Gradebook(ENV.GRADEBOOK_OPTIONS)
|
||||
|
@ -43,7 +43,7 @@ const GradebookRouter = Backbone.Router.extend({
|
|||
}
|
||||
},
|
||||
|
||||
initOutcomes () {
|
||||
initOutcomes() {
|
||||
const book = new OutcomeGradebookView({
|
||||
el: $('.outcome-gradebook-container'),
|
||||
gradebook: this.views.assignment,
|
||||
|
@ -57,20 +57,28 @@ const GradebookRouter = Backbone.Router.extend({
|
|||
|
||||
renderPagination(page, pageCount) {
|
||||
ReactDOM.render(
|
||||
<Paginator page={page} pageCount={pageCount} loadPage={(p) => this.views.outcome.loadPage(p)} />,
|
||||
document.getElementById("outcome-gradebook-paginator")
|
||||
<Paginator
|
||||
page={page}
|
||||
pageCount={pageCount}
|
||||
loadPage={p => this.views.outcome.loadPage(p)}
|
||||
/>,
|
||||
document.getElementById('outcome-gradebook-paginator')
|
||||
)
|
||||
},
|
||||
|
||||
handlePillChange (viewname) {
|
||||
handlePillChange(viewname) {
|
||||
if (viewname) this.navigate(`tab-${viewname}`, {trigger: true})
|
||||
},
|
||||
|
||||
tab (viewName) {
|
||||
tab(viewName) {
|
||||
if (!viewName) viewName = userSettings.contextGet('gradebook_tab')
|
||||
window.tab = viewName
|
||||
if ((viewName !== 'outcome') || !this.views.outcome) { viewName = 'assignment' }
|
||||
if (this.navigation) { this.navigation.setActiveView(viewName) }
|
||||
if (viewName !== 'outcome' || !this.views.outcome) {
|
||||
viewName = 'assignment'
|
||||
}
|
||||
if (this.navigation) {
|
||||
this.navigation.setActiveView(viewName)
|
||||
}
|
||||
$('.assignment-gradebook-container, .outcome-gradebook-container').addClass('hidden')
|
||||
$(`.${viewName}-gradebook-container`).removeClass('hidden')
|
||||
$('#outcome-gradebook-paginator').toggleClass('hidden', viewName !== 'outcome')
|
||||
|
|
|
@ -19,123 +19,130 @@
|
|||
import _ from 'underscore'
|
||||
import {divide, sum, sumBy} from './shared/helpers/GradeCalculationHelper'
|
||||
|
||||
function partition (collection, partitionFn) {
|
||||
const grouped = _.groupBy(collection, partitionFn);
|
||||
return [grouped.true || [], grouped.false || []];
|
||||
function partition(collection, partitionFn) {
|
||||
const grouped = _.groupBy(collection, partitionFn)
|
||||
return [grouped.true || [], grouped.false || []]
|
||||
}
|
||||
|
||||
function parseScore (score) {
|
||||
const result = parseFloat(score);
|
||||
return (result && isFinite(result)) ? result : 0;
|
||||
function parseScore(score) {
|
||||
const result = parseFloat(score)
|
||||
return result && isFinite(result) ? result : 0
|
||||
}
|
||||
|
||||
function sortPairsDescending ([scoreA, submissionA], [scoreB, submissionB]) {
|
||||
const scoreDiff = scoreB - scoreA;
|
||||
function sortPairsDescending([scoreA, submissionA], [scoreB, submissionB]) {
|
||||
const scoreDiff = scoreB - scoreA
|
||||
if (scoreDiff !== 0) {
|
||||
return scoreDiff;
|
||||
return scoreDiff
|
||||
}
|
||||
// To ensure stable sorting, use the assignment id as a secondary sort.
|
||||
return submissionA.assignment_id - submissionB.assignment_id;
|
||||
return submissionA.assignment_id - submissionB.assignment_id
|
||||
}
|
||||
|
||||
function sortPairsAscending ([scoreA, submissionA], [scoreB, submissionB]) {
|
||||
const scoreDiff = scoreA - scoreB;
|
||||
function sortPairsAscending([scoreA, submissionA], [scoreB, submissionB]) {
|
||||
const scoreDiff = scoreA - scoreB
|
||||
if (scoreDiff !== 0) {
|
||||
return scoreDiff;
|
||||
return scoreDiff
|
||||
}
|
||||
// To ensure stable sorting, use the assignment id as a secondary sort.
|
||||
return submissionA.assignment_id - submissionB.assignment_id;
|
||||
return submissionA.assignment_id - submissionB.assignment_id
|
||||
}
|
||||
|
||||
function sortSubmissionsAscending (submissionA, submissionB) {
|
||||
const scoreDiff = submissionA.score - submissionB.score;
|
||||
function sortSubmissionsAscending(submissionA, submissionB) {
|
||||
const scoreDiff = submissionA.score - submissionB.score
|
||||
if (scoreDiff !== 0) {
|
||||
return scoreDiff;
|
||||
return scoreDiff
|
||||
}
|
||||
// To ensure stable sorting, use the assignment id as a secondary sort.
|
||||
return submissionA.assignment_id - submissionB.assignment_id;
|
||||
return submissionA.assignment_id - submissionB.assignment_id
|
||||
}
|
||||
|
||||
function getSubmissionGrade ({ score, total }) {
|
||||
return score / total;
|
||||
function getSubmissionGrade({score, total}) {
|
||||
return score / total
|
||||
}
|
||||
|
||||
function estimateQHigh (pointed, unpointed, grades) {
|
||||
function estimateQHigh(pointed, unpointed, grades) {
|
||||
if (unpointed.length > 0) {
|
||||
const pointsPossible = sumBy(pointed, 'total');
|
||||
const bestPointedScore = Math.max(pointsPossible, sumBy(pointed, 'score'));
|
||||
const unpointedScore = sumBy(unpointed, 'score');
|
||||
return (bestPointedScore + unpointedScore) / pointsPossible;
|
||||
const pointsPossible = sumBy(pointed, 'total')
|
||||
const bestPointedScore = Math.max(pointsPossible, sumBy(pointed, 'score'))
|
||||
const unpointedScore = sumBy(unpointed, 'score')
|
||||
return (bestPointedScore + unpointedScore) / pointsPossible
|
||||
}
|
||||
|
||||
return grades[grades.length - 1];
|
||||
return grades[grades.length - 1]
|
||||
}
|
||||
|
||||
function buildBigF (keepCount, cannotDrop, sortFn) {
|
||||
return function bigF (q, submissions) {
|
||||
const ratedScores = _.map(submissions, submission => (
|
||||
[submission.score - (q * submission.total), submission]
|
||||
));
|
||||
const rankedScores = ratedScores.sort(sortFn);
|
||||
const keptScores = rankedScores.slice(0, keepCount);
|
||||
const qKept = sumBy(keptScores, ([score]) => score);
|
||||
const keptSubmissions = _.map(keptScores, ([_score, submission]) => submission);
|
||||
const qCannotDrop = sumBy(cannotDrop, submission => submission.score - (q * submission.total));
|
||||
return [qKept + qCannotDrop, keptSubmissions];
|
||||
function buildBigF(keepCount, cannotDrop, sortFn) {
|
||||
return function bigF(q, submissions) {
|
||||
const ratedScores = _.map(submissions, submission => [
|
||||
submission.score - q * submission.total,
|
||||
submission
|
||||
])
|
||||
const rankedScores = ratedScores.sort(sortFn)
|
||||
const keptScores = rankedScores.slice(0, keepCount)
|
||||
const qKept = sumBy(keptScores, ([score]) => score)
|
||||
const keptSubmissions = _.map(keptScores, ([_score, submission]) => submission)
|
||||
const qCannotDrop = sumBy(cannotDrop, submission => submission.score - q * submission.total)
|
||||
return [qKept + qCannotDrop, keptSubmissions]
|
||||
}
|
||||
}
|
||||
|
||||
function dropPointed (droppableSubmissionData, cannotDrop, keepHighest, keepLowest) {
|
||||
const totals = _.map(droppableSubmissionData, 'total');
|
||||
const maxTotal = Math.max(...totals);
|
||||
function dropPointed(droppableSubmissionData, cannotDrop, keepHighest, keepLowest) {
|
||||
const totals = _.map(droppableSubmissionData, 'total')
|
||||
const maxTotal = Math.max(...totals)
|
||||
|
||||
function keepHelper (submissions, initialKeepCount, bigFSort) {
|
||||
const keepCount = Math.max(1, initialKeepCount);
|
||||
function keepHelper(submissions, initialKeepCount, bigFSort) {
|
||||
const keepCount = Math.max(1, initialKeepCount)
|
||||
|
||||
if (submissions.length <= keepCount) {
|
||||
return submissions;
|
||||
return submissions
|
||||
}
|
||||
|
||||
const allSubmissionData = [...submissions, ...cannotDrop];
|
||||
const [unpointed, pointed] = partition(allSubmissionData, submissionDatum => submissionDatum.total === 0);
|
||||
const allSubmissionData = [...submissions, ...cannotDrop]
|
||||
const [unpointed, pointed] = partition(
|
||||
allSubmissionData,
|
||||
submissionDatum => submissionDatum.total === 0
|
||||
)
|
||||
|
||||
const grades = pointed.map(getSubmissionGrade).sort();
|
||||
let qHigh = estimateQHigh(pointed, unpointed, grades);
|
||||
let qLow = grades[0];
|
||||
let qMid = (qLow + qHigh) / 2;
|
||||
const grades = pointed.map(getSubmissionGrade).sort()
|
||||
let qHigh = estimateQHigh(pointed, unpointed, grades)
|
||||
let qLow = grades[0]
|
||||
let qMid = (qLow + qHigh) / 2
|
||||
|
||||
const bigF = buildBigF(keepCount, cannotDrop, bigFSort);
|
||||
const bigF = buildBigF(keepCount, cannotDrop, bigFSort)
|
||||
|
||||
let [x, submissionsToKeep] = bigF(qMid, submissions);
|
||||
const threshold = 1 / (2 * keepCount * (maxTotal ** 2));
|
||||
let [x, submissionsToKeep] = bigF(qMid, submissions)
|
||||
const threshold = 1 / (2 * keepCount * maxTotal ** 2)
|
||||
while (qHigh - qLow >= threshold) {
|
||||
if (x < 0) {
|
||||
qHigh = qMid;
|
||||
qHigh = qMid
|
||||
} else {
|
||||
qLow = qMid;
|
||||
qLow = qMid
|
||||
}
|
||||
qMid = (qLow + qHigh) / 2;
|
||||
qMid = (qLow + qHigh) / 2
|
||||
if (qMid === qHigh || qMid === qLow) {
|
||||
break;
|
||||
break
|
||||
}
|
||||
|
||||
[x, submissionsToKeep] = bigF(qMid, submissions);
|
||||
;[x, submissionsToKeep] = bigF(qMid, submissions)
|
||||
}
|
||||
|
||||
return submissionsToKeep;
|
||||
return submissionsToKeep
|
||||
}
|
||||
|
||||
const submissionsWithLowestDropped = keepHelper(
|
||||
droppableSubmissionData, keepHighest, sortPairsDescending
|
||||
);
|
||||
return keepHelper(
|
||||
submissionsWithLowestDropped, keepLowest, sortPairsAscending
|
||||
);
|
||||
droppableSubmissionData,
|
||||
keepHighest,
|
||||
sortPairsDescending
|
||||
)
|
||||
return keepHelper(submissionsWithLowestDropped, keepLowest, sortPairsAscending)
|
||||
}
|
||||
|
||||
function dropUnpointed (submissions, keepHighest, keepLowest) {
|
||||
const sortedSubmissions = submissions.sort(sortSubmissionsAscending);
|
||||
return _.chain(sortedSubmissions).last(keepHighest).first(keepLowest).value();
|
||||
function dropUnpointed(submissions, keepHighest, keepLowest) {
|
||||
const sortedSubmissions = submissions.sort(sortSubmissionsAscending)
|
||||
return _.chain(sortedSubmissions)
|
||||
.last(keepHighest)
|
||||
.first(keepLowest)
|
||||
.value()
|
||||
}
|
||||
|
||||
// I am not going to pretend that this code is understandable.
|
||||
|
@ -148,104 +155,118 @@ function dropUnpointed (submissions, keepHighest, keepLowest) {
|
|||
// Grades" by Daniel Kane and Jonathan Kane. Please see that paper for
|
||||
// a full explanation of the math.
|
||||
// (http://cseweb.ucsd.edu/~dakane/droplowest.pdf)
|
||||
function dropAssignments (allSubmissionData, rules = {}) {
|
||||
let dropLowest = rules.drop_lowest || 0;
|
||||
let dropHighest = rules.drop_highest || 0;
|
||||
const neverDropIds = rules.never_drop || [];
|
||||
function dropAssignments(allSubmissionData, rules = {}) {
|
||||
let dropLowest = rules.drop_lowest || 0
|
||||
let dropHighest = rules.drop_highest || 0
|
||||
const neverDropIds = rules.never_drop || []
|
||||
|
||||
if (!(dropLowest || dropHighest)) {
|
||||
return allSubmissionData;
|
||||
return allSubmissionData
|
||||
}
|
||||
|
||||
let cannotDrop = [];
|
||||
let droppableSubmissionData = allSubmissionData;
|
||||
let cannotDrop = []
|
||||
let droppableSubmissionData = allSubmissionData
|
||||
if (neverDropIds.length > 0) {
|
||||
[cannotDrop, droppableSubmissionData] = partition(allSubmissionData, submission => (
|
||||
;[cannotDrop, droppableSubmissionData] = partition(allSubmissionData, submission =>
|
||||
_.contains(neverDropIds, submission.submission.assignment_id)
|
||||
));
|
||||
)
|
||||
}
|
||||
|
||||
if (droppableSubmissionData.length === 0) {
|
||||
return cannotDrop;
|
||||
return cannotDrop
|
||||
}
|
||||
|
||||
dropLowest = Math.min(dropLowest, droppableSubmissionData.length - 1);
|
||||
dropHighest = (dropLowest + dropHighest) >= droppableSubmissionData.length ? 0 : dropHighest;
|
||||
dropLowest = Math.min(dropLowest, droppableSubmissionData.length - 1)
|
||||
dropHighest = dropLowest + dropHighest >= droppableSubmissionData.length ? 0 : dropHighest
|
||||
|
||||
const keepHighest = droppableSubmissionData.length - dropLowest;
|
||||
const keepLowest = keepHighest - dropHighest;
|
||||
const hasPointed = _.some(droppableSubmissionData, submission => submission.total > 0);
|
||||
const keepHighest = droppableSubmissionData.length - dropLowest
|
||||
const keepLowest = keepHighest - dropHighest
|
||||
const hasPointed = _.some(droppableSubmissionData, submission => submission.total > 0)
|
||||
|
||||
let submissionsToKeep;
|
||||
let submissionsToKeep
|
||||
if (hasPointed) {
|
||||
submissionsToKeep = dropPointed(droppableSubmissionData, cannotDrop, keepHighest, keepLowest);
|
||||
submissionsToKeep = dropPointed(droppableSubmissionData, cannotDrop, keepHighest, keepLowest)
|
||||
} else {
|
||||
submissionsToKeep = dropUnpointed(droppableSubmissionData, keepHighest, keepLowest);
|
||||
submissionsToKeep = dropUnpointed(droppableSubmissionData, keepHighest, keepLowest)
|
||||
}
|
||||
|
||||
submissionsToKeep = [...submissionsToKeep, ...cannotDrop];
|
||||
submissionsToKeep = [...submissionsToKeep, ...cannotDrop]
|
||||
|
||||
_.difference(droppableSubmissionData, submissionsToKeep).forEach((submission) => {
|
||||
submission.drop = true; // eslint-disable-line no-param-reassign
|
||||
});
|
||||
_.difference(droppableSubmissionData, submissionsToKeep).forEach(submission => {
|
||||
submission.drop = true // eslint-disable-line no-param-reassign
|
||||
})
|
||||
|
||||
return submissionsToKeep;
|
||||
return submissionsToKeep
|
||||
}
|
||||
|
||||
function calculateGroupGrade (group, allSubmissions, includeUngraded) {
|
||||
function calculateGroupGrade(group, allSubmissions, includeUngraded) {
|
||||
// Remove assignments without visibility from gradeableAssignments.
|
||||
const hiddenAssignmentsById = _.chain(allSubmissions).filter('hidden').indexBy('assignment_id').value();
|
||||
const gradeableAssignments = _.reject(group.assignments, assignment => (
|
||||
assignment.omit_from_final_grade ||
|
||||
const hiddenAssignmentsById = _.chain(allSubmissions)
|
||||
.filter('hidden')
|
||||
.indexBy('assignment_id')
|
||||
.value()
|
||||
const gradeableAssignments = _.reject(
|
||||
group.assignments,
|
||||
assignment =>
|
||||
assignment.omit_from_final_grade ||
|
||||
hiddenAssignmentsById[assignment.id] ||
|
||||
_.isEqual(assignment.submission_types, ['not_graded'])
|
||||
));
|
||||
const assignments = _.indexBy(gradeableAssignments, 'id');
|
||||
)
|
||||
const assignments = _.indexBy(gradeableAssignments, 'id')
|
||||
|
||||
// Remove submissions from other assignment groups.
|
||||
let submissions = _.filter(allSubmissions, submission => assignments[submission.assignment_id]);
|
||||
let submissions = _.filter(allSubmissions, submission => assignments[submission.assignment_id])
|
||||
|
||||
// To calculate grades for assignments to which the student has not yet
|
||||
// submitted, create a submission stub with a score of `null`.
|
||||
if (includeUngraded) {
|
||||
const submissionAssignmentIds = _.map(submissions, ({ assignment_id }) => assignment_id.toString());
|
||||
const missingAssignmentIds = _.difference(_.keys(assignments), submissionAssignmentIds);
|
||||
const submissionStubs = _.map(missingAssignmentIds, assignmentId => (
|
||||
{ assignment_id: assignmentId, score: null }
|
||||
));
|
||||
submissions = [...submissions, ...submissionStubs];
|
||||
const submissionAssignmentIds = _.map(submissions, ({assignment_id}) =>
|
||||
assignment_id.toString()
|
||||
)
|
||||
const missingAssignmentIds = _.difference(_.keys(assignments), submissionAssignmentIds)
|
||||
const submissionStubs = _.map(missingAssignmentIds, assignmentId => ({
|
||||
assignment_id: assignmentId,
|
||||
score: null
|
||||
}))
|
||||
submissions = [...submissions, ...submissionStubs]
|
||||
}
|
||||
|
||||
// Remove excused submissions.
|
||||
submissions = _.reject(submissions, 'excused');
|
||||
submissions = _.reject(submissions, 'excused')
|
||||
|
||||
const submissionData = _.map(submissions, submission => (
|
||||
{
|
||||
total: parseScore(assignments[submission.assignment_id].points_possible),
|
||||
score: parseScore(submission.score),
|
||||
submitted: submission.score != null && submission.score !== '',
|
||||
pending_review: submission.workflow_state === 'pending_review',
|
||||
submission
|
||||
}
|
||||
));
|
||||
const submissionData = _.map(submissions, submission => ({
|
||||
total: parseScore(assignments[submission.assignment_id].points_possible),
|
||||
score: parseScore(submission.score),
|
||||
submitted: submission.score != null && submission.score !== '',
|
||||
pending_review: submission.workflow_state === 'pending_review',
|
||||
submission
|
||||
}))
|
||||
|
||||
let relevantSubmissionData = submissionData;
|
||||
let relevantSubmissionData = submissionData
|
||||
if (!includeUngraded) {
|
||||
relevantSubmissionData = _.filter(submissionData, submission => (
|
||||
submission.submitted && !submission.pending_review
|
||||
));
|
||||
relevantSubmissionData = _.filter(
|
||||
submissionData,
|
||||
submission => submission.submitted && !submission.pending_review
|
||||
)
|
||||
}
|
||||
|
||||
const submissionsToKeep = dropAssignments(relevantSubmissionData, group.rules);
|
||||
const score = sum(_.chain(submissionsToKeep).map('score').map(parseScore).value());
|
||||
const possible = sumBy(submissionsToKeep, 'total');
|
||||
const submissionsToKeep = dropAssignments(relevantSubmissionData, group.rules)
|
||||
const score = sum(
|
||||
_.chain(submissionsToKeep)
|
||||
.map('score')
|
||||
.map(parseScore)
|
||||
.value()
|
||||
)
|
||||
const possible = sumBy(submissionsToKeep, 'total')
|
||||
|
||||
return {
|
||||
score,
|
||||
possible,
|
||||
submission_count: _.filter(submissionData, 'submitted').length,
|
||||
submissions: _.map(submissionData, submissionDatum => {
|
||||
const percent = submissionDatum.total ? divide(submissionDatum.score, submissionDatum.total) : 0
|
||||
const percent = submissionDatum.total
|
||||
? divide(submissionDatum.score, submissionDatum.total)
|
||||
: 0
|
||||
return {
|
||||
drop: submissionDatum.drop,
|
||||
percent: parseScore(percent),
|
||||
|
@ -255,7 +276,7 @@ function calculateGroupGrade (group, allSubmissions, includeUngraded) {
|
|||
submitted: submissionDatum.submitted
|
||||
}
|
||||
})
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Each submission requires the following properties:
|
||||
|
@ -300,15 +321,15 @@ function calculateGroupGrade (group, allSubmissions, includeUngraded) {
|
|||
// final: <AssignmentGroup Grade *see above>
|
||||
// scoreUnit: 'points'
|
||||
// }
|
||||
function calculate (allSubmissions, assignmentGroup) {
|
||||
const submissions = _.uniq(allSubmissions, 'assignment_id');
|
||||
function calculate(allSubmissions, assignmentGroup) {
|
||||
const submissions = _.uniq(allSubmissions, 'assignment_id')
|
||||
return {
|
||||
assignmentGroupId: assignmentGroup.id,
|
||||
assignmentGroupWeight: assignmentGroup.group_weight,
|
||||
current: calculateGroupGrade(assignmentGroup, submissions, false),
|
||||
final: calculateGroupGrade(assignmentGroup, submissions, true),
|
||||
scoreUnit: 'points'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
|
|
|
@ -19,72 +19,80 @@
|
|||
import _ from 'underscore'
|
||||
import tz from 'timezone'
|
||||
|
||||
function addStudentID(student, collection = []) {
|
||||
return collection.concat([student.id]);
|
||||
function addStudentID(student, collection = []) {
|
||||
return collection.concat([student.id])
|
||||
}
|
||||
|
||||
function studentIDCollections(students) {
|
||||
const sections = {}
|
||||
const groups = {}
|
||||
|
||||
_.each(students, function(student) {
|
||||
_.each(
|
||||
student.sections,
|
||||
sectionID => (sections[sectionID] = addStudentID(student, sections[sectionID]))
|
||||
)
|
||||
_.each(student.group_ids, groupID => (groups[groupID] = addStudentID(student, groups[groupID])))
|
||||
})
|
||||
|
||||
return {studentIDsInSections: sections, studentIDsInGroups: groups}
|
||||
}
|
||||
|
||||
function studentIDsOnOverride(override, sections, groups) {
|
||||
if (override.student_ids) {
|
||||
return override.student_ids
|
||||
} else if (override.course_section_id && sections[override.course_section_id]) {
|
||||
return sections[override.course_section_id]
|
||||
} else if (override.group_id && groups[override.group_id]) {
|
||||
return groups[override.group_id]
|
||||
} else {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
function studentIDCollections(students) {
|
||||
const sections = {};
|
||||
const groups = {};
|
||||
|
||||
_.each(students, function(student) {
|
||||
_.each(student.sections, sectionID => sections[sectionID] = addStudentID(student, sections[sectionID]));
|
||||
_.each(student.group_ids, groupID => groups[groupID] = addStudentID(student, groups[groupID]));
|
||||
});
|
||||
|
||||
return { studentIDsInSections: sections, studentIDsInGroups: groups };
|
||||
function getLatestDefinedDate(newDate, existingDate) {
|
||||
if (existingDate === undefined || newDate === null) {
|
||||
return newDate
|
||||
} else if (existingDate !== null && newDate > existingDate) {
|
||||
return newDate
|
||||
} else {
|
||||
return existingDate
|
||||
}
|
||||
}
|
||||
|
||||
function studentIDsOnOverride(override, sections, groups) {
|
||||
if (override.student_ids) {
|
||||
return override.student_ids;
|
||||
} else if (override.course_section_id && sections[override.course_section_id]) {
|
||||
return sections[override.course_section_id];
|
||||
} else if (override.group_id && groups[override.group_id]) {
|
||||
return groups[override.group_id];
|
||||
} else {
|
||||
return [];
|
||||
function effectiveDueDatesOnOverride(
|
||||
studentIDsInSections,
|
||||
studentIDsInGroups,
|
||||
studentDueDateMap,
|
||||
override
|
||||
) {
|
||||
const studentIDs = studentIDsOnOverride(override, studentIDsInSections, studentIDsInGroups)
|
||||
|
||||
_.each(studentIDs, function(studentID) {
|
||||
const existingDate = studentDueDateMap[studentID]
|
||||
const newDate = tz.parse(override.due_at)
|
||||
studentDueDateMap[studentID] = getLatestDefinedDate(newDate, existingDate)
|
||||
})
|
||||
|
||||
return studentDueDateMap
|
||||
}
|
||||
|
||||
function effectiveDueDatesForAssignment(assignment, overrides, students) {
|
||||
const {studentIDsInSections, studentIDsInGroups} = studentIDCollections(students)
|
||||
|
||||
const dates = _.reduce(
|
||||
overrides,
|
||||
effectiveDueDatesOnOverride.bind(this, studentIDsInSections, studentIDsInGroups),
|
||||
{}
|
||||
)
|
||||
|
||||
_.each(students, function(student) {
|
||||
if (dates[student.id] === undefined && !assignment.only_visible_to_overrides) {
|
||||
dates[student.id] = tz.parse(assignment.due_at)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
function getLatestDefinedDate(newDate, existingDate) {
|
||||
if (existingDate === undefined || newDate === null) {
|
||||
return newDate;
|
||||
} else if (existingDate !== null && newDate > existingDate) {
|
||||
return newDate;
|
||||
} else {
|
||||
return existingDate;
|
||||
}
|
||||
}
|
||||
return dates
|
||||
}
|
||||
|
||||
function effectiveDueDatesOnOverride(studentIDsInSections, studentIDsInGroups, studentDueDateMap, override) {
|
||||
const studentIDs = studentIDsOnOverride(override, studentIDsInSections, studentIDsInGroups);
|
||||
|
||||
_.each(studentIDs, function(studentID) {
|
||||
const existingDate = studentDueDateMap[studentID];
|
||||
const newDate = tz.parse(override.due_at);
|
||||
studentDueDateMap[studentID] = getLatestDefinedDate(newDate, existingDate);
|
||||
});
|
||||
|
||||
return studentDueDateMap;
|
||||
}
|
||||
|
||||
function effectiveDueDatesForAssignment(assignment, overrides, students) {
|
||||
const { studentIDsInSections, studentIDsInGroups } = studentIDCollections(students);
|
||||
|
||||
const dates = _.reduce(
|
||||
overrides,
|
||||
effectiveDueDatesOnOverride.bind(this, studentIDsInSections, studentIDsInGroups),
|
||||
{}
|
||||
);
|
||||
|
||||
_.each(students, function(student) {
|
||||
if (dates[student.id] === undefined && !assignment.only_visible_to_overrides) {
|
||||
dates[student.id] = tz.parse(assignment.due_at);
|
||||
}
|
||||
});
|
||||
|
||||
return dates;
|
||||
}
|
||||
|
||||
export default { effectiveDueDatesForAssignment }
|
||||
export default {effectiveDueDatesForAssignment}
|
||||
|
|
|
@ -19,31 +19,37 @@
|
|||
import _ from 'underscore'
|
||||
import round from 'compiled/util/round'
|
||||
import AssignmentGroupGradeCalculator from '../gradebook/AssignmentGroupGradeCalculator'
|
||||
import {bigSum, sum, sumBy, toNumber, weightedPercent} from './shared/helpers/GradeCalculationHelper'
|
||||
import {
|
||||
bigSum,
|
||||
sum,
|
||||
sumBy,
|
||||
toNumber,
|
||||
weightedPercent
|
||||
} from './shared/helpers/GradeCalculationHelper'
|
||||
|
||||
function combineAssignmentGroupGrades (assignmentGroupGrades, includeUngraded, options) {
|
||||
const scopedAssignmentGroupGrades = _.map(assignmentGroupGrades, (assignmentGroupGrade) => {
|
||||
const gradeVersion = includeUngraded ? assignmentGroupGrade.final : assignmentGroupGrade.current;
|
||||
return { ...gradeVersion, weight: assignmentGroupGrade.assignmentGroupWeight };
|
||||
});
|
||||
function combineAssignmentGroupGrades(assignmentGroupGrades, includeUngraded, options) {
|
||||
const scopedAssignmentGroupGrades = _.map(assignmentGroupGrades, assignmentGroupGrade => {
|
||||
const gradeVersion = includeUngraded ? assignmentGroupGrade.final : assignmentGroupGrade.current
|
||||
return {...gradeVersion, weight: assignmentGroupGrade.assignmentGroupWeight}
|
||||
})
|
||||
|
||||
if (options.weightAssignmentGroups) {
|
||||
const relevantGroupGrades = scopedAssignmentGroupGrades.filter(grade => grade.possible);
|
||||
const fullWeight = sumBy(relevantGroupGrades, 'weight');
|
||||
const relevantGroupGrades = scopedAssignmentGroupGrades.filter(grade => grade.possible)
|
||||
const fullWeight = sumBy(relevantGroupGrades, 'weight')
|
||||
|
||||
let finalGrade = bigSum(_.map(relevantGroupGrades, weightedPercent));
|
||||
let finalGrade = bigSum(_.map(relevantGroupGrades, weightedPercent))
|
||||
if (fullWeight === 0) {
|
||||
finalGrade = null;
|
||||
finalGrade = null
|
||||
} else if (fullWeight < 100) {
|
||||
finalGrade = toNumber(weightedPercent({score: finalGrade, possible: fullWeight, weight: 100}));
|
||||
finalGrade = toNumber(weightedPercent({score: finalGrade, possible: fullWeight, weight: 100}))
|
||||
}
|
||||
|
||||
const submissionCount = sumBy(relevantGroupGrades, 'submission_count');
|
||||
const possible = ((submissionCount > 0) || includeUngraded) ? 100 : 0;
|
||||
let score = finalGrade && round(finalGrade, 2);
|
||||
score = isNaN(score) ? null : score;
|
||||
const submissionCount = sumBy(relevantGroupGrades, 'submission_count')
|
||||
const possible = submissionCount > 0 || includeUngraded ? 100 : 0
|
||||
let score = finalGrade && round(finalGrade, 2)
|
||||
score = isNaN(score) ? null : score
|
||||
|
||||
return { score, possible };
|
||||
return {score, possible}
|
||||
}
|
||||
|
||||
return {
|
||||
|
@ -52,63 +58,68 @@ function combineAssignmentGroupGrades (assignmentGroupGrades, includeUngraded, o
|
|||
}
|
||||
}
|
||||
|
||||
function combineGradingPeriodGrades (gradingPeriodGradesByPeriodId, includeUngraded) {
|
||||
let scopedGradingPeriodGrades = _.map(gradingPeriodGradesByPeriodId, (gradingPeriodGrade) => {
|
||||
const gradeVersion = includeUngraded ? gradingPeriodGrade.final : gradingPeriodGrade.current;
|
||||
return { ...gradeVersion, weight: gradingPeriodGrade.gradingPeriodWeight };
|
||||
});
|
||||
function combineGradingPeriodGrades(gradingPeriodGradesByPeriodId, includeUngraded) {
|
||||
let scopedGradingPeriodGrades = _.map(gradingPeriodGradesByPeriodId, gradingPeriodGrade => {
|
||||
const gradeVersion = includeUngraded ? gradingPeriodGrade.final : gradingPeriodGrade.current
|
||||
return {...gradeVersion, weight: gradingPeriodGrade.gradingPeriodWeight}
|
||||
})
|
||||
|
||||
if (!includeUngraded) {
|
||||
scopedGradingPeriodGrades = _.filter(scopedGradingPeriodGrades, 'possible');
|
||||
scopedGradingPeriodGrades = _.filter(scopedGradingPeriodGrades, 'possible')
|
||||
}
|
||||
|
||||
const scoreSum = bigSum(_.map(scopedGradingPeriodGrades, weightedPercent));
|
||||
const totalWeight = sumBy(scopedGradingPeriodGrades, 'weight');
|
||||
const totalScore = totalWeight === 0 ? 0 : toNumber(scoreSum.times(100).div(Math.min(totalWeight, 100)));
|
||||
const scoreSum = bigSum(_.map(scopedGradingPeriodGrades, weightedPercent))
|
||||
const totalWeight = sumBy(scopedGradingPeriodGrades, 'weight')
|
||||
const totalScore =
|
||||
totalWeight === 0 ? 0 : toNumber(scoreSum.times(100).div(Math.min(totalWeight, 100)))
|
||||
|
||||
return {
|
||||
score: round(totalScore, 2),
|
||||
possible: 100
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function divideGroupByGradingPeriods (assignmentGroup, effectiveDueDates) {
|
||||
function divideGroupByGradingPeriods(assignmentGroup, effectiveDueDates) {
|
||||
// When using weighted grading periods, assignment groups must not contain assignments due in different grading
|
||||
// periods. This allows for calculated assignment group grades in closed grading periods to be accidentally
|
||||
// changed if a related assignment is considered to be in an open grading period.
|
||||
//
|
||||
// To avoid this, assignment groups meeting this criteria are "divided" (duplicated) in a way where each
|
||||
// instance of the assignment group includes assignments only from one grading period.
|
||||
const assignmentsByGradingPeriodId = _.groupBy(assignmentGroup.assignments, assignment => (
|
||||
effectiveDueDates[assignment.id].grading_period_id
|
||||
));
|
||||
return _.map(assignmentsByGradingPeriodId, assignments => (
|
||||
{ ...assignmentGroup, assignments }
|
||||
));
|
||||
const assignmentsByGradingPeriodId = _.groupBy(
|
||||
assignmentGroup.assignments,
|
||||
assignment => effectiveDueDates[assignment.id].grading_period_id
|
||||
)
|
||||
return _.map(assignmentsByGradingPeriodId, assignments => ({...assignmentGroup, assignments}))
|
||||
}
|
||||
|
||||
function extractPeriodBasedAssignmentGroups (assignmentGroups, effectiveDueDates) {
|
||||
return _.reduce(assignmentGroups, (periodBasedGroups, assignmentGroup) => {
|
||||
const assignedAssignments = _.filter(assignmentGroup.assignments, assignment => (
|
||||
effectiveDueDates[assignment.id]
|
||||
));
|
||||
if (assignedAssignments.length > 0) {
|
||||
const groupWithAssignedAssignments = { ...assignmentGroup, assignments: assignedAssignments };
|
||||
return [
|
||||
...periodBasedGroups,
|
||||
...divideGroupByGradingPeriods(groupWithAssignedAssignments, effectiveDueDates)
|
||||
];
|
||||
}
|
||||
return periodBasedGroups;
|
||||
}, []);
|
||||
function extractPeriodBasedAssignmentGroups(assignmentGroups, effectiveDueDates) {
|
||||
return _.reduce(
|
||||
assignmentGroups,
|
||||
(periodBasedGroups, assignmentGroup) => {
|
||||
const assignedAssignments = _.filter(
|
||||
assignmentGroup.assignments,
|
||||
assignment => effectiveDueDates[assignment.id]
|
||||
)
|
||||
if (assignedAssignments.length > 0) {
|
||||
const groupWithAssignedAssignments = {...assignmentGroup, assignments: assignedAssignments}
|
||||
return [
|
||||
...periodBasedGroups,
|
||||
...divideGroupByGradingPeriods(groupWithAssignedAssignments, effectiveDueDates)
|
||||
]
|
||||
}
|
||||
return periodBasedGroups
|
||||
},
|
||||
[]
|
||||
)
|
||||
}
|
||||
|
||||
function recombinePeriodBasedAssignmentGroupGrades (grades) {
|
||||
const map = {};
|
||||
function recombinePeriodBasedAssignmentGroupGrades(grades) {
|
||||
const map = {}
|
||||
|
||||
for (let g = 0; g < grades.length; g++) {
|
||||
const grade = grades[g]
|
||||
const previousGrade = map[grade.assignmentGroupId];
|
||||
const previousGrade = map[grade.assignmentGroupId]
|
||||
|
||||
if (previousGrade) {
|
||||
map[grade.assignmentGroupId] = {
|
||||
|
@ -125,40 +136,49 @@ function recombinePeriodBasedAssignmentGroupGrades (grades) {
|
|||
score: sum([previousGrade.final.score, grade.final.score]),
|
||||
possible: sum([previousGrade.final.possible, grade.final.possible])
|
||||
}
|
||||
};
|
||||
}
|
||||
} else {
|
||||
map[grade.assignmentGroupId] = grade;
|
||||
map[grade.assignmentGroupId] = grade
|
||||
}
|
||||
}
|
||||
|
||||
return map;
|
||||
return map
|
||||
}
|
||||
|
||||
function calculateWithGradingPeriods (submissions, assignmentGroups, gradingPeriods, effectiveDueDates, options) {
|
||||
const periodBasedGroups = extractPeriodBasedAssignmentGroups(assignmentGroups, effectiveDueDates);
|
||||
function calculateWithGradingPeriods(
|
||||
submissions,
|
||||
assignmentGroups,
|
||||
gradingPeriods,
|
||||
effectiveDueDates,
|
||||
options
|
||||
) {
|
||||
const periodBasedGroups = extractPeriodBasedAssignmentGroups(assignmentGroups, effectiveDueDates)
|
||||
|
||||
const assignmentGroupsByGradingPeriodId = _.groupBy(periodBasedGroups, (assignmentGroup) => {
|
||||
const assignmentId = assignmentGroup.assignments[0].id;
|
||||
return effectiveDueDates[assignmentId].grading_period_id;
|
||||
});
|
||||
const assignmentGroupsByGradingPeriodId = _.groupBy(periodBasedGroups, assignmentGroup => {
|
||||
const assignmentId = assignmentGroup.assignments[0].id
|
||||
return effectiveDueDates[assignmentId].grading_period_id
|
||||
})
|
||||
|
||||
const gradingPeriodsById = _.indexBy(gradingPeriods, 'id');
|
||||
const gradingPeriodGradesByPeriodId = {};
|
||||
const periodBasedAssignmentGroupGrades = [];
|
||||
const gradingPeriodsById = _.indexBy(gradingPeriods, 'id')
|
||||
const gradingPeriodGradesByPeriodId = {}
|
||||
const periodBasedAssignmentGroupGrades = []
|
||||
|
||||
for (let gp = 0; gp < gradingPeriods.length; gp++) {
|
||||
const groupGrades = {};
|
||||
const groupGrades = {}
|
||||
const gradingPeriod = gradingPeriods[gp]
|
||||
|
||||
const assignmentGroupsInPeriod = assignmentGroupsByGradingPeriodId[gradingPeriod.id] || []
|
||||
for (let ag = 0; ag < assignmentGroupsInPeriod.length; ag++) {
|
||||
const assignmentGroup = assignmentGroupsInPeriod[ag]
|
||||
|
||||
groupGrades[assignmentGroup.id] = AssignmentGroupGradeCalculator.calculate(submissions, assignmentGroup);
|
||||
periodBasedAssignmentGroupGrades.push(groupGrades[assignmentGroup.id]);
|
||||
groupGrades[assignmentGroup.id] = AssignmentGroupGradeCalculator.calculate(
|
||||
submissions,
|
||||
assignmentGroup
|
||||
)
|
||||
periodBasedAssignmentGroupGrades.push(groupGrades[assignmentGroup.id])
|
||||
}
|
||||
|
||||
const groupGradesList = _.values(groupGrades);
|
||||
const groupGradesList = _.values(groupGrades)
|
||||
|
||||
gradingPeriodGradesByPeriodId[gradingPeriod.id] = {
|
||||
gradingPeriodId: gradingPeriod.id,
|
||||
|
@ -167,7 +187,7 @@ function calculateWithGradingPeriods (submissions, assignmentGroups, gradingPeri
|
|||
current: combineAssignmentGroupGrades(groupGradesList, false, options),
|
||||
final: combineAssignmentGroupGrades(groupGradesList, true, options),
|
||||
scoreUnit: options.weightAssignmentGroups ? 'percentage' : 'points'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (options.weightGradingPeriods) {
|
||||
|
@ -177,12 +197,12 @@ function calculateWithGradingPeriods (submissions, assignmentGroups, gradingPeri
|
|||
current: combineGradingPeriodGrades(gradingPeriodGradesByPeriodId, false, options),
|
||||
final: combineGradingPeriodGrades(gradingPeriodGradesByPeriodId, true, options),
|
||||
scoreUnit: 'percentage'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const allAssignmentGroupGrades = _.map(assignmentGroups, assignmentGroup => (
|
||||
const allAssignmentGroupGrades = _.map(assignmentGroups, assignmentGroup =>
|
||||
AssignmentGroupGradeCalculator.calculate(submissions, assignmentGroup)
|
||||
));
|
||||
)
|
||||
|
||||
return {
|
||||
assignmentGroups: _.indexBy(allAssignmentGroupGrades, grade => grade.assignmentGroupId),
|
||||
|
@ -190,20 +210,20 @@ function calculateWithGradingPeriods (submissions, assignmentGroups, gradingPeri
|
|||
current: combineAssignmentGroupGrades(allAssignmentGroupGrades, false, options),
|
||||
final: combineAssignmentGroupGrades(allAssignmentGroupGrades, true, options),
|
||||
scoreUnit: options.weightAssignmentGroups ? 'percentage' : 'points'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function calculateWithoutGradingPeriods (submissions, assignmentGroups, options) {
|
||||
const assignmentGroupGrades = _.map(assignmentGroups, assignmentGroup => (
|
||||
function calculateWithoutGradingPeriods(submissions, assignmentGroups, options) {
|
||||
const assignmentGroupGrades = _.map(assignmentGroups, assignmentGroup =>
|
||||
AssignmentGroupGradeCalculator.calculate(submissions, assignmentGroup)
|
||||
));
|
||||
)
|
||||
|
||||
return {
|
||||
assignmentGroups: _.indexBy(assignmentGroupGrades, grade => grade.assignmentGroupId),
|
||||
current: combineAssignmentGroupGrades(assignmentGroupGrades, false, options),
|
||||
final: combineAssignmentGroupGrades(assignmentGroupGrades, true, options),
|
||||
scoreUnit: options.weightAssignmentGroups ? 'percentage' : 'points'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Each submission requires the following properties:
|
||||
|
@ -304,19 +324,29 @@ function calculateWithoutGradingPeriods (submissions, assignmentGroups, options)
|
|||
// final: <AssignmentGroup Grade *see above>
|
||||
// scoreUnit: 'points'|'percent'
|
||||
// }
|
||||
function calculate (submissions, assignmentGroups, weightingScheme, gradingPeriodSet, effectiveDueDates) {
|
||||
function calculate(
|
||||
submissions,
|
||||
assignmentGroups,
|
||||
weightingScheme,
|
||||
gradingPeriodSet,
|
||||
effectiveDueDates
|
||||
) {
|
||||
const options = {
|
||||
weightGradingPeriods: gradingPeriodSet && !!gradingPeriodSet.weighted,
|
||||
weightAssignmentGroups: weightingScheme === 'percent'
|
||||
};
|
||||
}
|
||||
|
||||
if (gradingPeriodSet && effectiveDueDates) {
|
||||
return calculateWithGradingPeriods(
|
||||
submissions, assignmentGroups, gradingPeriodSet.gradingPeriods, effectiveDueDates, options
|
||||
);
|
||||
submissions,
|
||||
assignmentGroups,
|
||||
gradingPeriodSet.gradingPeriods,
|
||||
effectiveDueDates,
|
||||
options
|
||||
)
|
||||
}
|
||||
|
||||
return calculateWithoutGradingPeriods(submissions, assignmentGroups, options);
|
||||
return calculateWithoutGradingPeriods(submissions, assignmentGroups, options)
|
||||
}
|
||||
|
||||
export default {
|
||||
|
|
|
@ -20,135 +20,145 @@ import $ from 'jquery'
|
|||
import cheaterDepaginate from '../shared/CheatDepaginator'
|
||||
import _ from 'underscore'
|
||||
|
||||
function getGradingPeriodAssignments (courseId) {
|
||||
const url = `/courses/${courseId}/gradebook/grading_period_assignments`;
|
||||
return $.ajaxJSON(url, 'GET', {});
|
||||
function getGradingPeriodAssignments(courseId) {
|
||||
const url = `/courses/${courseId}/gradebook/grading_period_assignments`
|
||||
return $.ajaxJSON(url, 'GET', {})
|
||||
}
|
||||
|
||||
// loaders
|
||||
const getAssignmentGroups = (url, params) => {
|
||||
return cheaterDepaginate(url, params);
|
||||
};
|
||||
const getCustomColumns = (url) => {
|
||||
return $.ajaxJSON(url, "GET", {});
|
||||
};
|
||||
const getSections = (url) => {
|
||||
return $.ajaxJSON(url, "GET", {});
|
||||
};
|
||||
// loaders
|
||||
const getAssignmentGroups = (url, params) => {
|
||||
return cheaterDepaginate(url, params)
|
||||
}
|
||||
const getCustomColumns = url => {
|
||||
return $.ajaxJSON(url, 'GET', {})
|
||||
}
|
||||
const getSections = url => {
|
||||
return $.ajaxJSON(url, 'GET', {})
|
||||
}
|
||||
|
||||
// submission loading is tricky
|
||||
let pendingStudentsForSubmissions;
|
||||
let submissionsLoaded;
|
||||
let studentsLoaded;
|
||||
let submissionChunkCount;
|
||||
let gotSubmissionChunkCount;
|
||||
let submissionsLoading = false;
|
||||
let submissionURL;
|
||||
let submissionParams;
|
||||
let submissionChunkSize;
|
||||
let submissionChunkCb;
|
||||
// submission loading is tricky
|
||||
let pendingStudentsForSubmissions
|
||||
let submissionsLoaded
|
||||
let studentsLoaded
|
||||
let submissionChunkCount
|
||||
let gotSubmissionChunkCount
|
||||
let submissionsLoading = false
|
||||
let submissionURL
|
||||
let submissionParams
|
||||
let submissionChunkSize
|
||||
let submissionChunkCb
|
||||
|
||||
const gotSubmissionsChunk = (data) => {
|
||||
gotSubmissionChunkCount++;
|
||||
submissionChunkCb(data);
|
||||
const gotSubmissionsChunk = data => {
|
||||
gotSubmissionChunkCount++
|
||||
submissionChunkCb(data)
|
||||
|
||||
if (gotSubmissionChunkCount === submissionChunkCount &&
|
||||
studentsLoaded.isResolved()) {
|
||||
submissionsLoaded.resolve();
|
||||
if (gotSubmissionChunkCount === submissionChunkCount && studentsLoaded.isResolved()) {
|
||||
submissionsLoaded.resolve()
|
||||
}
|
||||
}
|
||||
|
||||
const getPendingSubmissions = () => {
|
||||
while (pendingStudentsForSubmissions.length) {
|
||||
const studentIds = pendingStudentsForSubmissions.splice(0, submissionChunkSize)
|
||||
submissionChunkCount++
|
||||
cheaterDepaginate(submissionURL, {student_ids: studentIds, ...submissionParams}).then(
|
||||
gotSubmissionsChunk
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const getSubmissions = (url, params, cb, chunkSize) => {
|
||||
submissionURL = url
|
||||
submissionParams = params
|
||||
submissionChunkCb = cb
|
||||
submissionChunkSize = chunkSize
|
||||
|
||||
submissionsLoaded = $.Deferred()
|
||||
submissionChunkCount = 0
|
||||
gotSubmissionChunkCount = 0
|
||||
|
||||
submissionsLoading = true
|
||||
getPendingSubmissions()
|
||||
return submissionsLoaded
|
||||
}
|
||||
|
||||
const getStudents = (url, params, studentChunkCb) => {
|
||||
pendingStudentsForSubmissions = []
|
||||
|
||||
const gotStudentPage = students => {
|
||||
studentChunkCb(students)
|
||||
|
||||
const studentIds = _.pluck(students, 'id')
|
||||
;[].push.apply(pendingStudentsForSubmissions, studentIds)
|
||||
|
||||
if (submissionsLoading) {
|
||||
getPendingSubmissions()
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const getPendingSubmissions = () => {
|
||||
while (pendingStudentsForSubmissions.length) {
|
||||
const studentIds = pendingStudentsForSubmissions.splice(0, submissionChunkSize);
|
||||
submissionChunkCount++;
|
||||
cheaterDepaginate(submissionURL, { student_ids: studentIds, ...submissionParams }).then(gotSubmissionsChunk);
|
||||
}
|
||||
};
|
||||
studentsLoaded = cheaterDepaginate(url, params, gotStudentPage)
|
||||
return studentsLoaded
|
||||
}
|
||||
|
||||
const getSubmissions = (url, params, cb, chunkSize) => {
|
||||
submissionURL = url;
|
||||
submissionParams = params;
|
||||
submissionChunkCb = cb;
|
||||
submissionChunkSize = chunkSize;
|
||||
const getDataForColumn = (column, url, params, cb) => {
|
||||
url = url.replace(/:id/, column.id)
|
||||
const augmentedCallback = data => cb(column, data)
|
||||
return cheaterDepaginate(url, params, augmentedCallback)
|
||||
}
|
||||
|
||||
submissionsLoaded = $.Deferred();
|
||||
submissionChunkCount = 0;
|
||||
gotSubmissionChunkCount = 0;
|
||||
const getCustomColumnData = (url, params, cb, customColumnsDfd, waitForDfds) => {
|
||||
const customColumnDataLoaded = $.Deferred()
|
||||
let customColumnDataDfds
|
||||
|
||||
submissionsLoading = true;
|
||||
getPendingSubmissions();
|
||||
return submissionsLoaded;
|
||||
};
|
||||
// waitForDfds ensures that custom column data is loaded *last*
|
||||
$.when.apply($, waitForDfds).then(() => {
|
||||
customColumnsDfd.then(customColumns => {
|
||||
customColumnDataDfds = customColumns.map(col => getDataForColumn(col, url, params, cb))
|
||||
})
|
||||
})
|
||||
|
||||
const getStudents = (url, params, studentChunkCb) => {
|
||||
pendingStudentsForSubmissions = [];
|
||||
$.when.apply($, customColumnDataDfds).then(() => customColumnDataLoaded.resolve())
|
||||
|
||||
const gotStudentPage = (students) => {
|
||||
studentChunkCb(students);
|
||||
return customColumnDataLoaded
|
||||
}
|
||||
|
||||
const studentIds = _.pluck(students, 'id');
|
||||
[].push.apply(pendingStudentsForSubmissions, studentIds);
|
||||
const loadGradebookData = opts => {
|
||||
const gotAssignmentGroups = getAssignmentGroups(
|
||||
opts.assignmentGroupsURL,
|
||||
opts.assignmentGroupsParams
|
||||
)
|
||||
if (opts.onlyLoadAssignmentGroups) {
|
||||
return {gotAssignmentGroups}
|
||||
}
|
||||
|
||||
if (submissionsLoading) {
|
||||
getPendingSubmissions();
|
||||
}
|
||||
};
|
||||
let gotGradingPeriodAssignments
|
||||
if (opts.getGradingPeriodAssignments) {
|
||||
gotGradingPeriodAssignments = getGradingPeriodAssignments(opts.courseId)
|
||||
}
|
||||
const gotCustomColumns = getCustomColumns(opts.customColumnsURL)
|
||||
const gotStudents = getStudents(opts.studentsURL, opts.studentsParams, opts.studentsPageCb)
|
||||
const gotSubmissions = getSubmissions(
|
||||
opts.submissionsURL,
|
||||
opts.submissionsParams,
|
||||
opts.submissionsChunkCb,
|
||||
opts.submissionsChunkSize
|
||||
)
|
||||
const gotCustomColumnData = getCustomColumnData(
|
||||
opts.customColumnDataURL,
|
||||
opts.customColumnDataParams,
|
||||
opts.customColumnDataPageCb,
|
||||
gotCustomColumns,
|
||||
[gotSubmissions]
|
||||
)
|
||||
|
||||
studentsLoaded = cheaterDepaginate(url, params, gotStudentPage);
|
||||
return studentsLoaded;
|
||||
};
|
||||
return {
|
||||
gotAssignmentGroups,
|
||||
gotCustomColumns,
|
||||
gotGradingPeriodAssignments,
|
||||
gotStudents,
|
||||
gotSubmissions,
|
||||
gotCustomColumnData
|
||||
}
|
||||
}
|
||||
|
||||
const getDataForColumn = (column, url, params, cb) => {
|
||||
url = url.replace(/:id/, column.id);
|
||||
const augmentedCallback = (data) => cb(column, data);
|
||||
return cheaterDepaginate(url, params, augmentedCallback);
|
||||
};
|
||||
|
||||
const getCustomColumnData = (url, params, cb, customColumnsDfd, waitForDfds) => {
|
||||
const customColumnDataLoaded = $.Deferred();
|
||||
let customColumnDataDfds;
|
||||
|
||||
// waitForDfds ensures that custom column data is loaded *last*
|
||||
$.when.apply($, waitForDfds).then(() => {
|
||||
customColumnsDfd.then(customColumns => {
|
||||
customColumnDataDfds = customColumns.map(col => getDataForColumn(col, url, params, cb));
|
||||
});
|
||||
});
|
||||
|
||||
$.when.apply($, customColumnDataDfds)
|
||||
.then(() => customColumnDataLoaded.resolve());
|
||||
|
||||
return customColumnDataLoaded;
|
||||
};
|
||||
|
||||
const loadGradebookData = (opts) => {
|
||||
const gotAssignmentGroups = getAssignmentGroups(opts.assignmentGroupsURL, opts.assignmentGroupsParams);
|
||||
if (opts.onlyLoadAssignmentGroups) {
|
||||
return { gotAssignmentGroups };
|
||||
}
|
||||
|
||||
let gotGradingPeriodAssignments;
|
||||
if (opts.getGradingPeriodAssignments) {
|
||||
gotGradingPeriodAssignments = getGradingPeriodAssignments(opts.courseId);
|
||||
}
|
||||
const gotCustomColumns = getCustomColumns(opts.customColumnsURL);
|
||||
const gotStudents = getStudents(opts.studentsURL, opts.studentsParams, opts.studentsPageCb);
|
||||
const gotSubmissions = getSubmissions(opts.submissionsURL, opts.submissionsParams, opts.submissionsChunkCb, opts.submissionsChunkSize);
|
||||
const gotCustomColumnData = getCustomColumnData(opts.customColumnDataURL,
|
||||
opts.customColumnDataParams,
|
||||
opts.customColumnDataPageCb,
|
||||
gotCustomColumns,
|
||||
[gotSubmissions]);
|
||||
|
||||
return {
|
||||
gotAssignmentGroups,
|
||||
gotCustomColumns,
|
||||
gotGradingPeriodAssignments,
|
||||
gotStudents,
|
||||
gotSubmissions,
|
||||
gotCustomColumnData
|
||||
};
|
||||
};
|
||||
|
||||
export default { loadGradebookData: loadGradebookData, getDataForColumn: getDataForColumn }
|
||||
export default {loadGradebookData: loadGradebookData, getDataForColumn: getDataForColumn}
|
||||
|
|
|
@ -16,43 +16,43 @@
|
|||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import _ from 'lodash';
|
||||
import timezone from 'timezone';
|
||||
import GradingPeriodsHelper from '../grading/helpers/GradingPeriodsHelper';
|
||||
import _ from 'lodash'
|
||||
import timezone from 'timezone'
|
||||
import GradingPeriodsHelper from '../grading/helpers/GradingPeriodsHelper'
|
||||
|
||||
export function scopeToUser (dueDateData, userId) {
|
||||
const scopedData = {};
|
||||
export function scopeToUser(dueDateData, userId) {
|
||||
const scopedData = {}
|
||||
_.forEach(dueDateData, (dueDateDataByUserId, assignmentId) => {
|
||||
if (dueDateDataByUserId[userId]) {
|
||||
scopedData[assignmentId] = dueDateDataByUserId[userId];
|
||||
scopedData[assignmentId] = dueDateDataByUserId[userId]
|
||||
}
|
||||
});
|
||||
return scopedData;
|
||||
})
|
||||
return scopedData
|
||||
}
|
||||
|
||||
export function updateWithSubmissions (effectiveDueDates, submissions, gradingPeriods = []) {
|
||||
const helper = new GradingPeriodsHelper(gradingPeriods);
|
||||
const sortedPeriods = _.sortBy(gradingPeriods, 'startDate');
|
||||
export function updateWithSubmissions(effectiveDueDates, submissions, gradingPeriods = []) {
|
||||
const helper = new GradingPeriodsHelper(gradingPeriods)
|
||||
const sortedPeriods = _.sortBy(gradingPeriods, 'startDate')
|
||||
|
||||
submissions.forEach((submission) => {
|
||||
const dueDate = timezone.parse(submission.cached_due_date);
|
||||
submissions.forEach(submission => {
|
||||
const dueDate = timezone.parse(submission.cached_due_date)
|
||||
|
||||
let gradingPeriod = null;
|
||||
let gradingPeriod = null
|
||||
if (gradingPeriods.length) {
|
||||
if (dueDate) {
|
||||
gradingPeriod = helper.gradingPeriodForDueAt(dueDate);
|
||||
gradingPeriod = helper.gradingPeriodForDueAt(dueDate)
|
||||
} else {
|
||||
gradingPeriod = sortedPeriods[sortedPeriods.length - 1];
|
||||
gradingPeriod = sortedPeriods[sortedPeriods.length - 1]
|
||||
}
|
||||
}
|
||||
|
||||
const assignmentDueDates = effectiveDueDates[submission.assignment_id] || {};
|
||||
const assignmentDueDates = effectiveDueDates[submission.assignment_id] || {}
|
||||
assignmentDueDates[submission.user_id] = {
|
||||
due_at: submission.cached_due_date,
|
||||
grading_period_id: gradingPeriod ? gradingPeriod.id : null,
|
||||
in_closed_grading_period: gradingPeriod ? gradingPeriod.isClosed : false
|
||||
};
|
||||
}
|
||||
|
||||
effectiveDueDates[submission.assignment_id] = assignmentDueDates; // eslint-disable-line no-param-reassign
|
||||
});
|
||||
effectiveDueDates[submission.assignment_id] = assignmentDueDates // eslint-disable-line no-param-reassign
|
||||
})
|
||||
}
|
||||
|
|
|
@ -135,7 +135,8 @@ class PostGradesDialogCorrectionsPage extends React.Component {
|
|||
onClick={this.ignoreErrorsThenProceed}
|
||||
>
|
||||
{errorCount > 0 ? I18n.t('Ignore These') : I18n.t('Continue')}
|
||||
<i className="icon-arrow-right" />
|
||||
|
||||
<i className="icon-arrow-right" />
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
|
|
@ -63,7 +63,8 @@ class PostGradesDialogNeedsGradingPage extends React.Component {
|
|||
className="btn btn-primary"
|
||||
onClick={this.props.leaveNeedsGradingPage}
|
||||
>
|
||||
{I18n.t('Continue')} <i className="icon-arrow-right" />
|
||||
{I18n.t('Continue')}
|
||||
<i className="icon-arrow-right" />
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
|
|
@ -21,218 +21,239 @@ import _ from 'underscore'
|
|||
import createStore from '../../shared/helpers/createStore'
|
||||
import assignmentUtils from '../../gradebook/SISGradePassback/assignmentUtils'
|
||||
|
||||
var PostGradesStore = (state) => {
|
||||
var store = $.extend(createStore(state), {
|
||||
var PostGradesStore = state => {
|
||||
var store = $.extend(createStore(state), {
|
||||
reset() {
|
||||
var assignments = this.getAssignments()
|
||||
_.each(assignments, a => (a.please_ignore = false))
|
||||
this.setState({
|
||||
assignments: assignments,
|
||||
pleaseShowNeedsGradingPage: false
|
||||
})
|
||||
},
|
||||
|
||||
reset () {
|
||||
var assignments = this.getAssignments()
|
||||
_.each(assignments, (a) => a.please_ignore = false)
|
||||
this.setState({
|
||||
assignments: assignments,
|
||||
pleaseShowNeedsGradingPage: false
|
||||
})
|
||||
},
|
||||
hasAssignments() {
|
||||
var assignments = this.getAssignments()
|
||||
if (assignments != undefined && assignments.length > 0) {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
},
|
||||
|
||||
hasAssignments () {
|
||||
var assignments = this.getAssignments()
|
||||
if (assignments != undefined && assignments.length > 0) {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
},
|
||||
getSISSectionId(section_id) {
|
||||
var sections = this.getState().sections
|
||||
return sections && sections[section_id] ? sections[section_id].sis_section_id : null
|
||||
},
|
||||
|
||||
getSISSectionId (section_id) {
|
||||
var sections = this.getState().sections
|
||||
return (sections && sections[section_id]) ?
|
||||
sections[section_id].sis_section_id :
|
||||
null;
|
||||
},
|
||||
allOverrideIds(a) {
|
||||
var overrides = []
|
||||
_.each(a.overrides, o => {
|
||||
overrides.push(o.course_section_id)
|
||||
})
|
||||
return overrides
|
||||
},
|
||||
|
||||
allOverrideIds(a) {
|
||||
var overrides = []
|
||||
_.each(a.overrides, (o) => {
|
||||
overrides.push(o.course_section_id)
|
||||
})
|
||||
return overrides
|
||||
},
|
||||
overrideForEveryone(a) {
|
||||
var overrides = this.allOverrideIds(a)
|
||||
var sections = _.keys(this.getState().sections)
|
||||
var section_ids_with_no_overrides = $(sections)
|
||||
.not(overrides)
|
||||
.get()
|
||||
|
||||
overrideForEveryone(a) {
|
||||
var overrides = this.allOverrideIds(a)
|
||||
var sections = _.keys(this.getState().sections)
|
||||
var section_ids_with_no_overrides = $(sections).not(overrides).get();
|
||||
var section_for_everyone = _.find(section_ids_with_no_overrides, o => {
|
||||
return state.selected.id == o
|
||||
})
|
||||
return section_for_everyone
|
||||
},
|
||||
|
||||
var section_for_everyone = _.find(section_ids_with_no_overrides, (o) => {
|
||||
return state.selected.id == o
|
||||
});
|
||||
return section_for_everyone
|
||||
},
|
||||
|
||||
setGradeBookAssignments (gradebookAssignments) {
|
||||
var assignments = []
|
||||
for (var id in gradebookAssignments) {
|
||||
var gba = gradebookAssignments[id]
|
||||
// Only accept assignments suitable to post, e.g. published, post_to_sis
|
||||
if (assignmentUtils.suitableToPost(gba)) {
|
||||
// Push a copy, and only of relevant attributes
|
||||
assignments.push(assignmentUtils.copyFromGradebook(gba))
|
||||
}
|
||||
}
|
||||
// A second loop is needed to ensure non-unique name errors are included
|
||||
// in hasError
|
||||
_.each(assignments, (a) => {
|
||||
a.original_error = assignmentUtils.hasError(assignments, a)
|
||||
})
|
||||
this.setState({ assignments: assignments })
|
||||
},
|
||||
|
||||
setSections (sections) {
|
||||
this.setState({ sections: sections })
|
||||
this.setSelectedSection( this.getState().sectionToShow )
|
||||
},
|
||||
|
||||
validCheck(a) {
|
||||
if(a.overrideForThisSection != undefined && a.currentlySelected.type == 'course' && a.currentlySelected.id.toString() == a.overrideForThisSection.course_section_id){
|
||||
return a.due_at != null ? true : false
|
||||
}
|
||||
else if(a.overrideForThisSection != undefined && a.currentlySelected.type == 'section' && a.currentlySelected.id.toString() == a.overrideForThisSection.course_section_id){
|
||||
return a.overrideForThisSection.due_at != null ? true : false
|
||||
}
|
||||
else{
|
||||
return true
|
||||
}
|
||||
},
|
||||
|
||||
getAssignments() {
|
||||
var assignments = this.getState().assignments
|
||||
var state = this.getState()
|
||||
if (state.selected.type == "section") {
|
||||
_.each(assignments, (a) => {
|
||||
a.recentlyUpdated = false
|
||||
a.currentlySelected = state.selected
|
||||
a.sectionCount = _.keys(state.sections).length
|
||||
a.overrideForThisSection = _.find(a.overrides, (override) => {
|
||||
return override.course_section_id == state.selected.id;
|
||||
});
|
||||
|
||||
//Handle assignment with overrides and the 'Everyone Else' scenario with a section that does not have any overrides
|
||||
//cleanup overrideForThisSection logic
|
||||
if(a.overrideForThisSection == undefined){ a.selectedSectionForEveryone = this.overrideForEveryone(a) }
|
||||
});
|
||||
} else {
|
||||
_.each(assignments, (a) => {
|
||||
a.recentlyUpdated = false
|
||||
a.currentlySelected = state.selected
|
||||
a.sectionCount = _.keys(state.sections).length
|
||||
|
||||
//Course is currentlySlected with sections that have overrides AND are invalid
|
||||
a.overrideForThisSection = _.find(a.overrides, (override) => {
|
||||
return override.due_at == null || typeof(override.due_at) == 'object';
|
||||
});
|
||||
|
||||
//Handle assignment with overrides and the 'Everyone Else' scenario with the course currentlySelected
|
||||
if(a.overrideForThisSection == undefined){ a.selectedSectionForEveryone = this.overrideForEveryone(a) }
|
||||
});
|
||||
}
|
||||
return assignments;
|
||||
},
|
||||
|
||||
getAssignment(assignment_id) {
|
||||
var assignments = this.getAssignments()
|
||||
return _.find(assignments, (a) => a.id == assignment_id)
|
||||
},
|
||||
|
||||
setSelectedSection (section) {
|
||||
var state = this.getState()
|
||||
var section_id = parseInt(section)
|
||||
var selected;
|
||||
if (section) {
|
||||
selected = {
|
||||
type: "section",
|
||||
id: section_id,
|
||||
sis_id: this.getSISSectionId(section_id)
|
||||
};
|
||||
} else {
|
||||
selected = {
|
||||
type: "course",
|
||||
id: state.course.id,
|
||||
sis_id: state.course.sis_id
|
||||
};
|
||||
}
|
||||
|
||||
this.setState({ selected: selected, sectionToShow: section })
|
||||
},
|
||||
|
||||
updateAssignment (assignment_id, newAttrs) {
|
||||
var assignments = this.getAssignments()
|
||||
var assignment = _.find(assignments, (a) => a.id == assignment_id)
|
||||
$.extend(assignment, newAttrs)
|
||||
this.setState({assignments: assignments})
|
||||
},
|
||||
|
||||
updateAssignmentDate(assignment_id, date){
|
||||
var assignments = this.getState().assignments
|
||||
var assignment = _.find(assignments, (a) => a.id == assignment_id)
|
||||
//the assignment has an override and the override being updated is for the section that is currentlySelected update it
|
||||
if(assignment.overrideForThisSection != undefined && assignment.currentlySelected.id.toString() == assignment.overrideForThisSection.course_section_id) {
|
||||
assignment.overrideForThisSection.due_at = date
|
||||
assignment.please_ignore = false
|
||||
assignment.hadOriginalErrors = true
|
||||
|
||||
this.setState({assignments: assignments})
|
||||
}
|
||||
|
||||
//the section override being set from the course level of the sction dropdown
|
||||
else if(assignment.overrideForThisSection != undefined && assignment.currentlySelected.id.toString() != assignment.overrideForThisSection.course_section_id){
|
||||
assignment.overrideForThisSection.due_at = date
|
||||
assignment.please_ignore = false
|
||||
assignment.hadOriginalErrors = true
|
||||
|
||||
this.setState({assignments: assignments})
|
||||
}
|
||||
|
||||
//update normal assignment and the 'Everyone Else' scenario if the course is currentlySelected
|
||||
else {
|
||||
this.updateAssignment(assignment_id, {due_at: date, please_ignore: false, hadOriginalErrors: true})
|
||||
}
|
||||
},
|
||||
|
||||
assignmentOverrideOrigianlErrorCheck(a) {
|
||||
a.hadOriginalErrors = a.hadOriginalErrors == true
|
||||
},
|
||||
|
||||
saveAssignments () {
|
||||
var assignments = assignmentUtils.withOriginalErrorsNotIgnored(this.getAssignments())
|
||||
var course_id = this.getState().course.id
|
||||
_.each(assignments, (a) => {
|
||||
this.assignmentOverrideOrigianlErrorCheck(a)
|
||||
assignmentUtils.saveAssignmentToCanvas(course_id, a)
|
||||
});
|
||||
},
|
||||
|
||||
postGrades() {
|
||||
var assignments = assignmentUtils.notIgnored(this.getAssignments())
|
||||
var selected = this.getState().selected
|
||||
assignmentUtils.postGradesThroughCanvas(selected, assignments)
|
||||
},
|
||||
|
||||
getPage () {
|
||||
var state = this.getState()
|
||||
if (state.pleaseShowNeedsGradingPage) {
|
||||
return "needsGrading"
|
||||
} else {
|
||||
var originals = assignmentUtils.withOriginalErrors(this.getAssignments())
|
||||
var withErrorsCount = _.keys(assignmentUtils.withErrors(this.getAssignments())).length
|
||||
if (withErrorsCount == 0 && (state.pleaseShowSummaryPage || originals.length == 0)) {
|
||||
return "summary"
|
||||
} else {
|
||||
return "corrections"
|
||||
}
|
||||
setGradeBookAssignments(gradebookAssignments) {
|
||||
var assignments = []
|
||||
for (var id in gradebookAssignments) {
|
||||
var gba = gradebookAssignments[id]
|
||||
// Only accept assignments suitable to post, e.g. published, post_to_sis
|
||||
if (assignmentUtils.suitableToPost(gba)) {
|
||||
// Push a copy, and only of relevant attributes
|
||||
assignments.push(assignmentUtils.copyFromGradebook(gba))
|
||||
}
|
||||
}
|
||||
})
|
||||
// A second loop is needed to ensure non-unique name errors are included
|
||||
// in hasError
|
||||
_.each(assignments, a => {
|
||||
a.original_error = assignmentUtils.hasError(assignments, a)
|
||||
})
|
||||
this.setState({assignments: assignments})
|
||||
},
|
||||
|
||||
return store
|
||||
};
|
||||
setSections(sections) {
|
||||
this.setState({sections: sections})
|
||||
this.setSelectedSection(this.getState().sectionToShow)
|
||||
},
|
||||
|
||||
validCheck(a) {
|
||||
if (
|
||||
a.overrideForThisSection != undefined &&
|
||||
a.currentlySelected.type == 'course' &&
|
||||
a.currentlySelected.id.toString() == a.overrideForThisSection.course_section_id
|
||||
) {
|
||||
return a.due_at != null ? true : false
|
||||
} else if (
|
||||
a.overrideForThisSection != undefined &&
|
||||
a.currentlySelected.type == 'section' &&
|
||||
a.currentlySelected.id.toString() == a.overrideForThisSection.course_section_id
|
||||
) {
|
||||
return a.overrideForThisSection.due_at != null ? true : false
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
},
|
||||
|
||||
getAssignments() {
|
||||
var assignments = this.getState().assignments
|
||||
var state = this.getState()
|
||||
if (state.selected.type == 'section') {
|
||||
_.each(assignments, a => {
|
||||
a.recentlyUpdated = false
|
||||
a.currentlySelected = state.selected
|
||||
a.sectionCount = _.keys(state.sections).length
|
||||
a.overrideForThisSection = _.find(a.overrides, override => {
|
||||
return override.course_section_id == state.selected.id
|
||||
})
|
||||
|
||||
//Handle assignment with overrides and the 'Everyone Else' scenario with a section that does not have any overrides
|
||||
//cleanup overrideForThisSection logic
|
||||
if (a.overrideForThisSection == undefined) {
|
||||
a.selectedSectionForEveryone = this.overrideForEveryone(a)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
_.each(assignments, a => {
|
||||
a.recentlyUpdated = false
|
||||
a.currentlySelected = state.selected
|
||||
a.sectionCount = _.keys(state.sections).length
|
||||
|
||||
//Course is currentlySlected with sections that have overrides AND are invalid
|
||||
a.overrideForThisSection = _.find(a.overrides, override => {
|
||||
return override.due_at == null || typeof override.due_at == 'object'
|
||||
})
|
||||
|
||||
//Handle assignment with overrides and the 'Everyone Else' scenario with the course currentlySelected
|
||||
if (a.overrideForThisSection == undefined) {
|
||||
a.selectedSectionForEveryone = this.overrideForEveryone(a)
|
||||
}
|
||||
})
|
||||
}
|
||||
return assignments
|
||||
},
|
||||
|
||||
getAssignment(assignment_id) {
|
||||
var assignments = this.getAssignments()
|
||||
return _.find(assignments, a => a.id == assignment_id)
|
||||
},
|
||||
|
||||
setSelectedSection(section) {
|
||||
var state = this.getState()
|
||||
var section_id = parseInt(section)
|
||||
var selected
|
||||
if (section) {
|
||||
selected = {
|
||||
type: 'section',
|
||||
id: section_id,
|
||||
sis_id: this.getSISSectionId(section_id)
|
||||
}
|
||||
} else {
|
||||
selected = {
|
||||
type: 'course',
|
||||
id: state.course.id,
|
||||
sis_id: state.course.sis_id
|
||||
}
|
||||
}
|
||||
|
||||
this.setState({selected: selected, sectionToShow: section})
|
||||
},
|
||||
|
||||
updateAssignment(assignment_id, newAttrs) {
|
||||
var assignments = this.getAssignments()
|
||||
var assignment = _.find(assignments, a => a.id == assignment_id)
|
||||
$.extend(assignment, newAttrs)
|
||||
this.setState({assignments: assignments})
|
||||
},
|
||||
|
||||
updateAssignmentDate(assignment_id, date) {
|
||||
var assignments = this.getState().assignments
|
||||
var assignment = _.find(assignments, a => a.id == assignment_id)
|
||||
//the assignment has an override and the override being updated is for the section that is currentlySelected update it
|
||||
if (
|
||||
assignment.overrideForThisSection != undefined &&
|
||||
assignment.currentlySelected.id.toString() ==
|
||||
assignment.overrideForThisSection.course_section_id
|
||||
) {
|
||||
assignment.overrideForThisSection.due_at = date
|
||||
assignment.please_ignore = false
|
||||
assignment.hadOriginalErrors = true
|
||||
|
||||
this.setState({assignments: assignments})
|
||||
}
|
||||
|
||||
//the section override being set from the course level of the sction dropdown
|
||||
else if (
|
||||
assignment.overrideForThisSection != undefined &&
|
||||
assignment.currentlySelected.id.toString() !=
|
||||
assignment.overrideForThisSection.course_section_id
|
||||
) {
|
||||
assignment.overrideForThisSection.due_at = date
|
||||
assignment.please_ignore = false
|
||||
assignment.hadOriginalErrors = true
|
||||
|
||||
this.setState({assignments: assignments})
|
||||
}
|
||||
|
||||
//update normal assignment and the 'Everyone Else' scenario if the course is currentlySelected
|
||||
else {
|
||||
this.updateAssignment(assignment_id, {
|
||||
due_at: date,
|
||||
please_ignore: false,
|
||||
hadOriginalErrors: true
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
assignmentOverrideOrigianlErrorCheck(a) {
|
||||
a.hadOriginalErrors = a.hadOriginalErrors == true
|
||||
},
|
||||
|
||||
saveAssignments() {
|
||||
var assignments = assignmentUtils.withOriginalErrorsNotIgnored(this.getAssignments())
|
||||
var course_id = this.getState().course.id
|
||||
_.each(assignments, a => {
|
||||
this.assignmentOverrideOrigianlErrorCheck(a)
|
||||
assignmentUtils.saveAssignmentToCanvas(course_id, a)
|
||||
})
|
||||
},
|
||||
|
||||
postGrades() {
|
||||
var assignments = assignmentUtils.notIgnored(this.getAssignments())
|
||||
var selected = this.getState().selected
|
||||
assignmentUtils.postGradesThroughCanvas(selected, assignments)
|
||||
},
|
||||
|
||||
getPage() {
|
||||
var state = this.getState()
|
||||
if (state.pleaseShowNeedsGradingPage) {
|
||||
return 'needsGrading'
|
||||
} else {
|
||||
var originals = assignmentUtils.withOriginalErrors(this.getAssignments())
|
||||
var withErrorsCount = _.keys(assignmentUtils.withErrors(this.getAssignments())).length
|
||||
if (withErrorsCount == 0 && (state.pleaseShowSummaryPage || originals.length == 0)) {
|
||||
return 'summary'
|
||||
} else {
|
||||
return 'corrections'
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return store
|
||||
}
|
||||
|
||||
export default PostGradesStore
|
||||
|
|
|
@ -20,230 +20,330 @@ import $ from 'jquery'
|
|||
import _ from 'underscore'
|
||||
import '../../shared/helpers/createStore'
|
||||
|
||||
let assignmentUtils = {
|
||||
copyFromGradebook (assignment) {
|
||||
var a = _.pick(assignment, [
|
||||
"id",
|
||||
"name",
|
||||
"due_at",
|
||||
"needs_grading_count",
|
||||
"overrides"
|
||||
])
|
||||
a.please_ignore = false
|
||||
a.original_error = false
|
||||
return a
|
||||
},
|
||||
let assignmentUtils = {
|
||||
copyFromGradebook(assignment) {
|
||||
var a = _.pick(assignment, ['id', 'name', 'due_at', 'needs_grading_count', 'overrides'])
|
||||
a.please_ignore = false
|
||||
a.original_error = false
|
||||
return a
|
||||
},
|
||||
|
||||
namesMatch (a, b) {
|
||||
return a.name === b.name && a !== b
|
||||
},
|
||||
namesMatch(a, b) {
|
||||
return a.name === b.name && a !== b
|
||||
},
|
||||
|
||||
nameTooLong (a) {
|
||||
if (_.unescape(a.name).length > 30){
|
||||
return true
|
||||
}
|
||||
else{
|
||||
return false
|
||||
}
|
||||
},
|
||||
|
||||
nameEmpty (a) {
|
||||
if (a.name.length == 0){
|
||||
return true
|
||||
}
|
||||
else{
|
||||
return false
|
||||
}
|
||||
},
|
||||
|
||||
notUniqueName (assignments, a) {
|
||||
return assignments.some(_.partial(assignmentUtils.namesMatch, a))
|
||||
},
|
||||
|
||||
noDueDateForEveryoneElseOverride(a) {
|
||||
var has_overrides = a.overrides != undefined ? a.overrides.length > 0 : false
|
||||
if(has_overrides && a.overrides.length != a.sectionCount && !a.due_at){
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
},
|
||||
|
||||
withOriginalErrors (assignments) {
|
||||
// This logic handles an assignment with multiple overrides
|
||||
// because #setGradeBookAssignments runs on load
|
||||
// it does not have a reference to what the currently viewed section is.
|
||||
// Due to this an assignment with 2 overrides (one valid, one invalid)
|
||||
// it will set original_errors to true. This logic checks the override
|
||||
// being viewed for that section. If the override is valid make
|
||||
// original error false so that the override is not shown. Vice versa
|
||||
// for the invalid override on the assignment.
|
||||
_.each(assignments, (a) => {
|
||||
if(a.overrideForThisSection != undefined && a.recentlyUpdated != undefined && a.recentlyUpdated == true && a.overrideForThisSection.due_at != null){a.original_error = false}
|
||||
else if(a.overrideForThisSection != undefined && a.recentlyUpdated != undefined && a.recentlyUpdated == false && a.overrideForThisSection.due_at == null){a.original_error = true}
|
||||
//for handling original error detection of a valid override for one section and an invalid override for another section
|
||||
else if(a.overrideForThisSection != undefined && a.overrideForThisSection.due_at != null && !assignmentUtils.noDueDateForEveryoneElseOverride(a) && a.recentlyUpdated == false && a.hadOriginalErrors == false){a.original_error = false}
|
||||
//for handling original error detection of a valid override for one section and the EveryoneElse "override" scenario
|
||||
else if(a.overrideForThisSection != undefined && a.overrideForThisSection.due_at != null && assignmentUtils.noDueDateForEveryoneElseOverride(a) && a.currentlySelected.id.toString() == a.overrideForThisSection.course_section_id && a.recentlyUpdated == false && a.hadOriginalErrors == false){a.original_error = false}
|
||||
//for handling original error detection of an override for one section and the EveryoneElse "override" scenario but the second section is currentlySelected and IS NOT valid
|
||||
else if(a.overrideForThisSection == undefined && assignmentUtils.noDueDateForEveryoneElseOverride(a) && a.due_at == null && a.currentlySelected.id.toString() == a.selectedSectionForEveryone){a.original_error = true}
|
||||
//for handling original error detection of an override for one section and the EveryoneElse "override" scenario but the second section is currentlySelected and IS valid
|
||||
else if(a.overrideForThisSection == undefined && a.due_at != null && a.currentlySelected.id.toString() == a.selectedSectionForEveryone && a.hadOriginalErrors == false){a.original_error = false}
|
||||
//for handling original error detection of an "override" in the 'EveryoneElse "override" scenario but the course is currentlySelected and IS NOT valid
|
||||
else if(a.overrideForThisSection == undefined && assignmentUtils.noDueDateForEveryoneElseOverride(a) && a.due_at == null && a.currentlySelected.type == 'course' && a.currentlySelected.id.toString() != a.selectedSectionForEveryone){a.original_error = true}
|
||||
//for handling original error detection of an "override" in the 'EveryoneElse "override" scenario but the course is currentlySelected and IS valid
|
||||
else if(a.overrideForThisSection == undefined && a.due_at != null && a.currentlySelected.type == 'course' && a.currentlySelected.id.toString() != a.selectedSectionForEveryone && a.hadOriginalErrors == false){a.original_error = false}
|
||||
});
|
||||
return _.filter(assignments, (a) => a.original_error && !a.please_ignore)
|
||||
},
|
||||
|
||||
withOriginalErrorsNotIgnored (assignments) {
|
||||
return _.filter(assignments, function(a){ return (a.original_error || a.hadOriginalErrors) && !a.please_ignore})
|
||||
},
|
||||
|
||||
withErrors (assignments) {
|
||||
return _.filter(assignments, (a) => assignmentUtils.hasError(assignments, a))
|
||||
},
|
||||
|
||||
notIgnored (assignments) {
|
||||
return _.filter(assignments, (a) => !a.please_ignore)
|
||||
},
|
||||
|
||||
needsGrading (assignments) {
|
||||
return _.filter(assignments, (a) => a.needs_grading_count > 0)
|
||||
},
|
||||
|
||||
hasError (assignments, a) {
|
||||
////Decided to ignore
|
||||
if(a.please_ignore) return false
|
||||
|
||||
////Not unique
|
||||
if(assignmentUtils.notUniqueName(assignments, a)) return true
|
||||
|
||||
////Name too long
|
||||
if(assignmentUtils.nameTooLong(a)) return true
|
||||
|
||||
////Name empty
|
||||
if(assignmentUtils.nameEmpty(a)) return true
|
||||
|
||||
////Non-override missing due_at
|
||||
var has_overrides = a.overrides != undefined ? a.overrides.length > 0 : false
|
||||
if(!has_overrides && !a.due_at) return true
|
||||
|
||||
////Override missing due_at
|
||||
var has_this_override = a.overrideForThisSection != undefined
|
||||
if(has_this_override && a.overrideForThisSection.due_at == null && a.currentlySelected.id.toString() == a.overrideForThisSection.course_section_id) return true
|
||||
|
||||
////Override missing due_at while currentlySelecteed is at the course level
|
||||
if(has_this_override && a.overrideForThisSection.due_at == null && a.currentlySelected.id.toString() != a.overrideForThisSection.course_section_id) return true
|
||||
|
||||
////Has one override and another override for 'Everyone Else'
|
||||
////
|
||||
////The override for 'Everyone Else' isn't really an override and references
|
||||
////the assignments actual due_at. So we must check for this behavior
|
||||
if(assignmentUtils.noDueDateForEveryoneElseOverride(a) && a.currentlySelected != undefined && a.overrideForThisSection != undefined && a.currentlySelected.id.toString() != a.overrideForThisSection.course_section_id) return true
|
||||
|
||||
////Has only one override but the section that is currently selected does not have an override thus causing the assignment to have due_at that is null making it invalid
|
||||
if(assignmentUtils.noDueDateForEveryoneElseOverride(a) && a.overrideForThisSection == undefined && a.currentlySelected != undefined && a.currentlySelected.id.toString() == a.selectedSectionForEveryone) return true
|
||||
|
||||
////'Everyone Else' scenario and the course is currentlySelected but due_at is null making it invalid
|
||||
if(assignmentUtils.noDueDateForEveryoneElseOverride(a) && a.overrideForThisSection == undefined && a.currentlySelected != undefined && a.currentlySelected.type == 'course' && a.currentlySelected.id.toString() != a.selectedSectionForEveryone) return true
|
||||
|
||||
////Passes all tests, looks good.
|
||||
nameTooLong(a) {
|
||||
if (_.unescape(a.name).length > 30) {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
suitableToPost(assignment) {
|
||||
return assignment.published && assignment.post_to_sis
|
||||
},
|
||||
nameEmpty(a) {
|
||||
if (a.name.length == 0) {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
},
|
||||
|
||||
saveAssignmentToCanvas (course_id, assignment) {
|
||||
// if the date on an override is being updated confirm by checking if the due_at is an object
|
||||
if(assignment.overrideForThisSection != undefined && typeof(assignment.overrideForThisSection.due_at) == "object") {
|
||||
//allows the validation process to determine when it has been updated and can display the correct page
|
||||
assignment.hadOriginalErrors = false
|
||||
var url = '/api/v1/courses/' + course_id + '/assignments/' + assignment.id + '/overrides/' + assignment.overrideForThisSection.id
|
||||
//sets up form data to allow a single override to be updated
|
||||
var fd = new FormData();
|
||||
fd.append( 'assignment_override[due_at]', assignment.overrideForThisSection.due_at.toISOString() )
|
||||
notUniqueName(assignments, a) {
|
||||
return assignments.some(_.partial(assignmentUtils.namesMatch, a))
|
||||
},
|
||||
|
||||
$.ajax(url, {
|
||||
type: 'PUT',
|
||||
data: fd,
|
||||
processData: false,
|
||||
contentType: false,
|
||||
error: (err) => {
|
||||
var msg = 'An error occurred saving assignment override, (' + assignment.overrideForThisSection.id + '). '
|
||||
msg += "HTTP Error " + data.status + " : " + data.statusText
|
||||
$.flashError(msg)
|
||||
}
|
||||
})
|
||||
// if there is a naming conflict on the assignment that has an override with a date
|
||||
// that was just set AND the naming conflict is fixed we must also update the assignment
|
||||
// to mock natural behavior to the user so that the naming conflict does not appear again
|
||||
url = '/api/v1/courses/' + course_id + '/assignments/' + assignment.id
|
||||
data = { assignment: {
|
||||
name: assignment.name,
|
||||
due_at: assignment.due_at
|
||||
}}
|
||||
$.ajax(url, {
|
||||
type: 'PUT',
|
||||
data: JSON.stringify(data),
|
||||
contentType: 'application/json; charset=utf-8',
|
||||
error: (err) => {
|
||||
var msg = 'An error occurred saving assignment (' + assignment.id + '). '
|
||||
msg += "HTTP Error " + data.status + " : " + data.statusText
|
||||
$.flashError(msg)
|
||||
}
|
||||
})
|
||||
noDueDateForEveryoneElseOverride(a) {
|
||||
var has_overrides = a.overrides != undefined ? a.overrides.length > 0 : false
|
||||
if (has_overrides && a.overrides.length != a.sectionCount && !a.due_at) {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
},
|
||||
|
||||
withOriginalErrors(assignments) {
|
||||
// This logic handles an assignment with multiple overrides
|
||||
// because #setGradeBookAssignments runs on load
|
||||
// it does not have a reference to what the currently viewed section is.
|
||||
// Due to this an assignment with 2 overrides (one valid, one invalid)
|
||||
// it will set original_errors to true. This logic checks the override
|
||||
// being viewed for that section. If the override is valid make
|
||||
// original error false so that the override is not shown. Vice versa
|
||||
// for the invalid override on the assignment.
|
||||
_.each(assignments, a => {
|
||||
if (
|
||||
a.overrideForThisSection != undefined &&
|
||||
a.recentlyUpdated != undefined &&
|
||||
a.recentlyUpdated == true &&
|
||||
a.overrideForThisSection.due_at != null
|
||||
) {
|
||||
a.original_error = false
|
||||
} else if (
|
||||
a.overrideForThisSection != undefined &&
|
||||
a.recentlyUpdated != undefined &&
|
||||
a.recentlyUpdated == false &&
|
||||
a.overrideForThisSection.due_at == null
|
||||
) {
|
||||
a.original_error = true
|
||||
}
|
||||
else {
|
||||
//allows the validation process to determine when it has been updated and can display the correct page
|
||||
assignment.hadOriginalErrors = false
|
||||
var url = '/api/v1/courses/' + course_id + '/assignments/' + assignment.id
|
||||
var data = { assignment: {
|
||||
name: assignment.name,
|
||||
due_at: assignment.due_at
|
||||
}}
|
||||
$.ajax(url, {
|
||||
type: 'PUT',
|
||||
data: JSON.stringify(data),
|
||||
contentType: 'application/json; charset=utf-8',
|
||||
error: (err) => {
|
||||
var msg = 'An error occurred saving assignment (' + assignment.id + '). '
|
||||
msg += "HTTP Error " + data.status + " : " + data.statusText
|
||||
$.flashError(msg)
|
||||
}
|
||||
})
|
||||
//for handling original error detection of a valid override for one section and an invalid override for another section
|
||||
else if (
|
||||
a.overrideForThisSection != undefined &&
|
||||
a.overrideForThisSection.due_at != null &&
|
||||
!assignmentUtils.noDueDateForEveryoneElseOverride(a) &&
|
||||
a.recentlyUpdated == false &&
|
||||
a.hadOriginalErrors == false
|
||||
) {
|
||||
a.original_error = false
|
||||
}
|
||||
//for handling original error detection of a valid override for one section and the EveryoneElse "override" scenario
|
||||
else if (
|
||||
a.overrideForThisSection != undefined &&
|
||||
a.overrideForThisSection.due_at != null &&
|
||||
assignmentUtils.noDueDateForEveryoneElseOverride(a) &&
|
||||
a.currentlySelected.id.toString() == a.overrideForThisSection.course_section_id &&
|
||||
a.recentlyUpdated == false &&
|
||||
a.hadOriginalErrors == false
|
||||
) {
|
||||
a.original_error = false
|
||||
}
|
||||
//for handling original error detection of an override for one section and the EveryoneElse "override" scenario but the second section is currentlySelected and IS NOT valid
|
||||
else if (
|
||||
a.overrideForThisSection == undefined &&
|
||||
assignmentUtils.noDueDateForEveryoneElseOverride(a) &&
|
||||
a.due_at == null &&
|
||||
a.currentlySelected.id.toString() == a.selectedSectionForEveryone
|
||||
) {
|
||||
a.original_error = true
|
||||
}
|
||||
//for handling original error detection of an override for one section and the EveryoneElse "override" scenario but the second section is currentlySelected and IS valid
|
||||
else if (
|
||||
a.overrideForThisSection == undefined &&
|
||||
a.due_at != null &&
|
||||
a.currentlySelected.id.toString() == a.selectedSectionForEveryone &&
|
||||
a.hadOriginalErrors == false
|
||||
) {
|
||||
a.original_error = false
|
||||
}
|
||||
//for handling original error detection of an "override" in the 'EveryoneElse "override" scenario but the course is currentlySelected and IS NOT valid
|
||||
else if (
|
||||
a.overrideForThisSection == undefined &&
|
||||
assignmentUtils.noDueDateForEveryoneElseOverride(a) &&
|
||||
a.due_at == null &&
|
||||
a.currentlySelected.type == 'course' &&
|
||||
a.currentlySelected.id.toString() != a.selectedSectionForEveryone
|
||||
) {
|
||||
a.original_error = true
|
||||
}
|
||||
//for handling original error detection of an "override" in the 'EveryoneElse "override" scenario but the course is currentlySelected and IS valid
|
||||
else if (
|
||||
a.overrideForThisSection == undefined &&
|
||||
a.due_at != null &&
|
||||
a.currentlySelected.type == 'course' &&
|
||||
a.currentlySelected.id.toString() != a.selectedSectionForEveryone &&
|
||||
a.hadOriginalErrors == false
|
||||
) {
|
||||
a.original_error = false
|
||||
}
|
||||
})
|
||||
return _.filter(assignments, a => a.original_error && !a.please_ignore)
|
||||
},
|
||||
|
||||
},
|
||||
withOriginalErrorsNotIgnored(assignments) {
|
||||
return _.filter(assignments, function(a) {
|
||||
return (a.original_error || a.hadOriginalErrors) && !a.please_ignore
|
||||
})
|
||||
},
|
||||
|
||||
withErrors(assignments) {
|
||||
return _.filter(assignments, a => assignmentUtils.hasError(assignments, a))
|
||||
},
|
||||
|
||||
notIgnored(assignments) {
|
||||
return _.filter(assignments, a => !a.please_ignore)
|
||||
},
|
||||
|
||||
needsGrading(assignments) {
|
||||
return _.filter(assignments, a => a.needs_grading_count > 0)
|
||||
},
|
||||
|
||||
hasError(assignments, a) {
|
||||
////Decided to ignore
|
||||
if (a.please_ignore) return false
|
||||
|
||||
////Not unique
|
||||
if (assignmentUtils.notUniqueName(assignments, a)) return true
|
||||
|
||||
////Name too long
|
||||
if (assignmentUtils.nameTooLong(a)) return true
|
||||
|
||||
////Name empty
|
||||
if (assignmentUtils.nameEmpty(a)) return true
|
||||
|
||||
////Non-override missing due_at
|
||||
var has_overrides = a.overrides != undefined ? a.overrides.length > 0 : false
|
||||
if (!has_overrides && !a.due_at) return true
|
||||
|
||||
////Override missing due_at
|
||||
var has_this_override = a.overrideForThisSection != undefined
|
||||
if (
|
||||
has_this_override &&
|
||||
a.overrideForThisSection.due_at == null &&
|
||||
a.currentlySelected.id.toString() == a.overrideForThisSection.course_section_id
|
||||
)
|
||||
return true
|
||||
|
||||
////Override missing due_at while currentlySelecteed is at the course level
|
||||
if (
|
||||
has_this_override &&
|
||||
a.overrideForThisSection.due_at == null &&
|
||||
a.currentlySelected.id.toString() != a.overrideForThisSection.course_section_id
|
||||
)
|
||||
return true
|
||||
|
||||
////Has one override and another override for 'Everyone Else'
|
||||
////
|
||||
////The override for 'Everyone Else' isn't really an override and references
|
||||
////the assignments actual due_at. So we must check for this behavior
|
||||
if (
|
||||
assignmentUtils.noDueDateForEveryoneElseOverride(a) &&
|
||||
a.currentlySelected != undefined &&
|
||||
a.overrideForThisSection != undefined &&
|
||||
a.currentlySelected.id.toString() != a.overrideForThisSection.course_section_id
|
||||
)
|
||||
return true
|
||||
|
||||
////Has only one override but the section that is currently selected does not have an override thus causing the assignment to have due_at that is null making it invalid
|
||||
if (
|
||||
assignmentUtils.noDueDateForEveryoneElseOverride(a) &&
|
||||
a.overrideForThisSection == undefined &&
|
||||
a.currentlySelected != undefined &&
|
||||
a.currentlySelected.id.toString() == a.selectedSectionForEveryone
|
||||
)
|
||||
return true
|
||||
|
||||
////'Everyone Else' scenario and the course is currentlySelected but due_at is null making it invalid
|
||||
if (
|
||||
assignmentUtils.noDueDateForEveryoneElseOverride(a) &&
|
||||
a.overrideForThisSection == undefined &&
|
||||
a.currentlySelected != undefined &&
|
||||
a.currentlySelected.type == 'course' &&
|
||||
a.currentlySelected.id.toString() != a.selectedSectionForEveryone
|
||||
)
|
||||
return true
|
||||
|
||||
////Passes all tests, looks good.
|
||||
return false
|
||||
},
|
||||
|
||||
suitableToPost(assignment) {
|
||||
return assignment.published && assignment.post_to_sis
|
||||
},
|
||||
|
||||
saveAssignmentToCanvas(course_id, assignment) {
|
||||
// if the date on an override is being updated confirm by checking if the due_at is an object
|
||||
if (
|
||||
assignment.overrideForThisSection != undefined &&
|
||||
typeof assignment.overrideForThisSection.due_at == 'object'
|
||||
) {
|
||||
//allows the validation process to determine when it has been updated and can display the correct page
|
||||
assignment.hadOriginalErrors = false
|
||||
var url =
|
||||
'/api/v1/courses/' +
|
||||
course_id +
|
||||
'/assignments/' +
|
||||
assignment.id +
|
||||
'/overrides/' +
|
||||
assignment.overrideForThisSection.id
|
||||
//sets up form data to allow a single override to be updated
|
||||
var fd = new FormData()
|
||||
fd.append(
|
||||
'assignment_override[due_at]',
|
||||
assignment.overrideForThisSection.due_at.toISOString()
|
||||
)
|
||||
|
||||
// Sends a post-grades request to Canvas that is then forwarded to SIS App.
|
||||
// Expects a list of assignments that will later be queried for grades via
|
||||
// SIS App's workers
|
||||
postGradesThroughCanvas (selected, assignments) {
|
||||
var url = "/api/v1/" + selected.type + "s/" + selected.id + "/post_grades/"
|
||||
var data = { assignments: _.map(assignments, (assignment) => assignment.id) }
|
||||
$.ajax(url, {
|
||||
type: 'POST',
|
||||
type: 'PUT',
|
||||
data: fd,
|
||||
processData: false,
|
||||
contentType: false,
|
||||
error: err => {
|
||||
var msg =
|
||||
'An error occurred saving assignment override, (' +
|
||||
assignment.overrideForThisSection.id +
|
||||
'). '
|
||||
msg += 'HTTP Error ' + data.status + ' : ' + data.statusText
|
||||
$.flashError(msg)
|
||||
}
|
||||
})
|
||||
// if there is a naming conflict on the assignment that has an override with a date
|
||||
// that was just set AND the naming conflict is fixed we must also update the assignment
|
||||
// to mock natural behavior to the user so that the naming conflict does not appear again
|
||||
url = '/api/v1/courses/' + course_id + '/assignments/' + assignment.id
|
||||
data = {
|
||||
assignment: {
|
||||
name: assignment.name,
|
||||
due_at: assignment.due_at
|
||||
}
|
||||
}
|
||||
$.ajax(url, {
|
||||
type: 'PUT',
|
||||
data: JSON.stringify(data),
|
||||
contentType: 'application/json; charset=utf-8',
|
||||
success: (msg) =>{
|
||||
if (msg.error){
|
||||
$.flashError(msg.error)
|
||||
}else{
|
||||
$.flashMessage(msg.message)
|
||||
}
|
||||
},
|
||||
error: (err) => {
|
||||
var msg = 'An error occurred posting grades for (' + selected.type + ' : ' + selected.id +'). '
|
||||
msg += "HTTP Error " + data.status + " : " + data.statusText
|
||||
error: err => {
|
||||
var msg = 'An error occurred saving assignment (' + assignment.id + '). '
|
||||
msg += 'HTTP Error ' + data.status + ' : ' + data.statusText
|
||||
$.flashError(msg)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
//allows the validation process to determine when it has been updated and can display the correct page
|
||||
assignment.hadOriginalErrors = false
|
||||
var url = '/api/v1/courses/' + course_id + '/assignments/' + assignment.id
|
||||
var data = {
|
||||
assignment: {
|
||||
name: assignment.name,
|
||||
due_at: assignment.due_at
|
||||
}
|
||||
}
|
||||
$.ajax(url, {
|
||||
type: 'PUT',
|
||||
data: JSON.stringify(data),
|
||||
contentType: 'application/json; charset=utf-8',
|
||||
error: err => {
|
||||
var msg = 'An error occurred saving assignment (' + assignment.id + '). '
|
||||
msg += 'HTTP Error ' + data.status + ' : ' + data.statusText
|
||||
$.flashError(msg)
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
};
|
||||
// Sends a post-grades request to Canvas that is then forwarded to SIS App.
|
||||
// Expects a list of assignments that will later be queried for grades via
|
||||
// SIS App's workers
|
||||
postGradesThroughCanvas(selected, assignments) {
|
||||
var url = '/api/v1/' + selected.type + 's/' + selected.id + '/post_grades/'
|
||||
var data = {assignments: _.map(assignments, assignment => assignment.id)}
|
||||
$.ajax(url, {
|
||||
type: 'POST',
|
||||
data: JSON.stringify(data),
|
||||
contentType: 'application/json; charset=utf-8',
|
||||
success: msg => {
|
||||
if (msg.error) {
|
||||
$.flashError(msg.error)
|
||||
} else {
|
||||
$.flashMessage(msg.message)
|
||||
}
|
||||
},
|
||||
error: err => {
|
||||
var msg =
|
||||
'An error occurred posting grades for (' + selected.type + ' : ' + selected.id + '). '
|
||||
msg += 'HTTP Error ' + data.status + ' : ' + data.statusText
|
||||
$.flashError(msg)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export default assignmentUtils
|
||||
|
|
|
@ -19,100 +19,117 @@
|
|||
import _ from 'underscore'
|
||||
import GradingPeriodsHelper from '../grading/helpers/GradingPeriodsHelper'
|
||||
|
||||
const TOOLTIP_KEYS = {
|
||||
NOT_IN_ANY_GP: "not_in_any_grading_period",
|
||||
IN_ANOTHER_GP: "in_another_grading_period",
|
||||
IN_CLOSED_GP: "in_closed_grading_period",
|
||||
NONE: null
|
||||
};
|
||||
const TOOLTIP_KEYS = {
|
||||
NOT_IN_ANY_GP: 'not_in_any_grading_period',
|
||||
IN_ANOTHER_GP: 'in_another_grading_period',
|
||||
IN_CLOSED_GP: 'in_closed_grading_period',
|
||||
NONE: null
|
||||
}
|
||||
|
||||
function visibleToStudent(assignment, student) {
|
||||
if (!assignment.only_visible_to_overrides) return true;
|
||||
return _.contains(assignment.assignment_visibility, student.id);
|
||||
function visibleToStudent(assignment, student) {
|
||||
if (!assignment.only_visible_to_overrides) return true
|
||||
return _.contains(assignment.assignment_visibility, student.id)
|
||||
}
|
||||
|
||||
function cellMapForSubmission(
|
||||
assignment,
|
||||
student,
|
||||
hasGradingPeriods,
|
||||
selectedGradingPeriodID,
|
||||
isAdmin
|
||||
) {
|
||||
if (assignment.moderated_grading && !assignment.grades_published) {
|
||||
return {locked: true, hideGrade: false}
|
||||
} else if (assignment.anonymize_students) {
|
||||
return {locked: true, hideGrade: true}
|
||||
} else if (!visibleToStudent(assignment, student)) {
|
||||
return {locked: true, hideGrade: true, tooltip: TOOLTIP_KEYS.NONE}
|
||||
} else if (hasGradingPeriods) {
|
||||
return cellMappingsForMultipleGradingPeriods(
|
||||
assignment,
|
||||
student,
|
||||
selectedGradingPeriodID,
|
||||
isAdmin
|
||||
)
|
||||
} else {
|
||||
return {locked: false, hideGrade: false, tooltip: TOOLTIP_KEYS.NONE}
|
||||
}
|
||||
}
|
||||
|
||||
function cellMapForSubmission(
|
||||
function submissionGradingPeriodInformation(assignment, student) {
|
||||
const submissionInfo = assignment.effectiveDueDates[student.id] || {}
|
||||
return {
|
||||
gradingPeriodID: submissionInfo.grading_period_id,
|
||||
inClosedGradingPeriod: submissionInfo.in_closed_grading_period
|
||||
}
|
||||
}
|
||||
|
||||
function cellMappingsForMultipleGradingPeriods(
|
||||
assignment,
|
||||
student,
|
||||
selectedGradingPeriodID,
|
||||
isAdmin
|
||||
) {
|
||||
const specificPeriodSelected = !GradingPeriodsHelper.isAllGradingPeriods(selectedGradingPeriodID)
|
||||
const {gradingPeriodID, inClosedGradingPeriod} = submissionGradingPeriodInformation(
|
||||
assignment,
|
||||
student
|
||||
)
|
||||
|
||||
if (specificPeriodSelected && !gradingPeriodID) {
|
||||
return {locked: true, hideGrade: true, tooltip: TOOLTIP_KEYS.NOT_IN_ANY_GP}
|
||||
} else if (specificPeriodSelected && selectedGradingPeriodID != gradingPeriodID) {
|
||||
return {locked: true, hideGrade: true, tooltip: TOOLTIP_KEYS.IN_ANOTHER_GP}
|
||||
} else if (!isAdmin && inClosedGradingPeriod) {
|
||||
return {locked: true, hideGrade: false, tooltip: TOOLTIP_KEYS.IN_CLOSED_GP}
|
||||
} else {
|
||||
return {locked: false, hideGrade: false, tooltip: TOOLTIP_KEYS.NONE}
|
||||
}
|
||||
}
|
||||
|
||||
class SubmissionState {
|
||||
constructor({hasGradingPeriods, selectedGradingPeriodID, isAdmin}) {
|
||||
this.hasGradingPeriods = hasGradingPeriods
|
||||
this.selectedGradingPeriodID = selectedGradingPeriodID
|
||||
this.isAdmin = isAdmin
|
||||
this.submissionCellMap = {}
|
||||
this.submissionMap = {}
|
||||
}
|
||||
|
||||
setup(students, assignments) {
|
||||
students.forEach(student => {
|
||||
this.submissionCellMap[student.id] = {}
|
||||
this.submissionMap[student.id] = {}
|
||||
_.each(assignments, assignment => {
|
||||
this.setSubmissionCellState(student, assignment, student[`assignment_${assignment.id}`])
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
setSubmissionCellState(
|
||||
student,
|
||||
hasGradingPeriods,
|
||||
selectedGradingPeriodID,
|
||||
isAdmin
|
||||
assignment,
|
||||
submission = {assignment_id: assignment.id, user_id: student.id}
|
||||
) {
|
||||
if (assignment.moderated_grading && !assignment.grades_published) {
|
||||
return { locked: true, hideGrade: false };
|
||||
} else if (assignment.anonymize_students) {
|
||||
return { locked: true, hideGrade: true };
|
||||
} else if (!visibleToStudent(assignment, student)) {
|
||||
return { locked: true, hideGrade: true, tooltip: TOOLTIP_KEYS.NONE };
|
||||
} else if (hasGradingPeriods) {
|
||||
return cellMappingsForMultipleGradingPeriods(assignment, student, selectedGradingPeriodID, isAdmin);
|
||||
} else {
|
||||
return { locked: false, hideGrade: false, tooltip: TOOLTIP_KEYS.NONE };
|
||||
}
|
||||
this.submissionMap[student.id][assignment.id] = submission
|
||||
const params = [
|
||||
assignment,
|
||||
student,
|
||||
this.hasGradingPeriods,
|
||||
this.selectedGradingPeriodID,
|
||||
this.isAdmin
|
||||
]
|
||||
|
||||
this.submissionCellMap[student.id][assignment.id] = cellMapForSubmission(...params)
|
||||
}
|
||||
|
||||
function submissionGradingPeriodInformation(assignment, student) {
|
||||
const submissionInfo = assignment.effectiveDueDates[student.id] || {};
|
||||
return {
|
||||
gradingPeriodID: submissionInfo.grading_period_id,
|
||||
inClosedGradingPeriod: submissionInfo.in_closed_grading_period
|
||||
};
|
||||
getSubmission(user_id, assignment_id) {
|
||||
return (this.submissionMap[user_id] || {})[assignment_id]
|
||||
}
|
||||
|
||||
function cellMappingsForMultipleGradingPeriods(assignment, student, selectedGradingPeriodID, isAdmin) {
|
||||
const specificPeriodSelected = !GradingPeriodsHelper.isAllGradingPeriods(selectedGradingPeriodID);
|
||||
const { gradingPeriodID, inClosedGradingPeriod } = submissionGradingPeriodInformation(assignment, student);
|
||||
|
||||
if (specificPeriodSelected && !gradingPeriodID) {
|
||||
return { locked: true, hideGrade: true, tooltip: TOOLTIP_KEYS.NOT_IN_ANY_GP };
|
||||
} else if (specificPeriodSelected && selectedGradingPeriodID != gradingPeriodID) {
|
||||
return { locked: true, hideGrade: true, tooltip: TOOLTIP_KEYS.IN_ANOTHER_GP };
|
||||
} else if (!isAdmin && inClosedGradingPeriod) {
|
||||
return { locked: true, hideGrade: false, tooltip: TOOLTIP_KEYS.IN_CLOSED_GP };
|
||||
} else {
|
||||
return { locked: false, hideGrade: false, tooltip: TOOLTIP_KEYS.NONE };
|
||||
}
|
||||
getSubmissionState({user_id, assignment_id}) {
|
||||
return (this.submissionCellMap[user_id] || {})[assignment_id]
|
||||
}
|
||||
|
||||
class SubmissionState {
|
||||
constructor({ hasGradingPeriods, selectedGradingPeriodID, isAdmin }) {
|
||||
this.hasGradingPeriods = hasGradingPeriods;
|
||||
this.selectedGradingPeriodID = selectedGradingPeriodID;
|
||||
this.isAdmin = isAdmin;
|
||||
this.submissionCellMap = {};
|
||||
this.submissionMap = {};
|
||||
}
|
||||
|
||||
setup(students, assignments) {
|
||||
students.forEach((student) => {
|
||||
this.submissionCellMap[student.id] = {};
|
||||
this.submissionMap[student.id] = {};
|
||||
_.each(assignments, (assignment) => {
|
||||
this.setSubmissionCellState(student, assignment, student[`assignment_${assignment.id}`]);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
setSubmissionCellState(student, assignment, submission = { assignment_id: assignment.id, user_id: student.id }) {
|
||||
this.submissionMap[student.id][assignment.id] = submission;
|
||||
const params = [
|
||||
assignment,
|
||||
student,
|
||||
this.hasGradingPeriods,
|
||||
this.selectedGradingPeriodID,
|
||||
this.isAdmin
|
||||
];
|
||||
|
||||
this.submissionCellMap[student.id][assignment.id] = cellMapForSubmission(...params);
|
||||
}
|
||||
|
||||
getSubmission(user_id, assignment_id) {
|
||||
return (this.submissionMap[user_id] || {})[assignment_id];
|
||||
}
|
||||
|
||||
getSubmissionState({ user_id, assignment_id }) {
|
||||
return (this.submissionCellMap[user_id] || {})[assignment_id];
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default SubmissionState
|
||||
|
|
|
@ -16,8 +16,8 @@
|
|||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
const constants = {
|
||||
MAX_NOTE_LENGTH: 255
|
||||
};
|
||||
const constants = {
|
||||
MAX_NOTE_LENGTH: 255
|
||||
}
|
||||
|
||||
export default constants
|
||||
|
|
|
@ -50,7 +50,7 @@ export function sumBy(collection, attr) {
|
|||
}
|
||||
|
||||
export function scoreToPercentage(score, pointsPossible) {
|
||||
const floatingPointResult = score / pointsPossible * 100
|
||||
const floatingPointResult = (score / pointsPossible) * 100
|
||||
if (!Number.isFinite(floatingPointResult)) {
|
||||
return floatingPointResult
|
||||
}
|
||||
|
@ -59,5 +59,9 @@ export function scoreToPercentage(score, pointsPossible) {
|
|||
}
|
||||
|
||||
export function weightedPercent({score, possible, weight}) {
|
||||
return (score && weight) ? Big(score).div(possible).times(weight) : Big(0)
|
||||
return score && weight
|
||||
? Big(score)
|
||||
.div(possible)
|
||||
.times(weight)
|
||||
: Big(0)
|
||||
}
|
||||
|
|
|
@ -22,89 +22,93 @@ import numberHelper from '../../../shared/helpers/numberHelper'
|
|||
import {scoreToPercentage} from './GradeCalculationHelper'
|
||||
import {scoreToGrade} from '../../../gradebook/GradingSchemeHelper'
|
||||
|
||||
const POINTS = 'points';
|
||||
const PERCENT = 'percent';
|
||||
const PASS_FAIL = 'pass_fail';
|
||||
const POINTS = 'points'
|
||||
const PERCENT = 'percent'
|
||||
const PASS_FAIL = 'pass_fail'
|
||||
|
||||
const PASS_GRADES = ['complete', 'pass'];
|
||||
const FAIL_GRADES = ['incomplete', 'fail'];
|
||||
const PASS_GRADES = ['complete', 'pass']
|
||||
const FAIL_GRADES = ['incomplete', 'fail']
|
||||
|
||||
const UNGRADED = '–'
|
||||
|
||||
function isPassFail (grade, gradeType) {
|
||||
function isPassFail(grade, gradeType) {
|
||||
if (gradeType) {
|
||||
return gradeType === PASS_FAIL;
|
||||
return gradeType === PASS_FAIL
|
||||
}
|
||||
|
||||
return PASS_GRADES.includes(grade) || FAIL_GRADES.includes(grade);
|
||||
return PASS_GRADES.includes(grade) || FAIL_GRADES.includes(grade)
|
||||
}
|
||||
|
||||
function isPercent (grade, gradeType) {
|
||||
function isPercent(grade, gradeType) {
|
||||
if (gradeType) {
|
||||
return gradeType === PERCENT;
|
||||
return gradeType === PERCENT
|
||||
}
|
||||
|
||||
return /%/g.test(grade);
|
||||
return /%/g.test(grade)
|
||||
}
|
||||
|
||||
function isExcused (grade) {
|
||||
return grade === 'EX';
|
||||
function isExcused(grade) {
|
||||
return grade === 'EX'
|
||||
}
|
||||
|
||||
function normalizeCompleteIncompleteGrade (grade) {
|
||||
function normalizeCompleteIncompleteGrade(grade) {
|
||||
if (PASS_GRADES.includes(grade)) {
|
||||
return 'complete';
|
||||
return 'complete'
|
||||
}
|
||||
if (FAIL_GRADES.includes(grade)) {
|
||||
return 'incomplete';
|
||||
return 'incomplete'
|
||||
}
|
||||
return null;
|
||||
return null
|
||||
}
|
||||
|
||||
function shouldFormatGradingType (gradingType) {
|
||||
return gradingType === POINTS || gradingType === PERCENT || gradingType === PASS_FAIL;
|
||||
function shouldFormatGradingType(gradingType) {
|
||||
return gradingType === POINTS || gradingType === PERCENT || gradingType === PASS_FAIL
|
||||
}
|
||||
|
||||
function shouldFormatGrade (grade, gradingType) {
|
||||
function shouldFormatGrade(grade, gradingType) {
|
||||
if (gradingType) {
|
||||
return shouldFormatGradingType(gradingType);
|
||||
return shouldFormatGradingType(gradingType)
|
||||
}
|
||||
|
||||
return typeof grade === 'number' || isPassFail(grade);
|
||||
return typeof grade === 'number' || isPassFail(grade)
|
||||
}
|
||||
|
||||
function excused () {
|
||||
return I18n.t('Excused');
|
||||
function excused() {
|
||||
return I18n.t('Excused')
|
||||
}
|
||||
|
||||
function formatPointsGrade (score) {
|
||||
return I18n.n(score, { precision: 2, strip_insignificant_zeros: true });
|
||||
function formatPointsGrade(score) {
|
||||
return I18n.n(score, {precision: 2, strip_insignificant_zeros: true})
|
||||
}
|
||||
|
||||
function formatPercentageGrade (score, options) {
|
||||
function formatPercentageGrade(score, options) {
|
||||
const percent = options.pointsPossible ? scoreToPercentage(score, options.pointsPossible) : score
|
||||
return I18n.n(round(percent, 2), { percentage: true, precision: 2, strip_insignificant_zeros: true });
|
||||
return I18n.n(round(percent, 2), {
|
||||
percentage: true,
|
||||
precision: 2,
|
||||
strip_insignificant_zeros: true
|
||||
})
|
||||
}
|
||||
|
||||
function formatGradingSchemeGrade (score, grade, options) {
|
||||
function formatGradingSchemeGrade(score, grade, options) {
|
||||
if (options.pointsPossible) {
|
||||
const percent = scoreToPercentage(score, options.pointsPossible)
|
||||
return scoreToGrade(percent, options.gradingScheme);
|
||||
return scoreToGrade(percent, options.gradingScheme)
|
||||
} else if (grade != null) {
|
||||
return grade;
|
||||
return grade
|
||||
} else {
|
||||
return scoreToGrade(score, options.gradingScheme);
|
||||
return scoreToGrade(score, options.gradingScheme)
|
||||
}
|
||||
}
|
||||
|
||||
function formatCompleteIncompleteGrade (score, grade, options) {
|
||||
let passed = false;
|
||||
function formatCompleteIncompleteGrade(score, grade, options) {
|
||||
let passed = false
|
||||
if (options.pointsPossible) {
|
||||
passed = score > 0;
|
||||
passed = score > 0
|
||||
} else {
|
||||
passed = PASS_GRADES.includes(grade);
|
||||
passed = PASS_GRADES.includes(grade)
|
||||
}
|
||||
return passed ? I18n.t('Complete') : I18n.t('Incomplete');
|
||||
return passed ? I18n.t('Complete') : I18n.t('Incomplete')
|
||||
}
|
||||
|
||||
function formatGradeInfo(gradeInfo, options = {}) {
|
||||
|
@ -137,30 +141,30 @@ const GradeFormatHelper = {
|
|||
* @return {string} Given grade rounded to two decimal places and formatted with I18n
|
||||
* if it is a point or percent grade.
|
||||
*/
|
||||
formatGrade (grade, options = {}) {
|
||||
let formattedGrade = grade;
|
||||
formatGrade(grade, options = {}) {
|
||||
let formattedGrade = grade
|
||||
|
||||
if (grade == null || grade === '') {
|
||||
return ('defaultValue' in options) ? options.defaultValue : grade;
|
||||
return 'defaultValue' in options ? options.defaultValue : grade
|
||||
}
|
||||
|
||||
if (isExcused(grade)) {
|
||||
return excused();
|
||||
return excused()
|
||||
}
|
||||
|
||||
let parsedGrade = GradeFormatHelper.parseGrade(grade, options);
|
||||
let parsedGrade = GradeFormatHelper.parseGrade(grade, options)
|
||||
|
||||
if (shouldFormatGrade(parsedGrade, options.gradingType)) {
|
||||
if (isPassFail(parsedGrade, options.gradingType)) {
|
||||
parsedGrade = normalizeCompleteIncompleteGrade(parsedGrade);
|
||||
formattedGrade = parsedGrade === 'complete' ? I18n.t('complete') : I18n.t('incomplete');
|
||||
parsedGrade = normalizeCompleteIncompleteGrade(parsedGrade)
|
||||
formattedGrade = parsedGrade === 'complete' ? I18n.t('complete') : I18n.t('incomplete')
|
||||
} else {
|
||||
const roundedGrade = round(parsedGrade, options.precision || 2);
|
||||
formattedGrade = I18n.n(roundedGrade, { percentage: isPercent(grade, options.gradingType) });
|
||||
const roundedGrade = round(parsedGrade, options.precision || 2)
|
||||
formattedGrade = I18n.n(roundedGrade, {percentage: isPercent(grade, options.gradingType)})
|
||||
}
|
||||
}
|
||||
|
||||
return formattedGrade;
|
||||
return formattedGrade
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -168,54 +172,56 @@ const GradeFormatHelper = {
|
|||
* returns delocalized point or percentage string.
|
||||
* Otherwise, returns input.
|
||||
*/
|
||||
delocalizeGrade (localizedGrade) {
|
||||
if (localizedGrade === undefined ||
|
||||
localizedGrade === null ||
|
||||
typeof localizedGrade !== 'string') {
|
||||
return localizedGrade;
|
||||
delocalizeGrade(localizedGrade) {
|
||||
if (
|
||||
localizedGrade === undefined ||
|
||||
localizedGrade === null ||
|
||||
typeof localizedGrade !== 'string'
|
||||
) {
|
||||
return localizedGrade
|
||||
}
|
||||
|
||||
const delocalizedGrade = numberHelper.parse(localizedGrade.replace('%', ''));
|
||||
const delocalizedGrade = numberHelper.parse(localizedGrade.replace('%', ''))
|
||||
|
||||
if (isNaN(delocalizedGrade)) {
|
||||
return localizedGrade;
|
||||
return localizedGrade
|
||||
}
|
||||
|
||||
return delocalizedGrade + (/%/g.test(localizedGrade) ? '%' : '');
|
||||
return delocalizedGrade + (/%/g.test(localizedGrade) ? '%' : '')
|
||||
},
|
||||
|
||||
parseGrade (grade, options = {}) {
|
||||
let parsedGrade;
|
||||
parseGrade(grade, options = {}) {
|
||||
let parsedGrade
|
||||
|
||||
if (grade == null || grade === '' || typeof grade === 'number') {
|
||||
return grade;
|
||||
return grade
|
||||
}
|
||||
|
||||
const gradeNoPercent = grade.replace('%', '')
|
||||
if ( 'delocalize' in options && !options.delocalize && !isNaN(gradeNoPercent) ) {
|
||||
parsedGrade = parseFloat(gradeNoPercent);
|
||||
if ('delocalize' in options && !options.delocalize && !isNaN(gradeNoPercent)) {
|
||||
parsedGrade = parseFloat(gradeNoPercent)
|
||||
} else {
|
||||
parsedGrade = numberHelper.parse(gradeNoPercent);
|
||||
parsedGrade = numberHelper.parse(gradeNoPercent)
|
||||
}
|
||||
|
||||
if (isNaN(parsedGrade)) {
|
||||
return grade;
|
||||
return grade
|
||||
}
|
||||
|
||||
return parsedGrade;
|
||||
return parsedGrade
|
||||
},
|
||||
|
||||
excused,
|
||||
isExcused,
|
||||
formatGradeInfo,
|
||||
|
||||
formatSubmissionGrade (submission, options = { version: 'final' }) {
|
||||
formatSubmissionGrade(submission, options = {version: 'final'}) {
|
||||
if (submission.excused) {
|
||||
return excused();
|
||||
return excused()
|
||||
}
|
||||
|
||||
const score = options.version === 'entered' ? submission.enteredScore : submission.score;
|
||||
const grade = options.version === 'entered' ? submission.enteredGrade : submission.grade;
|
||||
const score = options.version === 'entered' ? submission.enteredScore : submission.score
|
||||
const grade = options.version === 'entered' ? submission.enteredGrade : submission.grade
|
||||
|
||||
if (score == null) {
|
||||
return options.defaultValue != null ? options.defaultValue : UNGRADED
|
||||
|
@ -223,17 +229,17 @@ const GradeFormatHelper = {
|
|||
|
||||
switch (options.formatType) {
|
||||
case 'percent':
|
||||
return formatPercentageGrade(score, options);
|
||||
return formatPercentageGrade(score, options)
|
||||
case 'gradingScheme':
|
||||
return formatGradingSchemeGrade(score, grade, options);
|
||||
return formatGradingSchemeGrade(score, grade, options)
|
||||
case 'passFail':
|
||||
return formatCompleteIncompleteGrade(score, grade, options);
|
||||
return formatCompleteIncompleteGrade(score, grade, options)
|
||||
default:
|
||||
return formatPointsGrade(score);
|
||||
return formatPointsGrade(score)
|
||||
}
|
||||
},
|
||||
|
||||
UNGRADED
|
||||
};
|
||||
}
|
||||
|
||||
export default GradeFormatHelper
|
||||
|
|
|
@ -16,50 +16,50 @@
|
|||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import GradeFormatHelper from '../../../gradebook/shared/helpers/GradeFormatHelper';
|
||||
import GradeFormatHelper from '../../../gradebook/shared/helpers/GradeFormatHelper'
|
||||
|
||||
const DEFAULT_GRADE = '';
|
||||
const DEFAULT_GRADE = ''
|
||||
|
||||
export default {
|
||||
scoreToGrade (score, assignment, gradingScheme) {
|
||||
scoreToGrade(score, assignment, gradingScheme) {
|
||||
if (score == null) {
|
||||
return DEFAULT_GRADE;
|
||||
return DEFAULT_GRADE
|
||||
}
|
||||
|
||||
const gradingType = assignment.grading_type;
|
||||
const gradingType = assignment.grading_type
|
||||
|
||||
switch (gradingType) {
|
||||
case 'points': {
|
||||
return GradeFormatHelper.formatGrade(score);
|
||||
return GradeFormatHelper.formatGrade(score)
|
||||
}
|
||||
case 'percent': {
|
||||
if (!assignment.points_possible) {
|
||||
return DEFAULT_GRADE;
|
||||
return DEFAULT_GRADE
|
||||
}
|
||||
|
||||
const percentage = 100 * (score / assignment.points_possible);
|
||||
return GradeFormatHelper.formatGrade(percentage, { gradingType });
|
||||
const percentage = 100 * (score / assignment.points_possible)
|
||||
return GradeFormatHelper.formatGrade(percentage, {gradingType})
|
||||
}
|
||||
case 'pass_fail': {
|
||||
const grade = score ? 'complete' : 'incomplete';
|
||||
return GradeFormatHelper.formatGrade(grade, { gradingType });
|
||||
const grade = score ? 'complete' : 'incomplete'
|
||||
return GradeFormatHelper.formatGrade(grade, {gradingType})
|
||||
}
|
||||
case 'letter_grade': {
|
||||
if (!gradingScheme || !assignment.points_possible) {
|
||||
return DEFAULT_GRADE;
|
||||
return DEFAULT_GRADE
|
||||
}
|
||||
|
||||
const normalizedScore = score / assignment.points_possible;
|
||||
const normalizedScore = score / assignment.points_possible
|
||||
|
||||
for (let i = 0; i < gradingScheme.length; i += 1) {
|
||||
if (gradingScheme[i][1] <= normalizedScore) {
|
||||
return gradingScheme[i][0];
|
||||
return gradingScheme[i][0]
|
||||
}
|
||||
}
|
||||
}
|
||||
// fall through
|
||||
default:
|
||||
return score;
|
||||
return score
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,71 +17,83 @@
|
|||
*/
|
||||
|
||||
import _ from 'underscore'
|
||||
function uniqueEffectiveDueDates(assignment) {
|
||||
const dueDates = _.map(assignment.effectiveDueDates, function(dueDateInfo) {
|
||||
const dueAt = dueDateInfo.due_at;
|
||||
return dueAt ? new Date(dueAt) : dueAt;
|
||||
});
|
||||
function uniqueEffectiveDueDates(assignment) {
|
||||
const dueDates = _.map(assignment.effectiveDueDates, function(dueDateInfo) {
|
||||
const dueAt = dueDateInfo.due_at
|
||||
return dueAt ? new Date(dueAt) : dueAt
|
||||
})
|
||||
|
||||
return _.uniq(dueDates, date => date ? date.toString() : date);
|
||||
return _.uniq(dueDates, date => (date ? date.toString() : date))
|
||||
}
|
||||
|
||||
function getDueDateFromAssignment(assignment) {
|
||||
if (assignment.due_at) {
|
||||
return new Date(assignment.due_at)
|
||||
}
|
||||
|
||||
function getDueDateFromAssignment(assignment) {
|
||||
if (assignment.due_at) {
|
||||
return new Date(assignment.due_at);
|
||||
}
|
||||
const dueDates = uniqueEffectiveDueDates(assignment)
|
||||
return dueDates.length === 1 ? dueDates[0] : null
|
||||
}
|
||||
|
||||
const dueDates = uniqueEffectiveDueDates(assignment);
|
||||
return dueDates.length === 1 ? dueDates[0] : null;
|
||||
const assignmentHelper = {
|
||||
compareByDueDate(a, b) {
|
||||
let aDate = getDueDateFromAssignment(a)
|
||||
let bDate = getDueDateFromAssignment(b)
|
||||
const aDateIsNull = _.isNull(aDate)
|
||||
const bDateIsNull = _.isNull(bDate)
|
||||
if (aDateIsNull && !bDateIsNull) {
|
||||
return 1
|
||||
}
|
||||
if (!aDateIsNull && bDateIsNull) {
|
||||
return -1
|
||||
}
|
||||
if (aDateIsNull && bDateIsNull) {
|
||||
const aHasMultipleDates = this.hasMultipleDueDates(a)
|
||||
const bHasMultipleDates = this.hasMultipleDueDates(b)
|
||||
if (aHasMultipleDates && !bHasMultipleDates) {
|
||||
return -1
|
||||
}
|
||||
if (!aHasMultipleDates && bHasMultipleDates) {
|
||||
return 1
|
||||
}
|
||||
}
|
||||
aDate = +aDate
|
||||
bDate = +bDate
|
||||
if (aDate === bDate) {
|
||||
const aName = a.name.toLowerCase()
|
||||
const bName = b.name.toLowerCase()
|
||||
if (aName === bName) {
|
||||
return 0
|
||||
}
|
||||
return aName > bName ? 1 : -1
|
||||
}
|
||||
return aDate - bDate
|
||||
},
|
||||
|
||||
hasMultipleDueDates(assignment) {
|
||||
return uniqueEffectiveDueDates(assignment).length > 1
|
||||
},
|
||||
|
||||
getComparator(arrangeBy) {
|
||||
if (arrangeBy === 'due_date') {
|
||||
return this.compareByDueDate.bind(this)
|
||||
}
|
||||
if (arrangeBy === 'assignment_group') {
|
||||
return this.compareByAssignmentGroup.bind(this)
|
||||
}
|
||||
},
|
||||
|
||||
compareByAssignmentGroup(a, b) {
|
||||
const diffOfAssignmentGroupPosition = a.assignment_group_position - b.assignment_group_position
|
||||
if (diffOfAssignmentGroupPosition === 0) {
|
||||
const diffOfAssignmentPosition = a.position - b.position
|
||||
if (diffOfAssignmentPosition === 0) {
|
||||
return 0
|
||||
}
|
||||
return diffOfAssignmentPosition
|
||||
}
|
||||
return diffOfAssignmentGroupPosition
|
||||
}
|
||||
|
||||
const assignmentHelper = {
|
||||
compareByDueDate (a, b) {
|
||||
let aDate = getDueDateFromAssignment(a);
|
||||
let bDate = getDueDateFromAssignment(b);
|
||||
const aDateIsNull = _.isNull(aDate);
|
||||
const bDateIsNull = _.isNull(bDate);
|
||||
if (aDateIsNull && !bDateIsNull) { return 1 }
|
||||
if (!aDateIsNull && bDateIsNull) { return -1 }
|
||||
if (aDateIsNull && bDateIsNull) {
|
||||
const aHasMultipleDates = this.hasMultipleDueDates(a);
|
||||
const bHasMultipleDates = this.hasMultipleDueDates(b);
|
||||
if (aHasMultipleDates && !bHasMultipleDates) { return -1 }
|
||||
if (!aHasMultipleDates && bHasMultipleDates) { return 1 }
|
||||
}
|
||||
aDate = +aDate;
|
||||
bDate = +bDate;
|
||||
if (aDate === bDate) {
|
||||
const aName = a.name.toLowerCase();
|
||||
const bName = b.name.toLowerCase();
|
||||
if (aName === bName) { return 0 }
|
||||
return aName > bName ? 1 : -1;
|
||||
}
|
||||
return aDate - bDate;
|
||||
},
|
||||
|
||||
hasMultipleDueDates (assignment) {
|
||||
return uniqueEffectiveDueDates(assignment).length > 1;
|
||||
},
|
||||
|
||||
getComparator (arrangeBy) {
|
||||
if (arrangeBy === 'due_date') {
|
||||
return this.compareByDueDate.bind(this);
|
||||
}
|
||||
if (arrangeBy === 'assignment_group') {
|
||||
return this.compareByAssignmentGroup.bind(this);
|
||||
}
|
||||
},
|
||||
|
||||
compareByAssignmentGroup (a, b) {
|
||||
const diffOfAssignmentGroupPosition = a.assignment_group_position - b.assignment_group_position;
|
||||
if (diffOfAssignmentGroupPosition === 0) {
|
||||
const diffOfAssignmentPosition = a.position - b.position;
|
||||
if (diffOfAssignmentPosition === 0) { return 0 }
|
||||
return diffOfAssignmentPosition;
|
||||
}
|
||||
return diffOfAssignmentGroupPosition;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default assignmentHelper
|
||||
|
|
|
@ -18,94 +18,102 @@
|
|||
|
||||
import _ from 'underscore'
|
||||
import I18n from 'i18n!gradebook'
|
||||
var MessageStudentsWhoHelper = {
|
||||
settings: function(assignment, students) {
|
||||
return {
|
||||
options: this.options(assignment),
|
||||
title: assignment.name,
|
||||
points_possible: assignment.points_possible,
|
||||
students: students,
|
||||
context_code: "course_" + assignment.course_id,
|
||||
callback: this.callbackFn.bind(this),
|
||||
subjectCallback: this.generateSubjectCallbackFn(assignment)
|
||||
}
|
||||
},
|
||||
|
||||
options: function(assignment) {
|
||||
var options = this.allOptions();
|
||||
var noSubmissions = !this.hasSubmission(assignment);
|
||||
if(noSubmissions) options.splice(0,1);
|
||||
return options;
|
||||
},
|
||||
|
||||
allOptions: function() {
|
||||
return [
|
||||
{
|
||||
text: I18n.t("students_who.havent_submitted_yet", "Haven't submitted yet"),
|
||||
subjectFn: (assignment) => I18n.t('students_who.no_submission_for', 'No submission for %{assignment}', { assignment: assignment.name }),
|
||||
criteriaFn: (student) => !student.submitted_at
|
||||
},
|
||||
{
|
||||
text: I18n.t("students_who.havent_been_graded", "Haven't been graded"),
|
||||
subjectFn: (assignment) => I18n.t('students_who.no_grade_for', 'No grade for %{assignment}', { assignment: assignment.name }),
|
||||
criteriaFn: (student) => !this.exists(student.score)
|
||||
},
|
||||
{
|
||||
text: I18n.t("students_who.scored_less_than", "Scored less than"),
|
||||
cutoff: true,
|
||||
subjectFn: (assignment, cutoff) => I18n.t(
|
||||
'Scored less than %{cutoff} on %{assignment}',
|
||||
{ assignment: assignment.name, cutoff: I18n.n(cutoff) }
|
||||
),
|
||||
criteriaFn: (student, cutoff) => this.scoreWithCutoff(student, cutoff) && student.score < cutoff
|
||||
},
|
||||
{
|
||||
text: I18n.t("students_who.scored_more_than", "Scored more than"),
|
||||
cutoff: true,
|
||||
subjectFn: (assignment, cutoff) => I18n.t(
|
||||
'Scored more than %{cutoff} on %{assignment}',
|
||||
{ assignment: assignment.name, cutoff: I18n.n(cutoff) }
|
||||
),
|
||||
criteriaFn: (student, cutoff) => this.scoreWithCutoff(student, cutoff) && student.score > cutoff
|
||||
}
|
||||
];
|
||||
},
|
||||
|
||||
hasSubmission: function(assignment) {
|
||||
var submissionTypes = assignment.submission_types;
|
||||
if(submissionTypes.length === 0) return false;
|
||||
|
||||
return _.any(submissionTypes, (submissionType) => {
|
||||
return submissionType !== 'none' && submissionType !== 'on_paper';
|
||||
});
|
||||
},
|
||||
|
||||
exists: function(value) {
|
||||
return !_.isUndefined(value) && !_.isNull(value);
|
||||
},
|
||||
|
||||
scoreWithCutoff: function(student, cutoff) {
|
||||
return this.exists(student.score)
|
||||
&& student.score !== ''
|
||||
&& this.exists(cutoff);
|
||||
},
|
||||
|
||||
callbackFn: function(selected, cutoff, students) {
|
||||
var criteriaFn = this.findOptionByText(selected).criteriaFn;
|
||||
var students = _.filter(students, student => criteriaFn(student.user_data, cutoff));
|
||||
return _.map(students, student => student.user_data.id);
|
||||
},
|
||||
|
||||
findOptionByText: function(text) {
|
||||
return _.find(this.allOptions(), option => option.text === text);
|
||||
},
|
||||
|
||||
generateSubjectCallbackFn: function(assignment) {
|
||||
return (selected, cutoff) => {
|
||||
var cutoffString = cutoff || '';
|
||||
var subjectFn = this.findOptionByText(selected).subjectFn;
|
||||
return subjectFn(assignment, cutoffString);
|
||||
}
|
||||
var MessageStudentsWhoHelper = {
|
||||
settings: function(assignment, students) {
|
||||
return {
|
||||
options: this.options(assignment),
|
||||
title: assignment.name,
|
||||
points_possible: assignment.points_possible,
|
||||
students: students,
|
||||
context_code: 'course_' + assignment.course_id,
|
||||
callback: this.callbackFn.bind(this),
|
||||
subjectCallback: this.generateSubjectCallbackFn(assignment)
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
options: function(assignment) {
|
||||
var options = this.allOptions()
|
||||
var noSubmissions = !this.hasSubmission(assignment)
|
||||
if (noSubmissions) options.splice(0, 1)
|
||||
return options
|
||||
},
|
||||
|
||||
allOptions: function() {
|
||||
return [
|
||||
{
|
||||
text: I18n.t('students_who.havent_submitted_yet', "Haven't submitted yet"),
|
||||
subjectFn: assignment =>
|
||||
I18n.t('students_who.no_submission_for', 'No submission for %{assignment}', {
|
||||
assignment: assignment.name
|
||||
}),
|
||||
criteriaFn: student => !student.submitted_at
|
||||
},
|
||||
{
|
||||
text: I18n.t('students_who.havent_been_graded', "Haven't been graded"),
|
||||
subjectFn: assignment =>
|
||||
I18n.t('students_who.no_grade_for', 'No grade for %{assignment}', {
|
||||
assignment: assignment.name
|
||||
}),
|
||||
criteriaFn: student => !this.exists(student.score)
|
||||
},
|
||||
{
|
||||
text: I18n.t('students_who.scored_less_than', 'Scored less than'),
|
||||
cutoff: true,
|
||||
subjectFn: (assignment, cutoff) =>
|
||||
I18n.t('Scored less than %{cutoff} on %{assignment}', {
|
||||
assignment: assignment.name,
|
||||
cutoff: I18n.n(cutoff)
|
||||
}),
|
||||
criteriaFn: (student, cutoff) =>
|
||||
this.scoreWithCutoff(student, cutoff) && student.score < cutoff
|
||||
},
|
||||
{
|
||||
text: I18n.t('students_who.scored_more_than', 'Scored more than'),
|
||||
cutoff: true,
|
||||
subjectFn: (assignment, cutoff) =>
|
||||
I18n.t('Scored more than %{cutoff} on %{assignment}', {
|
||||
assignment: assignment.name,
|
||||
cutoff: I18n.n(cutoff)
|
||||
}),
|
||||
criteriaFn: (student, cutoff) =>
|
||||
this.scoreWithCutoff(student, cutoff) && student.score > cutoff
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
hasSubmission: function(assignment) {
|
||||
var submissionTypes = assignment.submission_types
|
||||
if (submissionTypes.length === 0) return false
|
||||
|
||||
return _.any(submissionTypes, submissionType => {
|
||||
return submissionType !== 'none' && submissionType !== 'on_paper'
|
||||
})
|
||||
},
|
||||
|
||||
exists: function(value) {
|
||||
return !_.isUndefined(value) && !_.isNull(value)
|
||||
},
|
||||
|
||||
scoreWithCutoff: function(student, cutoff) {
|
||||
return this.exists(student.score) && student.score !== '' && this.exists(cutoff)
|
||||
},
|
||||
|
||||
callbackFn: function(selected, cutoff, students) {
|
||||
var criteriaFn = this.findOptionByText(selected).criteriaFn
|
||||
var students = _.filter(students, student => criteriaFn(student.user_data, cutoff))
|
||||
return _.map(students, student => student.user_data.id)
|
||||
},
|
||||
|
||||
findOptionByText: function(text) {
|
||||
return _.find(this.allOptions(), option => option.text === text)
|
||||
},
|
||||
|
||||
generateSubjectCallbackFn: function(assignment) {
|
||||
return (selected, cutoff) => {
|
||||
var cutoffString = cutoff || ''
|
||||
var subjectFn = this.findOptionByText(selected).subjectFn
|
||||
return subjectFn(assignment, cutoffString)
|
||||
}
|
||||
}
|
||||
}
|
||||
export default MessageStudentsWhoHelper
|
||||
|
|
|
@ -21,164 +21,188 @@ import $ from 'jquery'
|
|||
import I18n from 'i18n!gradebook_upload'
|
||||
import 'jquery.ajaxJSON'
|
||||
|
||||
const successMessage = I18n.t(
|
||||
'You will be redirected to Gradebook while your file is being uploaded. ' +
|
||||
'If you have a large CSV file, your changes may take a few minutes to update. ' +
|
||||
'To prevent overwriting any data, please confirm the upload has completed and ' +
|
||||
'Gradebook is correct before making additional changes.'
|
||||
);
|
||||
const successMessage = I18n.t(
|
||||
'You will be redirected to Gradebook while your file is being uploaded. ' +
|
||||
'If you have a large CSV file, your changes may take a few minutes to update. ' +
|
||||
'To prevent overwriting any data, please confirm the upload has completed and ' +
|
||||
'Gradebook is correct before making additional changes.'
|
||||
)
|
||||
|
||||
const ProcessGradebookUpload = {
|
||||
upload (gradebook) {
|
||||
if (gradebook != null && (_.isArray(gradebook.assignments) || _.isArray(gradebook.custom_columns)) && _.isArray(gradebook.students)) {
|
||||
if (gradebook.custom_columns && gradebook.custom_columns.length > 0) {
|
||||
this.uploadCustomColumnData(gradebook);
|
||||
}
|
||||
|
||||
const createAssignmentsResponses = this.createAssignments(gradebook);
|
||||
return $.when(...createAssignmentsResponses).then((...responses) => {
|
||||
this.uploadGradeData(gradebook, responses);
|
||||
});
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
|
||||
uploadCustomColumnData (gradebook) {
|
||||
const customColumnData = gradebook.students.reduce((accumulator, student) => {
|
||||
const student_id = Number.parseInt(student.id, 10);
|
||||
if (!(student_id in accumulator)) {
|
||||
accumulator[student_id] = student.custom_column_data // eslint-disable-line no-param-reassign
|
||||
}
|
||||
return accumulator;
|
||||
}, {});
|
||||
|
||||
if (!_.isEmpty(customColumnData)) {
|
||||
this.parseCustomColumnData(customColumnData);
|
||||
const ProcessGradebookUpload = {
|
||||
upload(gradebook) {
|
||||
if (
|
||||
gradebook != null &&
|
||||
(_.isArray(gradebook.assignments) || _.isArray(gradebook.custom_columns)) &&
|
||||
_.isArray(gradebook.students)
|
||||
) {
|
||||
if (gradebook.custom_columns && gradebook.custom_columns.length > 0) {
|
||||
this.uploadCustomColumnData(gradebook)
|
||||
}
|
||||
|
||||
if (!gradebook.assignments.length) {
|
||||
alert(successMessage); // eslint-disable-line no-alert
|
||||
this.goToGradebook();
|
||||
}
|
||||
},
|
||||
|
||||
parseCustomColumnData (customColumnData) {
|
||||
const data = [];
|
||||
Object.keys(customColumnData).forEach(studentId => {
|
||||
customColumnData[studentId].forEach((column) => {
|
||||
data.push({
|
||||
column_id: Number.parseInt(column.column_id, 10),
|
||||
user_id: studentId,
|
||||
content: column.new_content
|
||||
})
|
||||
});
|
||||
const createAssignmentsResponses = this.createAssignments(gradebook)
|
||||
return $.when(...createAssignmentsResponses).then((...responses) => {
|
||||
this.uploadGradeData(gradebook, responses)
|
||||
})
|
||||
}
|
||||
return undefined
|
||||
},
|
||||
|
||||
this.submitCustomColumnData(data);
|
||||
return data;
|
||||
},
|
||||
uploadCustomColumnData(gradebook) {
|
||||
const customColumnData = gradebook.students.reduce((accumulator, student) => {
|
||||
const student_id = Number.parseInt(student.id, 10)
|
||||
if (!(student_id in accumulator)) {
|
||||
accumulator[student_id] = student.custom_column_data // eslint-disable-line no-param-reassign
|
||||
}
|
||||
return accumulator
|
||||
}, {})
|
||||
|
||||
submitCustomColumnData (data) {
|
||||
return $.ajaxJSON(ENV.bulk_update_custom_columns_path,
|
||||
'PUT',
|
||||
JSON.stringify({column_data: data}),
|
||||
null,
|
||||
null,
|
||||
{contentType: 'application/json'});
|
||||
},
|
||||
if (!_.isEmpty(customColumnData)) {
|
||||
this.parseCustomColumnData(customColumnData)
|
||||
}
|
||||
|
||||
createAssignments (gradebook) {
|
||||
const newAssignments = this.getNewAssignmentsFromGradebook(gradebook);
|
||||
return newAssignments.map(assignment => this.createIndividualAssignment(assignment));
|
||||
},
|
||||
if (!gradebook.assignments.length) {
|
||||
alert(successMessage) // eslint-disable-line no-alert
|
||||
this.goToGradebook()
|
||||
}
|
||||
},
|
||||
|
||||
getNewAssignmentsFromGradebook (gradebook) {
|
||||
return gradebook.assignments.filter(a => a.id != null && a.id <= 0);
|
||||
},
|
||||
parseCustomColumnData(customColumnData) {
|
||||
const data = []
|
||||
Object.keys(customColumnData).forEach(studentId => {
|
||||
customColumnData[studentId].forEach(column => {
|
||||
data.push({
|
||||
column_id: Number.parseInt(column.column_id, 10),
|
||||
user_id: studentId,
|
||||
content: column.new_content
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
createIndividualAssignment (assignment) {
|
||||
return $.ajaxJSON(ENV.create_assignment_path, 'POST', JSON.stringify({
|
||||
this.submitCustomColumnData(data)
|
||||
return data
|
||||
},
|
||||
|
||||
submitCustomColumnData(data) {
|
||||
return $.ajaxJSON(
|
||||
ENV.bulk_update_custom_columns_path,
|
||||
'PUT',
|
||||
JSON.stringify({column_data: data}),
|
||||
null,
|
||||
null,
|
||||
{contentType: 'application/json'}
|
||||
)
|
||||
},
|
||||
|
||||
createAssignments(gradebook) {
|
||||
const newAssignments = this.getNewAssignmentsFromGradebook(gradebook)
|
||||
return newAssignments.map(assignment => this.createIndividualAssignment(assignment))
|
||||
},
|
||||
|
||||
getNewAssignmentsFromGradebook(gradebook) {
|
||||
return gradebook.assignments.filter(a => a.id != null && a.id <= 0)
|
||||
},
|
||||
|
||||
createIndividualAssignment(assignment) {
|
||||
return $.ajaxJSON(
|
||||
ENV.create_assignment_path,
|
||||
'POST',
|
||||
JSON.stringify({
|
||||
assignment: {
|
||||
name: assignment.title,
|
||||
points_possible: assignment.points_possible,
|
||||
published: true
|
||||
},
|
||||
calculate_grades: false
|
||||
}), null, null, {contentType: 'application/json'});
|
||||
},
|
||||
}),
|
||||
null,
|
||||
null,
|
||||
{contentType: 'application/json'}
|
||||
)
|
||||
},
|
||||
|
||||
uploadGradeData (gradebook, responses) {
|
||||
const gradeData = this.populateGradeData(gradebook, responses);
|
||||
uploadGradeData(gradebook, responses) {
|
||||
const gradeData = this.populateGradeData(gradebook, responses)
|
||||
|
||||
if (_.isEmpty(gradeData)) {
|
||||
this.goToGradebook();
|
||||
} else {
|
||||
this.submitGradeData(gradeData).then((progress) => {
|
||||
alert(successMessage); // eslint-disable-line no-alert
|
||||
ProcessGradebookUpload.goToGradebook();
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
populateGradeData (gradebook, responses) {
|
||||
const assignmentMap = this.mapLocalAssignmentsToDatabaseAssignments(gradebook, responses);
|
||||
|
||||
const gradeData = {};
|
||||
gradebook.students.forEach(student => this.populateGradeDataPerStudent(student, assignmentMap, gradeData));
|
||||
return gradeData;
|
||||
},
|
||||
|
||||
mapLocalAssignmentsToDatabaseAssignments (gradebook, responses) {
|
||||
const newAssignments = this.getNewAssignmentsFromGradebook(gradebook);
|
||||
let responsesLists = responses;
|
||||
|
||||
if (newAssignments.length === 1) {
|
||||
responsesLists = [responses];
|
||||
}
|
||||
|
||||
const assignmentMap = {};
|
||||
|
||||
_(newAssignments).zip(responsesLists).forEach((fakeAndCreated) => {
|
||||
const [assignmentStub, response] = fakeAndCreated;
|
||||
const [createdAssignment] = response;
|
||||
assignmentMap[assignmentStub.id] = createdAssignment.id;
|
||||
});
|
||||
|
||||
return assignmentMap;
|
||||
},
|
||||
|
||||
populateGradeDataPerStudent (student, assignmentMap, gradeData) {
|
||||
student.submissions.forEach((submission) => {
|
||||
this.populateGradeDataPerSubmission(submission, student.previous_id, assignmentMap, gradeData);
|
||||
});
|
||||
},
|
||||
|
||||
populateGradeDataPerSubmission (submission, studentId, assignmentMap, gradeData) {
|
||||
const assignmentId = assignmentMap[submission.assignment_id] || submission.assignment_id;
|
||||
|
||||
if (assignmentId <= 0) return; // unrecognized and ignored assignments
|
||||
if (submission.original_grade === submission.grade) return; // no change
|
||||
|
||||
gradeData[assignmentId] = gradeData[assignmentId] || {}; // eslint-disable-line no-param-reassign
|
||||
|
||||
if (String(submission.grade || '').toUpperCase() === 'EX') {
|
||||
gradeData[assignmentId][studentId] = { excuse: true }; // eslint-disable-line no-param-reassign
|
||||
} else {
|
||||
gradeData[assignmentId][studentId] = { // eslint-disable-line no-param-reassign
|
||||
posted_grade: submission.grade
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
submitGradeData (gradeData) {
|
||||
return $.ajaxJSON(ENV.bulk_update_path, 'POST', JSON.stringify({grade_data: gradeData}),
|
||||
null, null, {contentType: 'application/json'});
|
||||
},
|
||||
|
||||
goToGradebook () {
|
||||
$('#gradebook_grid_form').text(I18n.t('Done.'));
|
||||
window.location = ENV.gradebook_path;
|
||||
if (_.isEmpty(gradeData)) {
|
||||
this.goToGradebook()
|
||||
} else {
|
||||
this.submitGradeData(gradeData).then(progress => {
|
||||
alert(successMessage) // eslint-disable-line no-alert
|
||||
ProcessGradebookUpload.goToGradebook()
|
||||
})
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
populateGradeData(gradebook, responses) {
|
||||
const assignmentMap = this.mapLocalAssignmentsToDatabaseAssignments(gradebook, responses)
|
||||
|
||||
const gradeData = {}
|
||||
gradebook.students.forEach(student =>
|
||||
this.populateGradeDataPerStudent(student, assignmentMap, gradeData)
|
||||
)
|
||||
return gradeData
|
||||
},
|
||||
|
||||
mapLocalAssignmentsToDatabaseAssignments(gradebook, responses) {
|
||||
const newAssignments = this.getNewAssignmentsFromGradebook(gradebook)
|
||||
let responsesLists = responses
|
||||
|
||||
if (newAssignments.length === 1) {
|
||||
responsesLists = [responses]
|
||||
}
|
||||
|
||||
const assignmentMap = {}
|
||||
|
||||
_(newAssignments)
|
||||
.zip(responsesLists)
|
||||
.forEach(fakeAndCreated => {
|
||||
const [assignmentStub, response] = fakeAndCreated
|
||||
const [createdAssignment] = response
|
||||
assignmentMap[assignmentStub.id] = createdAssignment.id
|
||||
})
|
||||
|
||||
return assignmentMap
|
||||
},
|
||||
|
||||
populateGradeDataPerStudent(student, assignmentMap, gradeData) {
|
||||
student.submissions.forEach(submission => {
|
||||
this.populateGradeDataPerSubmission(submission, student.previous_id, assignmentMap, gradeData)
|
||||
})
|
||||
},
|
||||
|
||||
populateGradeDataPerSubmission(submission, studentId, assignmentMap, gradeData) {
|
||||
const assignmentId = assignmentMap[submission.assignment_id] || submission.assignment_id
|
||||
|
||||
if (assignmentId <= 0) return // unrecognized and ignored assignments
|
||||
if (submission.original_grade === submission.grade) return // no change
|
||||
|
||||
gradeData[assignmentId] = gradeData[assignmentId] || {} // eslint-disable-line no-param-reassign
|
||||
|
||||
if (String(submission.grade || '').toUpperCase() === 'EX') {
|
||||
gradeData[assignmentId][studentId] = {excuse: true} // eslint-disable-line no-param-reassign
|
||||
} else {
|
||||
gradeData[assignmentId][studentId] = {
|
||||
// eslint-disable-line no-param-reassign
|
||||
posted_grade: submission.grade
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
submitGradeData(gradeData) {
|
||||
return $.ajaxJSON(
|
||||
ENV.bulk_update_path,
|
||||
'POST',
|
||||
JSON.stringify({grade_data: gradeData}),
|
||||
null,
|
||||
null,
|
||||
{contentType: 'application/json'}
|
||||
)
|
||||
},
|
||||
|
||||
goToGradebook() {
|
||||
$('#gradebook_grid_form').text(I18n.t('Done.'))
|
||||
window.location = ENV.gradebook_path
|
||||
}
|
||||
}
|
||||
|
||||
export default ProcessGradebookUpload
|
||||
|
|
|
@ -238,23 +238,44 @@ test('does not disable "Set Default Grade" when isAdmin', function() {
|
|||
ENV.current_user_roles = ['admin']
|
||||
this.assignment = {inClosedGradingPeriod: true}
|
||||
this.disableUnavailableMenuActions(this.menu)
|
||||
strictEqual(this.menu.find('[data-action="setDefaultGrade"]')[0].getAttribute('aria-disabled'), null)
|
||||
strictEqual(
|
||||
this.menu.find('[data-action="setDefaultGrade"]')[0].getAttribute('aria-disabled'),
|
||||
null
|
||||
)
|
||||
})
|
||||
|
||||
test('disables "Unmute Assignment" when the assignment is moderated and grades have not been published', function () {
|
||||
this.assignment = {moderated_grading: true, grades_published: false, inClosedGradingPeriod: false, muted: true}
|
||||
test('disables "Unmute Assignment" when the assignment is moderated and grades have not been published', function() {
|
||||
this.assignment = {
|
||||
moderated_grading: true,
|
||||
grades_published: false,
|
||||
inClosedGradingPeriod: false,
|
||||
muted: true
|
||||
}
|
||||
this.disableUnavailableMenuActions(this.menu)
|
||||
strictEqual(this.menu.find('[data-action="toggleMuting"]')[0].getAttribute('aria-disabled'), 'true')
|
||||
strictEqual(
|
||||
this.menu.find('[data-action="toggleMuting"]')[0].getAttribute('aria-disabled'),
|
||||
'true'
|
||||
)
|
||||
})
|
||||
|
||||
test('does not disable "Unmute Assignment" when grades are published', function () {
|
||||
this.assignment = {moderated_grading: true, grades_published: true, inClosedGradingPeriod: false, muted: true}
|
||||
test('does not disable "Unmute Assignment" when grades are published', function() {
|
||||
this.assignment = {
|
||||
moderated_grading: true,
|
||||
grades_published: true,
|
||||
inClosedGradingPeriod: false,
|
||||
muted: true
|
||||
}
|
||||
this.disableUnavailableMenuActions(this.menu)
|
||||
strictEqual(this.menu.find('[data-action="toggleMuting"]')[0].getAttribute('aria-disabled'), null)
|
||||
})
|
||||
|
||||
test('does not disable "Mute Assignment" when the assignment can be muted', function () {
|
||||
this.assignment = {moderated_grading: true, grades_published: false, inClosedGradingPeriod: false, muted: false}
|
||||
test('does not disable "Mute Assignment" when the assignment can be muted', function() {
|
||||
this.assignment = {
|
||||
moderated_grading: true,
|
||||
grades_published: false,
|
||||
inClosedGradingPeriod: false,
|
||||
muted: false
|
||||
}
|
||||
this.disableUnavailableMenuActions(this.menu)
|
||||
strictEqual(this.menu.find('[data-action="toggleMuting"]')[0].getAttribute('aria-disabled'), null)
|
||||
})
|
||||
|
|
|
@ -366,7 +366,14 @@ QUnit.module('Gradebook#getVisibleGradeGridColumns', {
|
|||
{object: {assignment_group: {position: 1}, position: 1, name: 'first'}},
|
||||
{object: {assignment_group: {position: 1}, position: 2, name: 'second'}},
|
||||
{object: {assignment_group: {position: 1}, position: 3, name: 'third'}},
|
||||
{object: {assignment_group: {position: 1}, position: 4, name: 'moderated', moderation_in_progress: true}}
|
||||
{
|
||||
object: {
|
||||
assignment_group: {position: 1},
|
||||
position: 4,
|
||||
name: 'moderated',
|
||||
moderation_in_progress: true
|
||||
}
|
||||
}
|
||||
]
|
||||
this.aggregateColumns = []
|
||||
this.parentColumns = []
|
||||
|
@ -399,7 +406,10 @@ test('It does not sort columns when gradebookColumnOrderSettings is undefined',
|
|||
})
|
||||
|
||||
test('sets cannot_edit if moderation_in_progress is true on the column object', function() {
|
||||
const moderatedColumn = _.find(this.allAssignmentColumns, (column) => column.object.moderation_in_progress)
|
||||
const moderatedColumn = _.find(
|
||||
this.allAssignmentColumns,
|
||||
column => column.object.moderation_in_progress
|
||||
)
|
||||
this.getVisibleGradeGridColumns()
|
||||
strictEqual(moderatedColumn.cssClass, 'cannot_edit')
|
||||
})
|
||||
|
@ -414,16 +424,16 @@ QUnit.module('Gradebook#customColumnDefinitions', {
|
|||
setup() {
|
||||
this.gradebook = createGradebook()
|
||||
this.gradebook.customColumns = [
|
||||
{ id: '1', teacher_notes: false, hidden: false, title: 'Read Only', read_only: true },
|
||||
{ id: '2', teacher_notes: false, hidden: false, title: 'Not Read Only', read_only: false }
|
||||
{id: '1', teacher_notes: false, hidden: false, title: 'Read Only', read_only: true},
|
||||
{id: '2', teacher_notes: false, hidden: false, title: 'Not Read Only', read_only: false}
|
||||
]
|
||||
}
|
||||
})
|
||||
|
||||
test('includes the cannot_edit class for read_only columns', function () {
|
||||
test('includes the cannot_edit class for read_only columns', function() {
|
||||
columns = this.gradebook.customColumnDefinitions()
|
||||
equal(columns[0].cssClass, "meta-cell custom_column cannot_edit")
|
||||
equal(columns[1].cssClass, "meta-cell custom_column")
|
||||
equal(columns[0].cssClass, 'meta-cell custom_column cannot_edit')
|
||||
equal(columns[1].cssClass, 'meta-cell custom_column')
|
||||
})
|
||||
|
||||
QUnit.module('Gradebook#fieldsToExcludeFromAssignments', {
|
||||
|
@ -923,16 +933,18 @@ QUnit.module('Gradebook#gotAllAssignmentGroups', suiteHooks => {
|
|||
anonymize_students: true
|
||||
}
|
||||
|
||||
assignmentGroups = [{
|
||||
id: 1,
|
||||
assignments: [
|
||||
unmoderatedAssignment,
|
||||
moderatedUnpublishedAssignment,
|
||||
moderatedPublishedAssignment,
|
||||
anonymousUnmoderatedAssignment,
|
||||
anonymousModeratedAssignment
|
||||
]
|
||||
}]
|
||||
assignmentGroups = [
|
||||
{
|
||||
id: 1,
|
||||
assignments: [
|
||||
unmoderatedAssignment,
|
||||
moderatedUnpublishedAssignment,
|
||||
moderatedPublishedAssignment,
|
||||
anonymousUnmoderatedAssignment,
|
||||
anonymousModeratedAssignment
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
gradebook = createGradebook()
|
||||
sinon.stub(gradebook, 'updateAssignmentEffectiveDueDates')
|
||||
|
@ -948,7 +960,8 @@ QUnit.module('Gradebook#gotAllAssignmentGroups', suiteHooks => {
|
|||
|
||||
test('sets moderation_in_progress to true for a moderated assignment whose grades are not published', () => {
|
||||
gradebook.gotAllAssignmentGroups(assignmentGroups)
|
||||
strictEqual(moderatedUnpublishedAssignment.moderation_in_progress, true) })
|
||||
strictEqual(moderatedUnpublishedAssignment.moderation_in_progress, true)
|
||||
})
|
||||
|
||||
test('sets moderation_in_progress to false for a moderated assignment whose grades are published', () => {
|
||||
gradebook.gotAllAssignmentGroups(assignmentGroups)
|
||||
|
@ -961,7 +974,7 @@ QUnit.module('Gradebook#gotAllAssignmentGroups', suiteHooks => {
|
|||
})
|
||||
})
|
||||
|
||||
QUnit.module('Gradebook#calculateAndRoundGroupTotalScore', (hooks) => {
|
||||
QUnit.module('Gradebook#calculateAndRoundGroupTotalScore', hooks => {
|
||||
let gradebook
|
||||
|
||||
hooks.beforeEach(() => {
|
||||
|
@ -980,13 +993,13 @@ QUnit.module('Gradebook#calculateAndRoundGroupTotalScore', (hooks) => {
|
|||
|
||||
test('avoids floating point calculation issues', () => {
|
||||
const score = gradebook.calculateAndRoundGroupTotalScore(946.65, 1000)
|
||||
const floatingPointResult = 946.65 / 1000 * 100
|
||||
const floatingPointResult = (946.65 / 1000) * 100
|
||||
strictEqual(floatingPointResult, 94.66499999999999)
|
||||
strictEqual(score, 94.67)
|
||||
})
|
||||
})
|
||||
|
||||
QUnit.module('Gradebook#handleAssignmentMutingChange', (hooks) => {
|
||||
QUnit.module('Gradebook#handleAssignmentMutingChange', hooks => {
|
||||
let gradebook
|
||||
let updatedAssignment
|
||||
|
||||
|
@ -998,7 +1011,7 @@ QUnit.module('Gradebook#handleAssignmentMutingChange', (hooks) => {
|
|||
getColumns: sinon.stub().returns([{}]),
|
||||
invalidateRow() {},
|
||||
render() {},
|
||||
setColumns() {},
|
||||
setColumns() {}
|
||||
}
|
||||
gradebook.students = {4: {id: '4', name: 'fred'}}
|
||||
gradebook.studentViewStudents = {6: {id: '6', name: 'fake fred'}}
|
||||
|
@ -1008,7 +1021,10 @@ QUnit.module('Gradebook#handleAssignmentMutingChange', (hooks) => {
|
|||
|
||||
test('updates the anonymize_students attribute on the assignment', () => {
|
||||
gradebook.handleAssignmentMutingChange(updatedAssignment)
|
||||
strictEqual(gradebook.assignments[updatedAssignment.id].anonymize_students, updatedAssignment.anonymize_students)
|
||||
strictEqual(
|
||||
gradebook.assignments[updatedAssignment.id].anonymize_students,
|
||||
updatedAssignment.anonymize_students
|
||||
)
|
||||
})
|
||||
|
||||
test('updates the muted attribute on the assignment', () => {
|
||||
|
|
|
@ -41,11 +41,11 @@ test('Grid.Util._toRow', () => {
|
|||
Grid.students = {1: {}}
|
||||
Grid.sections = {1: {}}
|
||||
const rollup = {
|
||||
links: { section: "1", user: "1" },
|
||||
scores: [{ score: "3", hide_points: true, links: { outcome:"2" } }]
|
||||
links: {section: '1', user: '1'},
|
||||
scores: [{score: '3', hide_points: true, links: {outcome: '2'}}]
|
||||
}
|
||||
ok(
|
||||
isEqual(Grid.Util._toRow([rollup], null).outcome_2, { score: "3", hide_points: true }),
|
||||
isEqual(Grid.Util._toRow([rollup], null).outcome_2, {score: '3', hide_points: true}),
|
||||
'correctly returns an object with a score and hide_points for a cell'
|
||||
)
|
||||
})
|
||||
|
@ -55,17 +55,17 @@ test('Grid.Util.toRows', () => {
|
|||
Grid.sections = {1: {}}
|
||||
const rollups = [
|
||||
{
|
||||
links: { section: "1", user: "3" }
|
||||
links: {section: '1', user: '3'}
|
||||
},
|
||||
{
|
||||
links: { section: "1", user: "1" }
|
||||
links: {section: '1', user: '1'}
|
||||
},
|
||||
{
|
||||
links: { section: "1", user: "2" }
|
||||
links: {section: '1', user: '2'}
|
||||
}
|
||||
]
|
||||
ok(
|
||||
isEqual(Grid.Util.toRows(rollups).map((r) => r.student.id), [3, 1, 2]),
|
||||
isEqual(Grid.Util.toRows(rollups).map(r => r.student.id), [3, 1, 2]),
|
||||
'returns rows in the same user order as rollups'
|
||||
)
|
||||
})
|
||||
|
@ -74,14 +74,11 @@ test('Grid.View.masteryDetails', () => {
|
|||
const outcome = {mastery_points: 5, points_possible: 10}
|
||||
const spy = sinon.spy(Grid.View, 'legacyMasteryDetails')
|
||||
Grid.View.masteryDetails(10, outcome)
|
||||
ok(
|
||||
spy.calledOnce,
|
||||
'calls legacyMasteryDetails when no custom ratings defined'
|
||||
)
|
||||
ok(spy.calledOnce, 'calls legacyMasteryDetails when no custom ratings defined')
|
||||
Grid.ratings = [
|
||||
{points: 10, color: '00ff00', description: 'great'},
|
||||
{points: 5, color: '0000ff', description: 'OK'},
|
||||
{points: 0, color: 'ff0000', description: 'turrable'}
|
||||
{points: 5, color: '0000ff', description: 'OK'},
|
||||
{points: 0, color: 'ff0000', description: 'turrable'}
|
||||
]
|
||||
ok(
|
||||
isEqual(Grid.View.masteryDetails(10, outcome), ['rating_0', '#00ff00', 'great']),
|
||||
|
@ -109,8 +106,8 @@ test('Grid.View.masteryDetails with scaling', () => {
|
|||
const outcome = {points_possible: 5}
|
||||
Grid.ratings = [
|
||||
{points: 10, color: '00ff00', description: 'great'},
|
||||
{points: 5, color: '0000ff', description: 'OK'},
|
||||
{points: 0, color: 'ff0000', description: 'turrable'}
|
||||
{points: 5, color: '0000ff', description: 'OK'},
|
||||
{points: 0, color: 'ff0000', description: 'turrable'}
|
||||
]
|
||||
ok(
|
||||
isEqual(Grid.View.masteryDetails(5, outcome), ['rating_0', '#00ff00', 'great']),
|
||||
|
@ -130,8 +127,8 @@ test('Grid.View.masteryDetails with scaling (points_possible 0)', () => {
|
|||
const outcome = {mastery_points: 5, points_possible: 0}
|
||||
Grid.ratings = [
|
||||
{points: 10, color: '00ff00', description: 'great'},
|
||||
{points: 5, color: '0000ff', description: 'OK'},
|
||||
{points: 0, color: 'ff0000', description: 'turrable'}
|
||||
{points: 5, color: '0000ff', description: 'OK'},
|
||||
{points: 0, color: 'ff0000', description: 'turrable'}
|
||||
]
|
||||
ok(
|
||||
isEqual(Grid.View.masteryDetails(5, outcome), ['rating_0', '#00ff00', 'great']),
|
||||
|
@ -166,7 +163,11 @@ test('Grid.View.legacyMasteryDetails', () => {
|
|||
'returns "near-mastery" if half of mastery score or greater'
|
||||
)
|
||||
ok(
|
||||
isEqual(Grid.View.legacyMasteryDetails(1, outcome), ['rating_3', '#EE0612', 'Well Below Mastery']),
|
||||
isEqual(Grid.View.legacyMasteryDetails(1, outcome), [
|
||||
'rating_3',
|
||||
'#EE0612',
|
||||
'Well Below Mastery'
|
||||
]),
|
||||
'returns "remedial" if less than half of mastery score'
|
||||
)
|
||||
})
|
||||
|
@ -188,16 +189,15 @@ test('Grid.Util._studentColumn does not modify default options', () => {
|
|||
test('Grid.Util.toColumns hasResults', () => {
|
||||
const outcomes = [
|
||||
{
|
||||
id: "1"
|
||||
|
||||
id: '1'
|
||||
},
|
||||
{
|
||||
id: "2"
|
||||
id: '2'
|
||||
}
|
||||
]
|
||||
const rollup = {
|
||||
links: { section: "1", user: "1" },
|
||||
scores: [{ score: "3", hide_points: true, links: { outcome:"2" } }]
|
||||
links: {section: '1', user: '1'},
|
||||
scores: [{score: '3', hide_points: true, links: {outcome: '2'}}]
|
||||
}
|
||||
const columns = Grid.Util.toColumns(outcomes, [rollup])
|
||||
ok(isEqual(columns[1].hasResults, false))
|
||||
|
|
|
@ -80,7 +80,8 @@ test('#loadValue escapes html', function() {
|
|||
})
|
||||
|
||||
test('#class.formatter rounds numbers if they are numbers', function() {
|
||||
sandbox.stub(SubmissionCell.prototype, 'cellWrapper')
|
||||
sandbox
|
||||
.stub(SubmissionCell.prototype, 'cellWrapper')
|
||||
.withArgs('0.67')
|
||||
.returns('ok')
|
||||
const formattedResponse = SubmissionCell.formatter(0, 0, {grade: 0.666}, {}, {})
|
||||
|
@ -88,7 +89,8 @@ test('#class.formatter rounds numbers if they are numbers', function() {
|
|||
})
|
||||
|
||||
test('#class.formatter gives the value to the formatter if submission.grade isnt a parseable number', function() {
|
||||
sandbox.stub(SubmissionCell.prototype, 'cellWrapper')
|
||||
sandbox
|
||||
.stub(SubmissionCell.prototype, 'cellWrapper')
|
||||
.withArgs('happy')
|
||||
.returns('ok')
|
||||
const formattedResponse = SubmissionCell.formatter(0, 0, {grade: 'happy'}, {}, {})
|
||||
|
@ -96,7 +98,8 @@ test('#class.formatter gives the value to the formatter if submission.grade isnt
|
|||
})
|
||||
|
||||
test('#class.formatter adds a percent symbol for assignments with a percent grading_type', function() {
|
||||
sandbox.stub(SubmissionCell.prototype, 'cellWrapper')
|
||||
sandbox
|
||||
.stub(SubmissionCell.prototype, 'cellWrapper')
|
||||
.withArgs('73%')
|
||||
.returns('ok')
|
||||
const formattedResponse = SubmissionCell.formatter(
|
||||
|
@ -218,7 +221,8 @@ test("#class.formatter, isConcluded doesn't have grayed-out", () => {
|
|||
})
|
||||
|
||||
test('#letter_grade.formatter, shows EX when submission is excused', function() {
|
||||
sandbox.stub(SubmissionCell.prototype, 'cellWrapper')
|
||||
sandbox
|
||||
.stub(SubmissionCell.prototype, 'cellWrapper')
|
||||
.withArgs('EX')
|
||||
.returns('ok')
|
||||
const formattedResponse = SubmissionCell.letter_grade.formatter(0, 0, {excused: true}, {}, {})
|
||||
|
@ -226,7 +230,8 @@ test('#letter_grade.formatter, shows EX when submission is excused', function()
|
|||
})
|
||||
|
||||
test('#letter_grade.formatter, shows the score and letter grade', function() {
|
||||
sandbox.stub(SubmissionCell.prototype, 'cellWrapper')
|
||||
sandbox
|
||||
.stub(SubmissionCell.prototype, 'cellWrapper')
|
||||
.withArgs("F<span class='letter-grade-points'>0</span>")
|
||||
.returns('ok')
|
||||
const formattedResponse = SubmissionCell.letter_grade.formatter(
|
||||
|
@ -243,7 +248,8 @@ test('#letter_grade.formatter, shows the score and letter grade', function() {
|
|||
})
|
||||
|
||||
test('#letter_grade.formatter, shows the letter grade', function() {
|
||||
sandbox.stub(SubmissionCell.prototype, 'cellWrapper')
|
||||
sandbox
|
||||
.stub(SubmissionCell.prototype, 'cellWrapper')
|
||||
.withArgs('B')
|
||||
.returns('ok')
|
||||
const formattedResponse = SubmissionCell.letter_grade.formatter(0, 0, {grade: 'B'}, {}, {})
|
||||
|
@ -574,7 +580,10 @@ QUnit.module('SubmissionCell#classesBasedOnSubmission', () => {
|
|||
|
||||
test('does not return moderated if anonymize_students is set on the assignment', () => {
|
||||
const assignment = {anonymize_students: true}
|
||||
strictEqual(SubmissionCell.classesBasedOnSubmission({}, assignment).includes('moderated'), false)
|
||||
strictEqual(
|
||||
SubmissionCell.classesBasedOnSubmission({}, assignment).includes('moderated'),
|
||||
false
|
||||
)
|
||||
})
|
||||
|
||||
test('returns muted when muted is set on the assignment', () => {
|
||||
|
|
|
@ -88,12 +88,16 @@ test('speed_grader_enabled as false does not set speedgrader url', function() {
|
|||
|
||||
test('speedgrader url quotes the student id', function() {
|
||||
// Supply a value for context_url so we have a well-formed speedGraderUrl
|
||||
this.options.context_url = 'http://localhost';
|
||||
const submissionDetailsDialog = new SubmissionDetailsDialog(this.assignment, this.user, this.options);
|
||||
this.options.context_url = 'http://localhost'
|
||||
const submissionDetailsDialog = new SubmissionDetailsDialog(
|
||||
this.assignment,
|
||||
this.user,
|
||||
this.options
|
||||
)
|
||||
|
||||
const urlObject = new URL(submissionDetailsDialog.submission.speedGraderUrl);
|
||||
strictEqual(decodeURI(urlObject.hash), '#{"student_id":"1"}');
|
||||
submissionDetailsDialog.dialog.dialog('destroy');
|
||||
const urlObject = new URL(submissionDetailsDialog.submission.speedGraderUrl)
|
||||
strictEqual(decodeURI(urlObject.hash), '#{"student_id":"1"}')
|
||||
submissionDetailsDialog.dialog.dialog('destroy')
|
||||
})
|
||||
|
||||
test('lateness correctly passes through to the template', function() {
|
||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -16,12 +16,12 @@
|
|||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import _ from 'underscore';
|
||||
import timezone from 'timezone';
|
||||
import { scopeToUser, updateWithSubmissions } from 'jsx/gradebook/EffectiveDueDates';
|
||||
import _ from 'underscore'
|
||||
import timezone from 'timezone'
|
||||
import {scopeToUser, updateWithSubmissions} from 'jsx/gradebook/EffectiveDueDates'
|
||||
|
||||
QUnit.module('EffectiveDueDates', function () {
|
||||
QUnit.module('.scopeToUser', function () {
|
||||
QUnit.module('EffectiveDueDates', function() {
|
||||
QUnit.module('.scopeToUser', function() {
|
||||
const exampleDueDatesData = {
|
||||
201: {
|
||||
101: {
|
||||
|
@ -42,38 +42,42 @@ QUnit.module('EffectiveDueDates', function () {
|
|||
in_closed_grading_period: false
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
test('returns a map with effective due dates keyed to assignment ids', () => {
|
||||
const scopedDueDates = scopeToUser(exampleDueDatesData, '101');
|
||||
deepEqual(_.keys(scopedDueDates).sort(), ['201', '202']);
|
||||
deepEqual(_.keys(scopedDueDates[201]).sort(), ['due_at', 'grading_period_id', 'in_closed_grading_period']);
|
||||
});
|
||||
const scopedDueDates = scopeToUser(exampleDueDatesData, '101')
|
||||
deepEqual(_.keys(scopedDueDates).sort(), ['201', '202'])
|
||||
deepEqual(_.keys(scopedDueDates[201]).sort(), [
|
||||
'due_at',
|
||||
'grading_period_id',
|
||||
'in_closed_grading_period'
|
||||
])
|
||||
})
|
||||
|
||||
test('includes all effective due dates for the given user', () => {
|
||||
const scopedDueDates = scopeToUser(exampleDueDatesData, '101');
|
||||
equal(scopedDueDates[201].due_at, '2015-05-04T12:00:00Z');
|
||||
equal(scopedDueDates[201].grading_period_id, '701');
|
||||
equal(scopedDueDates[201].in_closed_grading_period, true);
|
||||
equal(scopedDueDates[202].due_at, '2015-06-04T12:00:00Z');
|
||||
equal(scopedDueDates[202].grading_period_id, '702');
|
||||
equal(scopedDueDates[202].in_closed_grading_period, false);
|
||||
});
|
||||
const scopedDueDates = scopeToUser(exampleDueDatesData, '101')
|
||||
equal(scopedDueDates[201].due_at, '2015-05-04T12:00:00Z')
|
||||
equal(scopedDueDates[201].grading_period_id, '701')
|
||||
equal(scopedDueDates[201].in_closed_grading_period, true)
|
||||
equal(scopedDueDates[202].due_at, '2015-06-04T12:00:00Z')
|
||||
equal(scopedDueDates[202].grading_period_id, '702')
|
||||
equal(scopedDueDates[202].in_closed_grading_period, false)
|
||||
})
|
||||
|
||||
test('excludes assignments not assigned to the given user', () => {
|
||||
const scopedDueDates = scopeToUser(exampleDueDatesData, '102');
|
||||
deepEqual(_.keys(scopedDueDates), ['201']);
|
||||
equal(scopedDueDates[201].due_at, '2015-05-05T12:00:00Z');
|
||||
equal(scopedDueDates[201].grading_period_id, '701');
|
||||
equal(scopedDueDates[201].in_closed_grading_period, true);
|
||||
});
|
||||
});
|
||||
const scopedDueDates = scopeToUser(exampleDueDatesData, '102')
|
||||
deepEqual(_.keys(scopedDueDates), ['201'])
|
||||
equal(scopedDueDates[201].due_at, '2015-05-05T12:00:00Z')
|
||||
equal(scopedDueDates[201].grading_period_id, '701')
|
||||
equal(scopedDueDates[201].in_closed_grading_period, true)
|
||||
})
|
||||
})
|
||||
|
||||
QUnit.module('.updateWithSubmissions', function (hooks) {
|
||||
let effectiveDueDates;
|
||||
let submissions;
|
||||
QUnit.module('.updateWithSubmissions', function(hooks) {
|
||||
let effectiveDueDates
|
||||
let submissions
|
||||
|
||||
hooks.beforeEach(function () {
|
||||
hooks.beforeEach(function() {
|
||||
effectiveDueDates = {
|
||||
2301: {
|
||||
1101: {
|
||||
|
@ -94,13 +98,13 @@ QUnit.module('EffectiveDueDates', function () {
|
|||
in_closed_grading_period: false
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
submissions = [
|
||||
{ assignment_id: '2301', user_id: '1101', cached_due_date: '2015-02-01T12:00:00Z' },
|
||||
{ assignment_id: '2302', user_id: '1101', cached_due_date: '2015-04-01T12:00:00Z' },
|
||||
{ assignment_id: '2302', user_id: '1102', cached_due_date: '2015-04-02T12:00:00Z' }
|
||||
];
|
||||
});
|
||||
{assignment_id: '2301', user_id: '1101', cached_due_date: '2015-02-01T12:00:00Z'},
|
||||
{assignment_id: '2302', user_id: '1101', cached_due_date: '2015-04-01T12:00:00Z'},
|
||||
{assignment_id: '2302', user_id: '1102', cached_due_date: '2015-04-02T12:00:00Z'}
|
||||
]
|
||||
})
|
||||
|
||||
const gradingPeriods = [
|
||||
{
|
||||
|
@ -124,77 +128,77 @@ QUnit.module('EffectiveDueDates', function () {
|
|||
isClosed: false,
|
||||
startDate: timezone.parse('2015-03-01T12:00:00Z')
|
||||
}
|
||||
];
|
||||
]
|
||||
|
||||
test('sets the due_at for each effective due date', function () {
|
||||
effectiveDueDates = {};
|
||||
updateWithSubmissions(effectiveDueDates, submissions, gradingPeriods);
|
||||
equal(effectiveDueDates[2301][1101].due_at, '2015-02-01T12:00:00Z');
|
||||
equal(effectiveDueDates[2302][1101].due_at, '2015-04-01T12:00:00Z');
|
||||
equal(effectiveDueDates[2302][1102].due_at, '2015-04-02T12:00:00Z');
|
||||
});
|
||||
test('sets the due_at for each effective due date', function() {
|
||||
effectiveDueDates = {}
|
||||
updateWithSubmissions(effectiveDueDates, submissions, gradingPeriods)
|
||||
equal(effectiveDueDates[2301][1101].due_at, '2015-02-01T12:00:00Z')
|
||||
equal(effectiveDueDates[2302][1101].due_at, '2015-04-01T12:00:00Z')
|
||||
equal(effectiveDueDates[2302][1102].due_at, '2015-04-02T12:00:00Z')
|
||||
})
|
||||
|
||||
test('sets the grading_period_id for each effective due date', function () {
|
||||
effectiveDueDates = {};
|
||||
updateWithSubmissions(effectiveDueDates, submissions, gradingPeriods);
|
||||
strictEqual(effectiveDueDates[2301][1101].grading_period_id, '1401');
|
||||
strictEqual(effectiveDueDates[2302][1101].grading_period_id, '1402');
|
||||
strictEqual(effectiveDueDates[2302][1102].grading_period_id, '1402');
|
||||
});
|
||||
test('sets the grading_period_id for each effective due date', function() {
|
||||
effectiveDueDates = {}
|
||||
updateWithSubmissions(effectiveDueDates, submissions, gradingPeriods)
|
||||
strictEqual(effectiveDueDates[2301][1101].grading_period_id, '1401')
|
||||
strictEqual(effectiveDueDates[2302][1101].grading_period_id, '1402')
|
||||
strictEqual(effectiveDueDates[2302][1102].grading_period_id, '1402')
|
||||
})
|
||||
|
||||
test('sets in_closed_grading_period for each effective due date', function () {
|
||||
effectiveDueDates = {};
|
||||
updateWithSubmissions(effectiveDueDates, submissions, gradingPeriods);
|
||||
strictEqual(effectiveDueDates[2301][1101].in_closed_grading_period, true);
|
||||
strictEqual(effectiveDueDates[2302][1101].in_closed_grading_period, false);
|
||||
strictEqual(effectiveDueDates[2302][1102].in_closed_grading_period, false);
|
||||
});
|
||||
test('sets in_closed_grading_period for each effective due date', function() {
|
||||
effectiveDueDates = {}
|
||||
updateWithSubmissions(effectiveDueDates, submissions, gradingPeriods)
|
||||
strictEqual(effectiveDueDates[2301][1101].in_closed_grading_period, true)
|
||||
strictEqual(effectiveDueDates[2302][1101].in_closed_grading_period, false)
|
||||
strictEqual(effectiveDueDates[2302][1102].in_closed_grading_period, false)
|
||||
})
|
||||
|
||||
test('updates existing effective due dates for students', function () {
|
||||
updateWithSubmissions(effectiveDueDates, submissions, gradingPeriods);
|
||||
equal(effectiveDueDates[2301][1101].due_at, '2015-02-01T12:00:00Z');
|
||||
strictEqual(effectiveDueDates[2301][1101].grading_period_id, '1401');
|
||||
strictEqual(effectiveDueDates[2301][1101].in_closed_grading_period, true);
|
||||
});
|
||||
test('updates existing effective due dates for students', function() {
|
||||
updateWithSubmissions(effectiveDueDates, submissions, gradingPeriods)
|
||||
equal(effectiveDueDates[2301][1101].due_at, '2015-02-01T12:00:00Z')
|
||||
strictEqual(effectiveDueDates[2301][1101].grading_period_id, '1401')
|
||||
strictEqual(effectiveDueDates[2301][1101].in_closed_grading_period, true)
|
||||
})
|
||||
|
||||
test('preserves effective due dates for unrelated students', function () {
|
||||
updateWithSubmissions(effectiveDueDates, submissions, gradingPeriods);
|
||||
equal(effectiveDueDates[2301][1103].due_at, '2015-02-02T12:00:00Z');
|
||||
strictEqual(effectiveDueDates[2301][1103].grading_period_id, '1401');
|
||||
strictEqual(effectiveDueDates[2301][1103].in_closed_grading_period, true);
|
||||
});
|
||||
test('preserves effective due dates for unrelated students', function() {
|
||||
updateWithSubmissions(effectiveDueDates, submissions, gradingPeriods)
|
||||
equal(effectiveDueDates[2301][1103].due_at, '2015-02-02T12:00:00Z')
|
||||
strictEqual(effectiveDueDates[2301][1103].grading_period_id, '1401')
|
||||
strictEqual(effectiveDueDates[2301][1103].in_closed_grading_period, true)
|
||||
})
|
||||
|
||||
test('preserves effective due dates for unrelated assignments', function () {
|
||||
updateWithSubmissions(effectiveDueDates, submissions, gradingPeriods);
|
||||
equal(effectiveDueDates[2303][1101].due_at, '2015-04-02T12:00:00Z');
|
||||
strictEqual(effectiveDueDates[2303][1101].grading_period_id, '1402');
|
||||
strictEqual(effectiveDueDates[2303][1101].in_closed_grading_period, false);
|
||||
});
|
||||
test('preserves effective due dates for unrelated assignments', function() {
|
||||
updateWithSubmissions(effectiveDueDates, submissions, gradingPeriods)
|
||||
equal(effectiveDueDates[2303][1101].due_at, '2015-04-02T12:00:00Z')
|
||||
strictEqual(effectiveDueDates[2303][1101].grading_period_id, '1402')
|
||||
strictEqual(effectiveDueDates[2303][1101].in_closed_grading_period, false)
|
||||
})
|
||||
|
||||
test('uses the last grading period when the cached due date is null', function () {
|
||||
effectiveDueDates = {};
|
||||
submissions[0].cached_due_date = null;
|
||||
updateWithSubmissions(effectiveDueDates, submissions, gradingPeriods);
|
||||
strictEqual(effectiveDueDates[2301][1101].due_at, null);
|
||||
strictEqual(effectiveDueDates[2301][1101].grading_period_id, '1403');
|
||||
strictEqual(effectiveDueDates[2301][1101].in_closed_grading_period, false);
|
||||
});
|
||||
test('uses the last grading period when the cached due date is null', function() {
|
||||
effectiveDueDates = {}
|
||||
submissions[0].cached_due_date = null
|
||||
updateWithSubmissions(effectiveDueDates, submissions, gradingPeriods)
|
||||
strictEqual(effectiveDueDates[2301][1101].due_at, null)
|
||||
strictEqual(effectiveDueDates[2301][1101].grading_period_id, '1403')
|
||||
strictEqual(effectiveDueDates[2301][1101].in_closed_grading_period, false)
|
||||
})
|
||||
|
||||
test('uses no grading period when the cached due date is outside any grading period', function () {
|
||||
effectiveDueDates = {};
|
||||
submissions[0].cached_due_date = '2015-07-02T12:00:00Z';
|
||||
updateWithSubmissions(effectiveDueDates, submissions, gradingPeriods);
|
||||
strictEqual(effectiveDueDates[2301][1101].due_at, '2015-07-02T12:00:00Z');
|
||||
strictEqual(effectiveDueDates[2301][1101].grading_period_id, null);
|
||||
strictEqual(effectiveDueDates[2301][1101].in_closed_grading_period, false);
|
||||
});
|
||||
test('uses no grading period when the cached due date is outside any grading period', function() {
|
||||
effectiveDueDates = {}
|
||||
submissions[0].cached_due_date = '2015-07-02T12:00:00Z'
|
||||
updateWithSubmissions(effectiveDueDates, submissions, gradingPeriods)
|
||||
strictEqual(effectiveDueDates[2301][1101].due_at, '2015-07-02T12:00:00Z')
|
||||
strictEqual(effectiveDueDates[2301][1101].grading_period_id, null)
|
||||
strictEqual(effectiveDueDates[2301][1101].in_closed_grading_period, false)
|
||||
})
|
||||
|
||||
test('uses no grading period when not given any grading periods', function () {
|
||||
effectiveDueDates = {};
|
||||
updateWithSubmissions(effectiveDueDates, submissions, undefined);
|
||||
strictEqual(effectiveDueDates[2301][1101].due_at, '2015-02-01T12:00:00Z');
|
||||
strictEqual(effectiveDueDates[2301][1101].grading_period_id, null);
|
||||
strictEqual(effectiveDueDates[2301][1101].in_closed_grading_period, false);
|
||||
});
|
||||
});
|
||||
});
|
||||
test('uses no grading period when not given any grading periods', function() {
|
||||
effectiveDueDates = {}
|
||||
updateWithSubmissions(effectiveDueDates, submissions, undefined)
|
||||
strictEqual(effectiveDueDates[2301][1101].due_at, '2015-02-01T12:00:00Z')
|
||||
strictEqual(effectiveDueDates[2301][1101].grading_period_id, null)
|
||||
strictEqual(effectiveDueDates[2301][1101].in_closed_grading_period, false)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import fakeENV from 'helpers/fakeENV'
|
||||
import fakeENV from 'helpers/fakeENV'
|
||||
import PostGradesFrameDialog from 'compiled/gradebook/PostGradesFrameDialog'
|
||||
|
||||
let dialog
|
||||
|
@ -25,7 +25,7 @@ let iframe
|
|||
let info
|
||||
|
||||
QUnit.module('PostGradesFrameDialog screenreader only content', {
|
||||
setup () {
|
||||
setup() {
|
||||
fakeENV.setup({LTI_LAUNCH_FRAME_ALLOWANCES: ['midi', 'media']})
|
||||
dialog = new PostGradesFrameDialog({})
|
||||
dialog.open()
|
||||
|
@ -33,7 +33,7 @@ QUnit.module('PostGradesFrameDialog screenreader only content', {
|
|||
iframe = el.find('iframe')
|
||||
},
|
||||
|
||||
teardown () {
|
||||
teardown() {
|
||||
dialog.close()
|
||||
dialog.$dialog.remove()
|
||||
fakeENV.teardown()
|
||||
|
@ -71,7 +71,12 @@ test('hides ending info alert and removes class from iframe', () => {
|
|||
})
|
||||
|
||||
test("doesn't show infos or add border to iframe by default", () => {
|
||||
equal(el.find('.before_external_content_info_alert.screenreader-only, .after_external_content_info_alert.screenreader-only').length, 2)
|
||||
equal(
|
||||
el.find(
|
||||
'.before_external_content_info_alert.screenreader-only, .after_external_content_info_alert.screenreader-only'
|
||||
).length,
|
||||
2
|
||||
)
|
||||
notOk(iframe.hasClass('info_alert_outline'))
|
||||
})
|
||||
|
||||
|
|
|
@ -16,24 +16,24 @@
|
|||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import Gradebook from 'compiled/gradebook/Gradebook';
|
||||
import _ from 'underscore';
|
||||
import fakeENV from 'helpers/fakeENV';
|
||||
import UserSettings from 'compiled/userSettings';
|
||||
import $ from 'jquery';
|
||||
import Gradebook from 'compiled/gradebook/Gradebook'
|
||||
import _ from 'underscore'
|
||||
import fakeENV from 'helpers/fakeENV'
|
||||
import UserSettings from 'compiled/userSettings'
|
||||
import $ from 'jquery'
|
||||
|
||||
function createGradebook (opts = {}) {
|
||||
return new Gradebook({ settings: {}, sections: {}, ...opts });
|
||||
function createGradebook(opts = {}) {
|
||||
return new Gradebook({settings: {}, sections: {}, ...opts})
|
||||
}
|
||||
|
||||
QUnit.module('addRow', {
|
||||
setup () {
|
||||
setup() {
|
||||
fakeENV.setup({
|
||||
GRADEBOOK_OPTIONS: { context_id: 1 },
|
||||
});
|
||||
GRADEBOOK_OPTIONS: {context_id: 1}
|
||||
})
|
||||
},
|
||||
teardown: () => fakeENV.teardown(),
|
||||
});
|
||||
teardown: () => fakeENV.teardown()
|
||||
})
|
||||
|
||||
test("doesn't add filtered out users", () => {
|
||||
const gb = {
|
||||
|
@ -43,81 +43,81 @@ test("doesn't add filtered out users", () => {
|
|||
rows: [],
|
||||
sectionToShow: '2', // this is the filter
|
||||
...Gradebook.prototype
|
||||
};
|
||||
}
|
||||
|
||||
const student1 = {
|
||||
enrollments: [{grades: {}}],
|
||||
sections: ['1'],
|
||||
name: 'student',
|
||||
};
|
||||
const student2 = {...student1, sections: ['2']};
|
||||
const student3 = {...student1, sections: ['2']};
|
||||
[student1, student2, student3].forEach(s => gb.addRow(s));
|
||||
name: 'student'
|
||||
}
|
||||
const student2 = {...student1, sections: ['2']}
|
||||
const student3 = {...student1, sections: ['2']}
|
||||
;[student1, student2, student3].forEach(s => gb.addRow(s))
|
||||
|
||||
ok(student1.row == null, 'filtered out students get no row number');
|
||||
ok(student2.row === 0, 'other students do get a row number');
|
||||
ok(student3.row === 1, 'row number increments');
|
||||
ok(_.isEqual(gb.rows, [student2, student3]));
|
||||
});
|
||||
ok(student1.row == null, 'filtered out students get no row number')
|
||||
ok(student2.row === 0, 'other students do get a row number')
|
||||
ok(student3.row === 1, 'row number increments')
|
||||
ok(_.isEqual(gb.rows, [student2, student3]))
|
||||
})
|
||||
|
||||
QUnit.module('Gradebook#groupTotalFormatter', {
|
||||
setup () {
|
||||
fakeENV.setup();
|
||||
setup() {
|
||||
fakeENV.setup()
|
||||
},
|
||||
teardown () {
|
||||
fakeENV.teardown();
|
||||
},
|
||||
});
|
||||
teardown() {
|
||||
fakeENV.teardown()
|
||||
}
|
||||
})
|
||||
|
||||
test('calculates percentage from given score and possible values', function () {
|
||||
const gradebook = new Gradebook({ settings: {}, sections: {} });
|
||||
const groupTotalOutput = gradebook.groupTotalFormatter(0, 0, { score: 9, possible: 10 }, {});
|
||||
ok(groupTotalOutput.includes('9 / 10'));
|
||||
ok(groupTotalOutput.includes('90%'));
|
||||
});
|
||||
test('calculates percentage from given score and possible values', function() {
|
||||
const gradebook = new Gradebook({settings: {}, sections: {}})
|
||||
const groupTotalOutput = gradebook.groupTotalFormatter(0, 0, {score: 9, possible: 10}, {})
|
||||
ok(groupTotalOutput.includes('9 / 10'))
|
||||
ok(groupTotalOutput.includes('90%'))
|
||||
})
|
||||
|
||||
test('displays percentage as "-" when group total score is positive infinity', function () {
|
||||
const gradebook = new Gradebook({ settings: {}, sections: {} });
|
||||
sandbox.stub(gradebook, 'calculateAndRoundGroupTotalScore').returns(Number.POSITIVE_INFINITY);
|
||||
const groupTotalOutput = gradebook.groupTotalFormatter(0, 0, { score: 9, possible: 0 }, {});
|
||||
ok(groupTotalOutput.includes('9 / 0'));
|
||||
ok(groupTotalOutput.includes('-'));
|
||||
});
|
||||
test('displays percentage as "-" when group total score is positive infinity', function() {
|
||||
const gradebook = new Gradebook({settings: {}, sections: {}})
|
||||
sandbox.stub(gradebook, 'calculateAndRoundGroupTotalScore').returns(Number.POSITIVE_INFINITY)
|
||||
const groupTotalOutput = gradebook.groupTotalFormatter(0, 0, {score: 9, possible: 0}, {})
|
||||
ok(groupTotalOutput.includes('9 / 0'))
|
||||
ok(groupTotalOutput.includes('-'))
|
||||
})
|
||||
|
||||
test('displays percentage as "-" when group total score is negative infinity', function () {
|
||||
const gradebook = new Gradebook({ settings: {}, sections: {} });
|
||||
sandbox.stub(gradebook, 'calculateAndRoundGroupTotalScore').returns(Number.NEGATIVE_INFINITY);
|
||||
const groupTotalOutput = gradebook.groupTotalFormatter(0, 0, { score: 9, possible: 0 }, {});
|
||||
ok(groupTotalOutput.includes('9 / 0'));
|
||||
ok(groupTotalOutput.includes('-'));
|
||||
});
|
||||
test('displays percentage as "-" when group total score is negative infinity', function() {
|
||||
const gradebook = new Gradebook({settings: {}, sections: {}})
|
||||
sandbox.stub(gradebook, 'calculateAndRoundGroupTotalScore').returns(Number.NEGATIVE_INFINITY)
|
||||
const groupTotalOutput = gradebook.groupTotalFormatter(0, 0, {score: 9, possible: 0}, {})
|
||||
ok(groupTotalOutput.includes('9 / 0'))
|
||||
ok(groupTotalOutput.includes('-'))
|
||||
})
|
||||
|
||||
test('displays percentage as "-" when group total score is not a number', function () {
|
||||
const gradebook = new Gradebook({ settings: {}, sections: {} });
|
||||
sandbox.stub(gradebook, 'calculateAndRoundGroupTotalScore').returns(NaN);
|
||||
const groupTotalOutput = gradebook.groupTotalFormatter(0, 0, { score: 9, possible: 0 }, {});
|
||||
ok(groupTotalOutput.includes('9 / 0'));
|
||||
ok(groupTotalOutput.includes('-'));
|
||||
});
|
||||
test('displays percentage as "-" when group total score is not a number', function() {
|
||||
const gradebook = new Gradebook({settings: {}, sections: {}})
|
||||
sandbox.stub(gradebook, 'calculateAndRoundGroupTotalScore').returns(NaN)
|
||||
const groupTotalOutput = gradebook.groupTotalFormatter(0, 0, {score: 9, possible: 0}, {})
|
||||
ok(groupTotalOutput.includes('9 / 0'))
|
||||
ok(groupTotalOutput.includes('-'))
|
||||
})
|
||||
|
||||
QUnit.module('Gradebook#getFrozenColumnCount');
|
||||
QUnit.module('Gradebook#getFrozenColumnCount')
|
||||
|
||||
test('returns number of columns in frozen section', function () {
|
||||
const gradebook = new Gradebook({ settings: {}, sections: {} });
|
||||
gradebook.parentColumns = [{ id: 'student' }, { id: 'secondary_identifier' }];
|
||||
gradebook.customColumns = [{ id: 'custom_col_1' }];
|
||||
equal(gradebook.getFrozenColumnCount(), 3);
|
||||
});
|
||||
test('returns number of columns in frozen section', function() {
|
||||
const gradebook = new Gradebook({settings: {}, sections: {}})
|
||||
gradebook.parentColumns = [{id: 'student'}, {id: 'secondary_identifier'}]
|
||||
gradebook.customColumns = [{id: 'custom_col_1'}]
|
||||
equal(gradebook.getFrozenColumnCount(), 3)
|
||||
})
|
||||
|
||||
QUnit.module('Gradebook#switchTotalDisplay', {
|
||||
setupThis ({ showTotalGradeAsPoints = true } = {}) {
|
||||
setupThis({showTotalGradeAsPoints = true} = {}) {
|
||||
return {
|
||||
options: {
|
||||
show_total_grade_as_points: showTotalGradeAsPoints,
|
||||
setting_update_url: 'http://settingUpdateUrl'
|
||||
},
|
||||
displayPointTotals () {
|
||||
return true;
|
||||
displayPointTotals() {
|
||||
return true
|
||||
},
|
||||
grid: {
|
||||
invalidate: sinon.stub()
|
||||
|
@ -128,65 +128,65 @@ QUnit.module('Gradebook#switchTotalDisplay', {
|
|||
}
|
||||
},
|
||||
|
||||
setup () {
|
||||
sandbox.stub($, 'ajaxJSON');
|
||||
this.switchTotalDisplay = Gradebook.prototype.switchTotalDisplay;
|
||||
setup() {
|
||||
sandbox.stub($, 'ajaxJSON')
|
||||
this.switchTotalDisplay = Gradebook.prototype.switchTotalDisplay
|
||||
},
|
||||
|
||||
teardown () {
|
||||
UserSettings.contextRemove('warned_about_totals_display');
|
||||
teardown() {
|
||||
UserSettings.contextRemove('warned_about_totals_display')
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
test('sets the warned_about_totals_display setting when called with true', function () {
|
||||
notOk(UserSettings.contextGet('warned_about_totals_display'));
|
||||
test('sets the warned_about_totals_display setting when called with true', function() {
|
||||
notOk(UserSettings.contextGet('warned_about_totals_display'))
|
||||
|
||||
const self = this.setupThis();
|
||||
this.switchTotalDisplay.call(self, { dontWarnAgain: true });
|
||||
const self = this.setupThis()
|
||||
this.switchTotalDisplay.call(self, {dontWarnAgain: true})
|
||||
|
||||
ok(UserSettings.contextGet('warned_about_totals_display'));
|
||||
});
|
||||
ok(UserSettings.contextGet('warned_about_totals_display'))
|
||||
})
|
||||
|
||||
test('flips the show_total_grade_as_points property', function () {
|
||||
const self = this.setupThis();
|
||||
this.switchTotalDisplay.call(self, { dontWarnAgain: false });
|
||||
test('flips the show_total_grade_as_points property', function() {
|
||||
const self = this.setupThis()
|
||||
this.switchTotalDisplay.call(self, {dontWarnAgain: false})
|
||||
|
||||
equal(self.options.show_total_grade_as_points, false);
|
||||
equal(self.options.show_total_grade_as_points, false)
|
||||
|
||||
this.switchTotalDisplay.call(self, { dontWarnAgain: false });
|
||||
this.switchTotalDisplay.call(self, {dontWarnAgain: false})
|
||||
|
||||
equal(self.options.show_total_grade_as_points, true);
|
||||
});
|
||||
equal(self.options.show_total_grade_as_points, true)
|
||||
})
|
||||
|
||||
test('updates the total display preferences for the current user', function () {
|
||||
const self = this.setupThis({ showTotalGradeAsPoints: false });
|
||||
this.switchTotalDisplay.call(self, { dontWarnAgain: false });
|
||||
test('updates the total display preferences for the current user', function() {
|
||||
const self = this.setupThis({showTotalGradeAsPoints: false})
|
||||
this.switchTotalDisplay.call(self, {dontWarnAgain: false})
|
||||
|
||||
equal($.ajaxJSON.callCount, 1);
|
||||
equal($.ajaxJSON.getCall(0).args[0], 'http://settingUpdateUrl');
|
||||
equal($.ajaxJSON.getCall(0).args[1], 'PUT');
|
||||
equal($.ajaxJSON.getCall(0).args[2].show_total_grade_as_points, true);
|
||||
});
|
||||
equal($.ajaxJSON.callCount, 1)
|
||||
equal($.ajaxJSON.getCall(0).args[0], 'http://settingUpdateUrl')
|
||||
equal($.ajaxJSON.getCall(0).args[1], 'PUT')
|
||||
equal($.ajaxJSON.getCall(0).args[2].show_total_grade_as_points, true)
|
||||
})
|
||||
|
||||
test('invalidates the grid so it re-renders it', function () {
|
||||
const self = this.setupThis();
|
||||
this.switchTotalDisplay.call(self, { dontWarnAgain: false });
|
||||
test('invalidates the grid so it re-renders it', function() {
|
||||
const self = this.setupThis()
|
||||
this.switchTotalDisplay.call(self, {dontWarnAgain: false})
|
||||
|
||||
equal(self.grid.invalidate.callCount, 1);
|
||||
});
|
||||
equal(self.grid.invalidate.callCount, 1)
|
||||
})
|
||||
|
||||
test('updates the total grade column header with the new value of the show_total_grade_as_points property', function () {
|
||||
const self = this.setupThis();
|
||||
this.switchTotalDisplay.call(self, false);
|
||||
test('updates the total grade column header with the new value of the show_total_grade_as_points property', function() {
|
||||
const self = this.setupThis()
|
||||
this.switchTotalDisplay.call(self, false)
|
||||
this.switchTotalDisplay.call(self, false)
|
||||
|
||||
equal(self.totalHeader.switchTotalDisplay.callCount, 2);
|
||||
equal(self.totalHeader.switchTotalDisplay.getCall(0).args[0], false);
|
||||
equal(self.totalHeader.switchTotalDisplay.getCall(1).args[0], true);
|
||||
});
|
||||
equal(self.totalHeader.switchTotalDisplay.callCount, 2)
|
||||
equal(self.totalHeader.switchTotalDisplay.getCall(0).args[0], false)
|
||||
equal(self.totalHeader.switchTotalDisplay.getCall(1).args[0], true)
|
||||
})
|
||||
|
||||
QUnit.module('Gradebook#togglePointsOrPercentTotals', {
|
||||
setupThis () {
|
||||
setupThis() {
|
||||
return {
|
||||
options: {
|
||||
show_total_grade_as_points: true,
|
||||
|
@ -196,95 +196,106 @@ QUnit.module('Gradebook#togglePointsOrPercentTotals', {
|
|||
}
|
||||
},
|
||||
|
||||
setup () {
|
||||
sandbox.stub($, 'ajaxJSON');
|
||||
this.togglePointsOrPercentTotals = Gradebook.prototype.togglePointsOrPercentTotals;
|
||||
setup() {
|
||||
sandbox.stub($, 'ajaxJSON')
|
||||
this.togglePointsOrPercentTotals = Gradebook.prototype.togglePointsOrPercentTotals
|
||||
},
|
||||
|
||||
teardown () {
|
||||
UserSettings.contextRemove('warned_about_totals_display');
|
||||
$(".ui-dialog").remove();
|
||||
teardown() {
|
||||
UserSettings.contextRemove('warned_about_totals_display')
|
||||
$('.ui-dialog').remove()
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
test('when user is ignoring warnings, immediately toggles the total grade display', function () {
|
||||
UserSettings.contextSet('warned_about_totals_display', true);
|
||||
test('when user is ignoring warnings, immediately toggles the total grade display', function() {
|
||||
UserSettings.contextSet('warned_about_totals_display', true)
|
||||
|
||||
const self = this.setupThis(true);
|
||||
const self = this.setupThis(true)
|
||||
|
||||
this.togglePointsOrPercentTotals.call(self);
|
||||
this.togglePointsOrPercentTotals.call(self)
|
||||
|
||||
equal(self.switchTotalDisplay.callCount, 1, 'toggles the total grade display');
|
||||
});
|
||||
equal(self.switchTotalDisplay.callCount, 1, 'toggles the total grade display')
|
||||
})
|
||||
|
||||
test('when user is not ignoring warnings, return a dialog', function () {
|
||||
UserSettings.contextSet('warned_about_totals_display', false);
|
||||
test('when user is not ignoring warnings, return a dialog', function() {
|
||||
UserSettings.contextSet('warned_about_totals_display', false)
|
||||
|
||||
const self = this.setupThis(true);
|
||||
const dialog = this.togglePointsOrPercentTotals.call(self);
|
||||
const self = this.setupThis(true)
|
||||
const dialog = this.togglePointsOrPercentTotals.call(self)
|
||||
|
||||
equal(dialog.constructor.name, 'GradeDisplayWarningDialog', 'returns a grade display warning dialog');
|
||||
equal(
|
||||
dialog.constructor.name,
|
||||
'GradeDisplayWarningDialog',
|
||||
'returns a grade display warning dialog'
|
||||
)
|
||||
|
||||
dialog.cancel();
|
||||
});
|
||||
dialog.cancel()
|
||||
})
|
||||
|
||||
test('when user is not ignoring warnings, the dialog has a save property which is the switchTotalDisplay function', function () {
|
||||
sandbox.stub(UserSettings, 'contextGet').withArgs('warned_about_totals_display').returns(false);
|
||||
const self = this.setupThis(true);
|
||||
const dialog = this.togglePointsOrPercentTotals.call(self);
|
||||
test('when user is not ignoring warnings, the dialog has a save property which is the switchTotalDisplay function', function() {
|
||||
sandbox
|
||||
.stub(UserSettings, 'contextGet')
|
||||
.withArgs('warned_about_totals_display')
|
||||
.returns(false)
|
||||
const self = this.setupThis(true)
|
||||
const dialog = this.togglePointsOrPercentTotals.call(self)
|
||||
|
||||
equal(dialog.options.save, self.switchTotalDisplay);
|
||||
equal(dialog.options.save, self.switchTotalDisplay)
|
||||
|
||||
dialog.cancel();
|
||||
});
|
||||
dialog.cancel()
|
||||
})
|
||||
|
||||
QUnit.module('Gradebook', (_suiteHooks) => {
|
||||
let gradebook;
|
||||
QUnit.module('Gradebook', _suiteHooks => {
|
||||
let gradebook
|
||||
|
||||
QUnit.module('#updateSubmissionsFromExternal', (hooks) => {
|
||||
QUnit.module('#updateSubmissionsFromExternal', hooks => {
|
||||
const columns = [
|
||||
{ id: 'student', type: 'student' },
|
||||
{ id: 'assignment_232', type: 'assignment' },
|
||||
{ id: 'total_grade', type: 'total_grade' },
|
||||
{ id: 'assignment_group_12', type: 'assignment' }
|
||||
];
|
||||
{id: 'student', type: 'student'},
|
||||
{id: 'assignment_232', type: 'assignment'},
|
||||
{id: 'total_grade', type: 'total_grade'},
|
||||
{id: 'assignment_group_12', type: 'assignment'}
|
||||
]
|
||||
|
||||
hooks.beforeEach(() => {
|
||||
gradebook = createGradebook();
|
||||
gradebook = createGradebook()
|
||||
|
||||
gradebook.students = {
|
||||
1101: { id: '1101', row: '1', assignment_201: {}, assignment_202: {} },
|
||||
1102: { id: '1102', row: '2', assignment_201: {} }
|
||||
};
|
||||
1101: {id: '1101', row: '1', assignment_201: {}, assignment_202: {}},
|
||||
1102: {id: '1102', row: '2', assignment_201: {}}
|
||||
}
|
||||
gradebook.assignments = []
|
||||
gradebook.submissionStateMap = {
|
||||
setSubmissionCellState () {},
|
||||
getSubmissionState () { return { locked: false } }
|
||||
};
|
||||
setSubmissionCellState() {},
|
||||
getSubmissionState() {
|
||||
return {locked: false}
|
||||
}
|
||||
}
|
||||
|
||||
sinon.stub(gradebook, 'updateAssignmentVisibilities');
|
||||
sinon.stub(gradebook, 'updateSubmission');
|
||||
sinon.stub(gradebook, 'calculateStudentGrade');
|
||||
sinon.stub(gradebook, 'updateRowTotals');
|
||||
sinon.stub(gradebook, 'updateAssignmentVisibilities')
|
||||
sinon.stub(gradebook, 'updateSubmission')
|
||||
sinon.stub(gradebook, 'calculateStudentGrade')
|
||||
sinon.stub(gradebook, 'updateRowTotals')
|
||||
|
||||
gradebook.grid = {
|
||||
getActiveCell () {},
|
||||
getColumns () { return columns },
|
||||
getActiveCell() {},
|
||||
getColumns() {
|
||||
return columns
|
||||
},
|
||||
updateCell: sinon.stub(),
|
||||
getActiveCellNode: sinon.stub(),
|
||||
};
|
||||
});
|
||||
getActiveCellNode: sinon.stub()
|
||||
}
|
||||
})
|
||||
|
||||
test('ignores submissions for students not currently loaded', () => {
|
||||
const submissions = [
|
||||
{ assignment_id: '201', user_id: '1101', score: 10, assignment_visible: true },
|
||||
{ assignment_id: '201', user_id: '1103', score: 9, assignment_visible: true },
|
||||
{ assignment_id: '201', user_id: '1102', score: 8, assignment_visible: true }
|
||||
];
|
||||
gradebook.updateSubmissionsFromExternal(submissions);
|
||||
{assignment_id: '201', user_id: '1101', score: 10, assignment_visible: true},
|
||||
{assignment_id: '201', user_id: '1103', score: 9, assignment_visible: true},
|
||||
{assignment_id: '201', user_id: '1102', score: 8, assignment_visible: true}
|
||||
]
|
||||
gradebook.updateSubmissionsFromExternal(submissions)
|
||||
|
||||
const rowsUpdated = gradebook.updateRowTotals.getCalls().map((stubCall) => stubCall.args[0]);
|
||||
deepEqual(rowsUpdated, ['1', '2']);
|
||||
});
|
||||
});
|
||||
});
|
||||
const rowsUpdated = gradebook.updateRowTotals.getCalls().map(stubCall => stubCall.args[0])
|
||||
deepEqual(rowsUpdated, ['1', '2'])
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -18,53 +18,67 @@
|
|||
|
||||
import ScoreToGradeHelper from 'jsx/gradebook/shared/helpers/ScoreToGradeHelper'
|
||||
|
||||
QUnit.module('ScoreToGradeHelper#scoreToGrade');
|
||||
QUnit.module('ScoreToGradeHelper#scoreToGrade')
|
||||
|
||||
test('formats score as empty string when score is null', function () {
|
||||
const grade = ScoreToGradeHelper.scoreToGrade(null);
|
||||
equal(grade, '');
|
||||
});
|
||||
test('formats score as empty string when score is null', function() {
|
||||
const grade = ScoreToGradeHelper.scoreToGrade(null)
|
||||
equal(grade, '')
|
||||
})
|
||||
|
||||
test('formats score as points when grading_type is "points"', function () {
|
||||
const grade = ScoreToGradeHelper.scoreToGrade(12.34, { grading_type: 'points' });
|
||||
equal(grade, '12.34');
|
||||
});
|
||||
test('formats score as points when grading_type is "points"', function() {
|
||||
const grade = ScoreToGradeHelper.scoreToGrade(12.34, {grading_type: 'points'})
|
||||
equal(grade, '12.34')
|
||||
})
|
||||
|
||||
test('formats score as percentage when grading_type is "percent"', function () {
|
||||
const grade = ScoreToGradeHelper.scoreToGrade(12.34, { grading_type: 'percent', points_possible: 50 });
|
||||
equal(grade, '24.68%');
|
||||
});
|
||||
test('formats score as percentage when grading_type is "percent"', function() {
|
||||
const grade = ScoreToGradeHelper.scoreToGrade(12.34, {
|
||||
grading_type: 'percent',
|
||||
points_possible: 50
|
||||
})
|
||||
equal(grade, '24.68%')
|
||||
})
|
||||
|
||||
test('formats score as empty string when grading_type is "percent" and assignment has no points_possible', function () {
|
||||
const grade = ScoreToGradeHelper.scoreToGrade(12.34, { grading_type: 'percent', points_possible: 0 });
|
||||
equal(grade, '');
|
||||
});
|
||||
test('formats score as empty string when grading_type is "percent" and assignment has no points_possible', function() {
|
||||
const grade = ScoreToGradeHelper.scoreToGrade(12.34, {
|
||||
grading_type: 'percent',
|
||||
points_possible: 0
|
||||
})
|
||||
equal(grade, '')
|
||||
})
|
||||
|
||||
test('formats score as "complete" when grading_type is "pass_fail" and score is nonzero"', function () {
|
||||
const grade = ScoreToGradeHelper.scoreToGrade(12.34, { grading_type: 'pass_fail' });
|
||||
equal(grade, 'complete');
|
||||
});
|
||||
test('formats score as "complete" when grading_type is "pass_fail" and score is nonzero"', function() {
|
||||
const grade = ScoreToGradeHelper.scoreToGrade(12.34, {grading_type: 'pass_fail'})
|
||||
equal(grade, 'complete')
|
||||
})
|
||||
|
||||
test('formats score as "incomplete" when grading_type is "pass_fail" and score is zero"', function () {
|
||||
const grade = ScoreToGradeHelper.scoreToGrade(0, { grading_type: 'pass_fail' });
|
||||
equal(grade, 'incomplete');
|
||||
});
|
||||
test('formats score as "incomplete" when grading_type is "pass_fail" and score is zero"', function() {
|
||||
const grade = ScoreToGradeHelper.scoreToGrade(0, {grading_type: 'pass_fail'})
|
||||
equal(grade, 'incomplete')
|
||||
})
|
||||
|
||||
test('formats score as empty string when grading_type is "letter_grade" and no gradingScheme given', function () {
|
||||
const grade = ScoreToGradeHelper.scoreToGrade(12.34, { grading_type: 'letter_grade', points_possible: 10 });
|
||||
equal(grade, '');
|
||||
});
|
||||
test('formats score as empty string when grading_type is "letter_grade" and no gradingScheme given', function() {
|
||||
const grade = ScoreToGradeHelper.scoreToGrade(12.34, {
|
||||
grading_type: 'letter_grade',
|
||||
points_possible: 10
|
||||
})
|
||||
equal(grade, '')
|
||||
})
|
||||
|
||||
test('formats score as empty string when grading_type is "letter_grade" and assignment has no points_possible', function () {
|
||||
const grade = ScoreToGradeHelper.scoreToGrade(12.34, { grading_type: 'letter_grade' }, [
|
||||
['A', 0.9], ['B', 0.8], ['C', 0.7], ['F', 0]
|
||||
]);
|
||||
equal(grade, '');
|
||||
});
|
||||
test('formats score as empty string when grading_type is "letter_grade" and assignment has no points_possible', function() {
|
||||
const grade = ScoreToGradeHelper.scoreToGrade(12.34, {grading_type: 'letter_grade'}, [
|
||||
['A', 0.9],
|
||||
['B', 0.8],
|
||||
['C', 0.7],
|
||||
['F', 0]
|
||||
])
|
||||
equal(grade, '')
|
||||
})
|
||||
|
||||
test('formats score as letter grade when grading_type is "letter_grade" and gradingScheme given', function () {
|
||||
const grade = ScoreToGradeHelper.scoreToGrade(7, { grading_type: 'letter_grade', points_possible: 10 }, [
|
||||
['A', 0.9], ['B', 0.8], ['C', 0.7], ['F', 0]
|
||||
]);
|
||||
equal(grade, 'C');
|
||||
});
|
||||
test('formats score as letter grade when grading_type is "letter_grade" and gradingScheme given', function() {
|
||||
const grade = ScoreToGradeHelper.scoreToGrade(
|
||||
7,
|
||||
{grading_type: 'letter_grade', points_possible: 10},
|
||||
[['A', 0.9], ['B', 0.8], ['C', 0.7], ['F', 0]]
|
||||
)
|
||||
equal(grade, 'C')
|
||||
})
|
||||
|
|
Loading…
Reference in New Issue