Merge branch 'dev/weighted-grading' into master
Fixes CNVS-35214 Change-Id: I55a1d4a02ed2640ad3f147e2c5534ef8f747c2f4
This commit is contained in:
commit
ec51d5843a
|
@ -3,10 +3,11 @@ define [
|
|||
'underscore'
|
||||
'i18n!grading_periods'
|
||||
'jsx/shared/helpers/dateHelper'
|
||||
'compiled/api/gradingPeriodsApi'
|
||||
'axios'
|
||||
'jsx/shared/CheatDepaginator'
|
||||
'jquery.instructure_misc_helpers'
|
||||
], ($, _, I18n, DateHelper, axios, Depaginate) ->
|
||||
], ($, _, I18n, DateHelper, gradingPeriodsApi, axios, Depaginate) ->
|
||||
listUrl = () =>
|
||||
ENV.GRADING_PERIOD_SETS_URL
|
||||
|
||||
|
@ -17,26 +18,15 @@ define [
|
|||
$.replaceTags(ENV.GRADING_PERIOD_SET_UPDATE_URL, 'id', id)
|
||||
|
||||
serializeSet = (set) =>
|
||||
grading_period_set: { title: set.title },
|
||||
grading_period_set: { title: set.title, weighted: set.weighted },
|
||||
enrollment_term_ids: set.enrollmentTermIDs
|
||||
|
||||
deserializePeriods = (periods) =>
|
||||
_.map periods, (period) ->
|
||||
{
|
||||
id: period.id.toString()
|
||||
title: period.title
|
||||
startDate: new Date(period.start_date)
|
||||
endDate: new Date(period.end_date)
|
||||
# TODO: After the close_date data fixup has run, this can become:
|
||||
# `closeDate: new Date(period.close_date)`
|
||||
closeDate: new Date(period.close_date || period.end_date)
|
||||
}
|
||||
|
||||
baseDeserializeSet = (set) ->
|
||||
{
|
||||
id: set.id.toString()
|
||||
title: gradingPeriodSetTitle(set)
|
||||
gradingPeriods: deserializePeriods(set.grading_periods)
|
||||
weighted: set.weighted || false
|
||||
gradingPeriods: gradingPeriodsApi.deserializePeriods(set.grading_periods)
|
||||
permissions: set.permissions
|
||||
createdAt: new Date(set.created_at)
|
||||
}
|
||||
|
@ -46,7 +36,7 @@ define [
|
|||
set.title.trim()
|
||||
else
|
||||
createdAt = DateHelper.formatDateForDisplay(new Date(set.created_at))
|
||||
I18n.t("Set created %{createdAt}", { createdAt: createdAt });
|
||||
I18n.t('Set created %{createdAt}', { createdAt })
|
||||
|
||||
deserializeSet = (set) ->
|
||||
newSet = baseDeserializeSet(set)
|
||||
|
@ -57,29 +47,23 @@ define [
|
|||
_.flatten _.map setGroups, (group) ->
|
||||
_.map group.grading_period_sets, (set) -> baseDeserializeSet(set)
|
||||
|
||||
deserializeSet: deserializeSet
|
||||
|
||||
list: () ->
|
||||
promise = new Promise (resolve, reject) =>
|
||||
Depaginate(listUrl())
|
||||
.then (response) ->
|
||||
resolve(deserializeSets(response))
|
||||
.fail (error) ->
|
||||
reject(error)
|
||||
.then (response) ->
|
||||
resolve(deserializeSets(response))
|
||||
.fail (error) ->
|
||||
reject(error)
|
||||
promise
|
||||
|
||||
create: (set) ->
|
||||
promise = new Promise (resolve, reject) =>
|
||||
axios.post(createUrl(), serializeSet(set))
|
||||
.then (response) ->
|
||||
resolve(deserializeSet(response.data.grading_period_set))
|
||||
.catch (error) ->
|
||||
reject(error)
|
||||
promise
|
||||
axios.post(createUrl(), serializeSet(set))
|
||||
.then (response) ->
|
||||
deserializeSet(response.data.grading_period_set)
|
||||
|
||||
update: (set) ->
|
||||
promise = new Promise (resolve, reject) =>
|
||||
axios.patch(updateUrl(set.id), serializeSet(set))
|
||||
.then (response) ->
|
||||
resolve(set)
|
||||
.catch (error) ->
|
||||
reject(error)
|
||||
promise
|
||||
axios.patch(updateUrl(set.id), serializeSet(set))
|
||||
.then (response) ->
|
||||
set
|
||||
|
|
|
@ -15,6 +15,7 @@ define [
|
|||
start_date: period.startDate
|
||||
end_date: period.endDate
|
||||
close_date: period.closeDate
|
||||
weight: period.weight
|
||||
}
|
||||
grading_periods: serialized
|
||||
|
||||
|
@ -25,11 +26,10 @@ define [
|
|||
title: period.title
|
||||
startDate: new Date(period.start_date)
|
||||
endDate: new Date(period.end_date)
|
||||
# TODO: After the close_date data fixup has run, this can become:
|
||||
# `closeDate: new Date(period.close_date)`
|
||||
closeDate: new Date(period.close_date || period.end_date)
|
||||
closeDate: new Date(period.close_date)
|
||||
isLast: period.is_last
|
||||
isClosed: period.is_closed
|
||||
weight: period.weight
|
||||
}
|
||||
|
||||
batchUpdate: (setId, periods) ->
|
||||
|
|
|
@ -7,7 +7,6 @@ require [
|
|||
|
||||
ReactDOM.render(
|
||||
TabContainerFactory(
|
||||
multipleGradingPeriodsEnabled: ENV.MULTIPLE_GRADING_PERIODS
|
||||
readOnly: ENV.GRADING_PERIODS_READ_ONLY
|
||||
urls:
|
||||
enrollmentTermsURL: ENV.ENROLLMENT_TERMS_URL
|
||||
|
|
|
@ -95,7 +95,7 @@ require [
|
|||
|
||||
# kick it all off
|
||||
assignmentGroups.fetch(reset: true).then ->
|
||||
app.filterResults() if ENV.MULTIPLE_GRADING_PERIODS_ENABLED
|
||||
app.filterResults() if ENV.HAS_GRADING_PERIODS
|
||||
if ENV.PERMISSIONS.manage
|
||||
assignmentGroups.loadModuleNames()
|
||||
else
|
||||
|
|
|
@ -4,8 +4,7 @@ require [
|
|||
'jsx/grading/CourseTabContainer'
|
||||
], (React, ReactDOM, CourseTabContainer) ->
|
||||
CourseTabContainerFactory = React.createFactory CourseTabContainer
|
||||
mgpEnabled = ENV.MULTIPLE_GRADING_PERIODS
|
||||
ReactDOM.render(
|
||||
CourseTabContainerFactory(multipleGradingPeriodsEnabled: mgpEnabled),
|
||||
CourseTabContainerFactory(hasGradingPeriods: ENV.HAS_GRADING_PERIODS),
|
||||
document.getElementById("react_grading_tabs")
|
||||
)
|
||||
|
|
|
@ -17,7 +17,7 @@ define [
|
|||
|
||||
pointRatio: ( ->
|
||||
"#{I18n.n @get('student.total_grade.score')} / #{I18n.n @get('student.total_grade.possible')}"
|
||||
).property("weighted_groups", "student.total_grade.score", "student.total_grade.possible")
|
||||
).property("weighted_grades", "student.total_grade.score", "student.total_grade.possible")
|
||||
|
||||
letterGrade:(->
|
||||
GradingSchemeHelper.scoreToGrade(@get('percent'), @get('gradingStandard'))
|
||||
|
@ -26,7 +26,7 @@ define [
|
|||
showGrade: Ember.computed.bool('student.total_grade.possible')
|
||||
|
||||
showPoints:(->
|
||||
!!(!@get("weighted_groups") && @get("student.total_grade"))
|
||||
).property("weighted_groups","student.total_grade")
|
||||
!!(!@get("weighted_grades") && @get("student.total_grade"))
|
||||
).property("weighted_grades","student.total_grade")
|
||||
|
||||
showLetterGrade: Ember.computed.bool("gradingStandard")
|
||||
|
|
|
@ -32,19 +32,23 @@ define [
|
|||
'compiled/AssignmentDetailsDialog'
|
||||
'compiled/AssignmentMuter'
|
||||
'jsx/gradebook/CourseGradeCalculator'
|
||||
'jsx/gradebook/EffectiveDueDates'
|
||||
'compiled/gradebook/OutcomeGradebookGrid'
|
||||
'../../shared/components/ic_submission_download_dialog_component'
|
||||
'str/htmlEscape'
|
||||
'compiled/models/grade_summary/CalculationMethodContent'
|
||||
'jsx/gradebook/SubmissionStateMap'
|
||||
'compiled/api/gradingPeriodsApi'
|
||||
'compiled/api/gradingPeriodSetsApi'
|
||||
'jsx/gradezilla/individual-gradebook/components/GradebookSelector'
|
||||
'jquery.instructure_date_and_time'
|
||||
], ($, React, ReactDOM, ajax, round, userSettings, fetchAllPages, parseLinkHeader,
|
||||
I18n, Ember, _, tz, AssignmentDetailsDialog, AssignmentMuter,
|
||||
CourseGradeCalculator, outcomeGrid, ic_submission_download_dialog,
|
||||
htmlEscape, CalculationMethodContent, SubmissionStateMap, GradingPeriodsAPI,
|
||||
GradebookSelector) ->
|
||||
], (
|
||||
$, React, ReactDOM,
|
||||
ajax, round, userSettings, fetchAllPages, parseLinkHeader, I18n, Ember, _, tz, AssignmentDetailsDialog,
|
||||
AssignmentMuter, CourseGradeCalculator, EffectiveDueDates, outcomeGrid, ic_submission_download_dialog,
|
||||
htmlEscape, CalculationMethodContent, SubmissionStateMap, GradingPeriodsApi, GradingPeriodSetsApi,
|
||||
GradebookSelector
|
||||
) ->
|
||||
|
||||
{ get, set, setProperties } = Ember
|
||||
|
||||
|
@ -117,17 +121,33 @@ define [
|
|||
|
||||
submissionsUrl: get(window, 'ENV.GRADEBOOK_OPTIONS.submissions_url')
|
||||
|
||||
mgpEnabled: get(window, 'ENV.GRADEBOOK_OPTIONS.multiple_grading_periods_enabled')
|
||||
has_grading_periods: get(window, 'ENV.GRADEBOOK_OPTIONS.has_grading_periods')
|
||||
|
||||
gradingPeriods:
|
||||
(->
|
||||
periods = get(window, 'ENV.GRADEBOOK_OPTIONS.active_grading_periods')
|
||||
deserializedPeriods = GradingPeriodsApi.deserializePeriods(periods)
|
||||
optionForAllPeriods =
|
||||
id: '0', title: I18n.t("all_grading_periods", "All Grading Periods")
|
||||
_.compact([optionForAllPeriods].concat(deserializedPeriods))
|
||||
)()
|
||||
|
||||
getGradingPeriodSet: ->
|
||||
grading_period_set = get(window, 'ENV.GRADEBOOK_OPTIONS.grading_period_set')
|
||||
if grading_period_set
|
||||
GradingPeriodSetsApi.deserializeSet(grading_period_set)
|
||||
else
|
||||
null
|
||||
|
||||
gradingPeriods: (->
|
||||
periods = get(window, 'ENV.GRADEBOOK_OPTIONS.active_grading_periods')
|
||||
deserializedPeriods = GradingPeriodsAPI.deserializePeriods(periods)
|
||||
deserializedPeriods = GradingPeriodsApi.deserializePeriods(periods)
|
||||
optionForAllPeriods =
|
||||
id: '0', title: I18n.t("all_grading_periods", "All Grading Periods")
|
||||
_.compact([optionForAllPeriods].concat(deserializedPeriods))
|
||||
)()
|
||||
|
||||
lastGeneratedCsvLabel: do () =>
|
||||
lastGeneratedCsvLabel: do () =>
|
||||
if get(window, 'ENV.GRADEBOOK_OPTIONS.gradebook_csv_progress')
|
||||
gradebook_csv_export_date = get(window, 'ENV.GRADEBOOK_OPTIONS.gradebook_csv_progress.progress.updated_at')
|
||||
I18n.t('Download Scores Generated on %{date}',
|
||||
|
@ -333,12 +353,26 @@ define [
|
|||
id != userId
|
||||
set(assignment, 'assignment_visibility', filteredVisibilities)
|
||||
|
||||
calculate: (submissionsArray) ->
|
||||
CourseGradeCalculator.calculate submissionsArray, @assignmentGroupsHash(), @get('weightingScheme')
|
||||
calculate: (student) ->
|
||||
submissions = @submissionsForStudent(student)
|
||||
assignmentGroups = @assignmentGroupsHash()
|
||||
weightingScheme = @get('weightingScheme')
|
||||
gradingPeriodSet = @getGradingPeriodSet()
|
||||
effectiveDueDates = @get('effectiveDueDates.content')
|
||||
|
||||
hasGradingPeriods = gradingPeriodSet and effectiveDueDates
|
||||
|
||||
CourseGradeCalculator.calculate(
|
||||
submissions,
|
||||
assignmentGroups,
|
||||
weightingScheme,
|
||||
gradingPeriodSet if hasGradingPeriods,
|
||||
EffectiveDueDates.scopeToUser(effectiveDueDates, student.id) if hasGradingPeriods
|
||||
)
|
||||
|
||||
submissionsForStudent: (student) ->
|
||||
allSubmissions = (value for key, value of student when key.match /^assignment_(?!group)/)
|
||||
return allSubmissions unless @get('mgpEnabled')
|
||||
return allSubmissions unless @get('has_grading_periods')
|
||||
selectedPeriodID = @get('selectedGradingPeriod.id')
|
||||
return allSubmissions if !selectedPeriodID or selectedPeriodID == '0'
|
||||
|
||||
|
@ -348,23 +382,32 @@ define [
|
|||
|
||||
calculateStudentGrade: (student) ->
|
||||
if student.isLoaded
|
||||
finalOrCurrent = if @get('includeUngradedAssignments') then 'final' else 'current'
|
||||
result = @calculate(@submissionsForStudent(student))
|
||||
for group in result.group_sums
|
||||
set(student, "assignment_group_#{group.group.id}", group[finalOrCurrent])
|
||||
for submissionData in group[finalOrCurrent].submissions
|
||||
set(submissionData.submission, 'drop', submissionData.drop)
|
||||
result = result[finalOrCurrent]
|
||||
grades = @calculate(student)
|
||||
|
||||
percent = round (result.score / result.possible * 100), 2
|
||||
selectedPeriodID = @get('selectedGradingPeriod.id')
|
||||
if selectedPeriodID && selectedPeriodID != '0'
|
||||
grades = grades.gradingPeriods[selectedPeriodID]
|
||||
|
||||
finalOrCurrent = if @get('includeUngradedAssignments') then 'final' else 'current'
|
||||
|
||||
for assignmentGroupId, grade of grades.assignmentGroups
|
||||
set(student, "assignment_group_#{assignmentGroupId}", grade[finalOrCurrent])
|
||||
for submissionData in grade[finalOrCurrent].submissions
|
||||
set(submissionData.submission, 'drop', submissionData.drop)
|
||||
grades = grades[finalOrCurrent]
|
||||
|
||||
percent = round (grades.score / grades.possible * 100), 2
|
||||
percent = 0 if isNaN(percent)
|
||||
setProperties student,
|
||||
total_grade: result
|
||||
total_grade: grades
|
||||
total_percent: I18n.n(percent, percentage: true)
|
||||
|
||||
calculateAllGrades: (->
|
||||
@get('students').forEach (student) => @calculateStudentGrade student
|
||||
).observes('includeUngradedAssignments','groupsAreWeighted', 'assignment_groups.@each.group_weight')
|
||||
).observes(
|
||||
'includeUngradedAssignments','groupsAreWeighted', 'assignment_groups.@each.group_weight',
|
||||
'effectiveDueDates.isLoaded'
|
||||
)
|
||||
|
||||
sectionSelectDefaultLabel: I18n.t "all_sections", "All Sections"
|
||||
studentSelectDefaultLabel: I18n.t "no_student", "No Student Selected"
|
||||
|
@ -378,7 +421,7 @@ define [
|
|||
fetchAssignmentGroups: (->
|
||||
params = { exclude_response_fields: ['in_closed_grading_period'] }
|
||||
gpId = @get('selectedGradingPeriod.id')
|
||||
if @get('mgpEnabled') && gpId != '0'
|
||||
if @get('has_grading_periods') && gpId != '0'
|
||||
params.grading_period_id = gpId
|
||||
@set('assignment_groups', [])
|
||||
@set('assignmentsFromGroups', [])
|
||||
|
@ -475,16 +518,17 @@ define [
|
|||
)
|
||||
|
||||
displayPointTotals: (->
|
||||
if @get("groupsAreWeighted")
|
||||
false
|
||||
else
|
||||
@get("showTotalAsPoints")
|
||||
).property('groupsAreWeighted', 'showTotalAsPoints')
|
||||
@get('showTotalAsPoints') and not @get('gradesAreWeighted')
|
||||
).property('gradesAreWeighted', 'showTotalAsPoints')
|
||||
|
||||
groupsAreWeighted: (->
|
||||
@get("weightingScheme") == "percent"
|
||||
).property("weightingScheme")
|
||||
|
||||
gradesAreWeighted: (->
|
||||
@get('groupsAreWeighted') or !!@getGradingPeriodSet()?.weighted
|
||||
).property('weightingScheme')
|
||||
|
||||
updateShowTotalAs: (->
|
||||
@set "showTotalAsPoints", @get("displayPointTotals")
|
||||
ajax.request(
|
||||
|
@ -493,7 +537,7 @@ define [
|
|||
url: ENV.GRADEBOOK_OPTIONS.setting_update_url
|
||||
data:
|
||||
show_total_grade_as_points: @get("displayPointTotals"))
|
||||
).observes('showTotalAsPoints', 'groupsAreWeighted')
|
||||
).observes('showTotalAsPoints', 'gradesAreWeighted')
|
||||
|
||||
studentColumnData: {}
|
||||
|
||||
|
@ -705,7 +749,7 @@ define [
|
|||
|
||||
populateSubmissionStateMap: (->
|
||||
map = new SubmissionStateMap(
|
||||
gradingPeriodsEnabled: !!@mgpEnabled
|
||||
hasGradingPeriods: !!@has_grading_periods
|
||||
selectedGradingPeriodID: @get('selectedGradingPeriod.id') || '0'
|
||||
isAdmin: ENV.current_user_roles && _.contains(ENV.current_user_roles, "admin")
|
||||
)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
{{#if mgpEnabled}}
|
||||
{{#if has_grading_periods}}
|
||||
<div class="row">
|
||||
<div class="span4">
|
||||
<label for="grading_period_select" class="text-right-responsive">
|
||||
|
@ -18,4 +18,4 @@
|
|||
}}
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
|
|
|
@ -35,7 +35,7 @@
|
|||
{{
|
||||
final-grade
|
||||
student=selectedStudent
|
||||
weighted_groups=groupsAreWeighted
|
||||
weighted_grades=gradesAreWeighted
|
||||
gradingStandard=ENV.GRADEBOOK_OPTIONS.grading_standard
|
||||
}}
|
||||
|
||||
|
@ -55,4 +55,4 @@
|
|||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -17,8 +17,7 @@ define [
|
|||
App = startApp()
|
||||
@component = App.GradingCellComponent.create()
|
||||
|
||||
ENV.GRADEBOOK_OPTIONS.multiple_grading_periods_enabled = true
|
||||
ENV.GRADEBOOK_OPTIONS.latest_end_date_of_admin_created_grading_periods_in_the_past = "2013-10-01T10:00:00Z"
|
||||
ENV.GRADEBOOK_OPTIONS.has_grading_periods = true
|
||||
ENV.current_user_roles = []
|
||||
|
||||
setType = (type) =>
|
||||
|
|
|
@ -5,10 +5,15 @@ define [
|
|||
'../start_app'
|
||||
'ember'
|
||||
'../shared_ajax_fixtures'
|
||||
'spec/jsx/gradebook/GradeCalculatorSpecHelper'
|
||||
'../../controllers/screenreader_gradebook_controller'
|
||||
'compiled/userSettings'
|
||||
'jsx/gradebook/CourseGradeCalculator'
|
||||
'vendor/jquery.ba-tinypubsub'
|
||||
], ($, _, ajax, startApp, Ember, fixtures, SRGBController, userSettings) ->
|
||||
], (
|
||||
$, _, ajax, startApp, Ember, fixtures, GradeCalculatorSpecHelper, SRGBController, userSettings,
|
||||
CourseGradeCalculator
|
||||
) ->
|
||||
|
||||
workAroundRaceCondition = ->
|
||||
ajax.request()
|
||||
|
@ -19,6 +24,13 @@ define [
|
|||
clone = (obj) ->
|
||||
Ember.copy obj, true
|
||||
|
||||
createExampleGrades = GradeCalculatorSpecHelper.createCourseGradesWithGradingPeriods
|
||||
|
||||
createExampleGradingPeriodSet = ->
|
||||
id: '1501'
|
||||
gradingPeriods: [{ id: '701', weight: 50 }, { id: '702', weight: 50 }]
|
||||
weighted: true
|
||||
|
||||
setup = (isDraftState=false, sortOrder='assignment_group') ->
|
||||
fixtures.create()
|
||||
@contextGetStub = sinon.stub(userSettings, 'contextGet')
|
||||
|
@ -51,7 +63,6 @@ define [
|
|||
teardown: ->
|
||||
teardown.call this
|
||||
|
||||
|
||||
test 'calculates students properly', ->
|
||||
equal @srgb.get('students.length'), 10
|
||||
equal @srgb.get('students.firstObject').name, fixtures.students[0].user.name
|
||||
|
@ -85,19 +96,19 @@ define [
|
|||
|
||||
test 'displayName is hiddenName when hideStudentNames is true', ->
|
||||
@srgb.set('hideStudentNames', true)
|
||||
equal @srgb.get('displayName'), "hiddenName"
|
||||
equal @srgb.get('displayName'), 'hiddenName'
|
||||
@srgb.set('hideStudentNames', false)
|
||||
equal @srgb.get('displayName'), "name"
|
||||
equal @srgb.get('displayName'), 'name'
|
||||
|
||||
test 'displayPointTotals is false when groups are weighted even if showTotalAsPoints is true', ->
|
||||
test 'displayPointTotals is false when grades are weighted even if showTotalAsPoints is true', ->
|
||||
Ember.run =>
|
||||
@srgb.set('showTotalAsPoints', true)
|
||||
@srgb.set('groupsAreWeighted', true)
|
||||
@srgb.set('gradesAreWeighted', true)
|
||||
equal @srgb.get('displayPointTotals'), false
|
||||
|
||||
test 'displayPointTotals is toggled by showTotalAsPoints when groups are unweighted', ->
|
||||
test 'displayPointTotals is toggled by showTotalAsPoints when grades are unweighted', ->
|
||||
Ember.run =>
|
||||
@srgb.set('groupsAreWeighted', false)
|
||||
@srgb.set('gradesAreWeighted', false)
|
||||
@srgb.set('showTotalAsPoints', true)
|
||||
equal @srgb.get('displayPointTotals'), true
|
||||
@srgb.set('showTotalAsPoints', false)
|
||||
|
@ -161,18 +172,75 @@ define [
|
|||
equal @srgb.get('assignments.lastObject.name'), 'Drink Water'
|
||||
start()
|
||||
|
||||
QUnit.module 'screenreader_gradebook_controller#gradesAreWeighted',
|
||||
setup: ->
|
||||
setup.call this
|
||||
teardown: ->
|
||||
teardown.call this
|
||||
|
||||
test 'is true when the grading period set is weighted', ->
|
||||
gradingPeriodSet = createExampleGradingPeriodSet()
|
||||
gradingPeriodSet.weighted = true
|
||||
@stub(@srgb, 'getGradingPeriodSet').returns(gradingPeriodSet)
|
||||
Ember.run =>
|
||||
@srgb.set('groupsAreWeighted', false)
|
||||
equal @srgb.get('gradesAreWeighted'), true
|
||||
|
||||
test 'is true when groupsAreWeighted is true', ->
|
||||
gradingPeriodSet = createExampleGradingPeriodSet()
|
||||
gradingPeriodSet.weighted = false
|
||||
@stub(@srgb, 'getGradingPeriodSet').returns(gradingPeriodSet)
|
||||
Ember.run =>
|
||||
@srgb.set('groupsAreWeighted', true)
|
||||
equal @srgb.get('gradesAreWeighted'), true
|
||||
|
||||
test 'is false when assignment groups are not weighted and the grading period set is not weighted', ->
|
||||
gradingPeriodSet = createExampleGradingPeriodSet()
|
||||
gradingPeriodSet.weighted = false
|
||||
@stub(@srgb, 'getGradingPeriodSet').returns(gradingPeriodSet)
|
||||
Ember.run =>
|
||||
@srgb.set('groupsAreWeighted', false)
|
||||
equal @srgb.get('gradesAreWeighted'), false
|
||||
|
||||
test 'is false when assignment groups are not weighted and the grading period set is not defined', ->
|
||||
@stub(@srgb, 'getGradingPeriodSet').returns(null)
|
||||
Ember.run =>
|
||||
@srgb.set('groupsAreWeighted', false)
|
||||
equal @srgb.get('gradesAreWeighted'), false
|
||||
|
||||
QUnit.module '#getGradingPeriodSet',
|
||||
setup: ->
|
||||
setup.call this
|
||||
|
||||
teardown: ->
|
||||
teardown.call this
|
||||
|
||||
test 'normalizes the grading period set from the env', ->
|
||||
ENV.GRADEBOOK_OPTIONS.grading_period_set =
|
||||
id: '1501'
|
||||
grading_periods: [{ id: '701', weight: 50 }, { id: '702', weight: 50 }]
|
||||
weighted: true
|
||||
gradingPeriodSet = @srgb.getGradingPeriodSet()
|
||||
deepEqual(gradingPeriodSet.id, '1501')
|
||||
equal(gradingPeriodSet.gradingPeriods.length, 2)
|
||||
deepEqual(_.map(gradingPeriodSet.gradingPeriods, 'id'), ['701', '702'])
|
||||
|
||||
test 'sets grading period set to null when not defined in the env', ->
|
||||
gradingPeriodSet = @srgb.getGradingPeriodSet()
|
||||
deepEqual(gradingPeriodSet, null)
|
||||
|
||||
QUnit.module '#submissionsForStudent',
|
||||
setupThis: (options = {}) ->
|
||||
effectiveDueDates = Ember.ObjectProxy.create(
|
||||
content: {
|
||||
1: { 1: { grading_period_id: "1" } },
|
||||
2: { 1: { grading_period_id: "2" } }
|
||||
1: { 1: { grading_period_id: '1' } },
|
||||
2: { 1: { grading_period_id: '2' } }
|
||||
}
|
||||
)
|
||||
|
||||
defaults = {
|
||||
mgpEnabled: false,
|
||||
"selectedGradingPeriod.id": null,
|
||||
has_grading_periods: false,
|
||||
'selectedGradingPeriod.id': null,
|
||||
effectiveDueDates
|
||||
}
|
||||
self = _.defaults options, defaults
|
||||
|
@ -181,40 +249,41 @@ define [
|
|||
|
||||
setup: ->
|
||||
@student =
|
||||
id: "1"
|
||||
assignment_1: { assignment_id: "1", user_id: "1", name: "yolo" }
|
||||
assignment_2: { assignment_id: "2", user_id: "1", name: "froyo" }
|
||||
id: '1'
|
||||
assignment_1: { assignment_id: '1', user_id: '1', name: 'yolo' }
|
||||
assignment_2: { assignment_id: '2', user_id: '1', name: 'froyo' }
|
||||
|
||||
setup.call this
|
||||
|
||||
teardown: ->
|
||||
teardown.call this
|
||||
|
||||
test "returns all submissions for the student (multiple grading periods disabled)", ->
|
||||
test 'returns all submissions for the student when there are no grading periods', ->
|
||||
self = @setupThis()
|
||||
submissions = @srgb.submissionsForStudent.call(self, @student)
|
||||
propEqual _.pluck(submissions, "assignment_id"), ["1", "2"]
|
||||
propEqual _.pluck(submissions, 'assignment_id'), ['1', '2']
|
||||
|
||||
test "returns all submissions if 'All Grading Periods' is selected", ->
|
||||
test 'returns all submissions if "All Grading Periods" is selected', ->
|
||||
self = @setupThis(
|
||||
mgpEnabled: true,
|
||||
"selectedGradingPeriod.id": "0",
|
||||
has_grading_periods: true,
|
||||
'selectedGradingPeriod.id': '0'
|
||||
)
|
||||
submissions = @srgb.submissionsForStudent.call(self, @student)
|
||||
propEqual _.pluck(submissions, "assignment_id"), ["1", "2"]
|
||||
propEqual _.pluck(submissions, 'assignment_id'), ['1', '2']
|
||||
|
||||
test "only returns submissions due for the student in the selected grading period", ->
|
||||
test 'only returns submissions due for the student in the selected grading period', ->
|
||||
self = @setupThis(
|
||||
mgpEnabled: true,
|
||||
"selectedGradingPeriod.id": "2"
|
||||
has_grading_periods: true,
|
||||
'selectedGradingPeriod.id': '2'
|
||||
)
|
||||
submissions = @srgb.submissionsForStudent.call(self, @student)
|
||||
propEqual _.pluck(submissions, "assignment_id"), ["2"]
|
||||
propEqual _.pluck(submissions, 'assignment_id'), ['2']
|
||||
|
||||
|
||||
QUnit.module 'screenreader_gradebook_controller: with selected student',
|
||||
setup: ->
|
||||
setup.call this
|
||||
@stub(@srgb, 'calculateStudentGrade')
|
||||
@completeSetup = =>
|
||||
workAroundRaceCondition().then =>
|
||||
Ember.run =>
|
||||
|
@ -232,7 +301,7 @@ define [
|
|||
start()
|
||||
|
||||
asyncTest 'assignments excludes any due for the selected student in a different grading period', ->
|
||||
@srgb.mgpEnabled = true
|
||||
@srgb.has_grading_periods = true
|
||||
@completeSetup().then =>
|
||||
deepEqual(@srgb.get('assignments').mapBy('id'), ['3'])
|
||||
start()
|
||||
|
@ -350,8 +419,8 @@ define [
|
|||
id: '21'
|
||||
name: 'Unpublished Assignment'
|
||||
points_possible: 10
|
||||
grading_type: "percent"
|
||||
submission_types: ["none"]
|
||||
grading_type: 'percent'
|
||||
submission_types: ['none']
|
||||
due_at: null
|
||||
position: 6
|
||||
assignment_group_id:'4'
|
||||
|
@ -370,24 +439,7 @@ define [
|
|||
|
||||
|
||||
calc_stub = {
|
||||
group_sums: [
|
||||
{
|
||||
final:
|
||||
possible: 100
|
||||
score: 50
|
||||
submission_count: 10
|
||||
weight: 50
|
||||
submissions: []
|
||||
current:
|
||||
possible: 100
|
||||
score: 20
|
||||
submission_count: 5
|
||||
weight: 50
|
||||
submissions:[]
|
||||
group:
|
||||
id: "1"
|
||||
}
|
||||
]
|
||||
assignmentGroups: {}
|
||||
final:
|
||||
possible: 100
|
||||
score: 90
|
||||
|
@ -398,24 +450,7 @@ define [
|
|||
|
||||
|
||||
calc_stub_with_0_possible = {
|
||||
group_sums: [
|
||||
{
|
||||
final:
|
||||
possible: 0
|
||||
score: 50
|
||||
submission_count: 10
|
||||
weight: 50
|
||||
submissions: []
|
||||
current:
|
||||
possible: 0
|
||||
score: 20
|
||||
submission_count: 5
|
||||
weight: 50
|
||||
submissions:[]
|
||||
group:
|
||||
id: "1"
|
||||
}
|
||||
]
|
||||
assignmentGroups: {}
|
||||
final:
|
||||
possible: 0
|
||||
score: 0
|
||||
|
@ -457,6 +492,125 @@ define [
|
|||
equal @srgb.get('students.firstObject.total_percent'), '0%'
|
||||
start()
|
||||
|
||||
QUnit.module 'screenreader_gradebook_controller: calculate',
|
||||
setupThis:(options = {}) ->
|
||||
assignments = [{ id: 201, points_possible: 10, omit_from_final_grade: false }]
|
||||
submissions = [{ assignment_id: 201, score: 10 }]
|
||||
assignmentGroupsHash = { 301: { id: 301, group_weight: 60, rules: {}, assignments } }
|
||||
gradingPeriodSet =
|
||||
id: '1501'
|
||||
gradingPeriods: [{ id: '701', weight: 50 }, { id: '702', weight: 50 }]
|
||||
weighted: true
|
||||
props = _.defaults options,
|
||||
weightingScheme: 'points'
|
||||
getGradingPeriodSet: () -> gradingPeriodSet
|
||||
'effectiveDueDates.content': { 201: { 101: { grading_period_id: '701' } } }
|
||||
_.extend {}, props,
|
||||
get: (attr) -> props[attr]
|
||||
submissionsForStudent: () -> submissions
|
||||
assignmentGroupsHash: () -> assignmentGroupsHash
|
||||
|
||||
setup: ->
|
||||
@calculate = SRGBController.prototype.calculate
|
||||
|
||||
test 'calculates grades using properties from the gradebook', ->
|
||||
self = @setupThis()
|
||||
@stub(CourseGradeCalculator, 'calculate').returns('expected')
|
||||
grades = @calculate.call(self, id: '101', loaded: true)
|
||||
equal(grades, 'expected')
|
||||
args = CourseGradeCalculator.calculate.getCall(0).args
|
||||
equal(args[0], self.submissionsForStudent())
|
||||
equal(args[1], self.assignmentGroupsHash())
|
||||
equal(args[2], self.get('weightingScheme'))
|
||||
equal(args[3], self.getGradingPeriodSet())
|
||||
|
||||
test 'scopes effective due dates to the user', ->
|
||||
self = @setupThis()
|
||||
@stub(CourseGradeCalculator, 'calculate')
|
||||
@calculate.call(self, id: '101', loaded: true)
|
||||
dueDates = CourseGradeCalculator.calculate.getCall(0).args[4]
|
||||
deepEqual(dueDates, 201: { grading_period_id: '701' })
|
||||
|
||||
test 'calculates grades without grading period data when grading period set is null', ->
|
||||
self = @setupThis(getGradingPeriodSet: -> null)
|
||||
@stub(CourseGradeCalculator, 'calculate')
|
||||
@calculate.call(self, id: '101', loaded: true)
|
||||
args = CourseGradeCalculator.calculate.getCall(0).args
|
||||
equal(args[0], self.submissionsForStudent())
|
||||
equal(args[1], self.assignmentGroupsHash())
|
||||
equal(args[2], self.get('weightingScheme'))
|
||||
equal(typeof args[3], 'undefined')
|
||||
equal(typeof args[4], 'undefined')
|
||||
|
||||
test 'calculates grades without grading period data when effective due dates are not defined', ->
|
||||
self = @setupThis('effectiveDueDates.content': null)
|
||||
@stub(CourseGradeCalculator, 'calculate')
|
||||
@calculate.call(self, id: '101', loaded: true)
|
||||
args = CourseGradeCalculator.calculate.getCall(0).args
|
||||
equal(args[0], self.submissionsForStudent())
|
||||
equal(args[1], self.assignmentGroupsHash())
|
||||
equal(args[2], self.get('weightingScheme'))
|
||||
equal(typeof args[3], 'undefined')
|
||||
equal(typeof args[4], 'undefined')
|
||||
|
||||
QUnit.module 'screenreader_gradebook_controller: calculateStudentGrade',
|
||||
setupThis:(options = {}) ->
|
||||
assignments = [{ id: 201, points_possible: 10, omit_from_final_grade: false }]
|
||||
submissions = [{ assignment_id: 201, score: 10 }]
|
||||
assignmentGroupsHash = { 301: { id: 301, group_weight: 60, rules: {}, assignments } }
|
||||
gradingPeriodSet =
|
||||
id: '1501'
|
||||
gradingPeriods: [{ id: '701', weight: 50 }, { id: '702', weight: 50 }]
|
||||
weighted: true
|
||||
props = _.defaults options,
|
||||
weightingScheme: 'points'
|
||||
getGradingPeriodSet: () -> gradingPeriodSet
|
||||
calculate: () -> CourseGradeCalculator.calculate()
|
||||
'effectiveDueDates.content': { 201: { 101: { grading_period_id: '701' } } }
|
||||
'selectedGradingPeriod.id': '0'
|
||||
_.extend {}, props,
|
||||
get: (attr) -> props[attr]
|
||||
submissionsForStudent: () -> submissions
|
||||
assignmentGroupsHash: () -> assignmentGroupsHash
|
||||
|
||||
setup: ->
|
||||
@calculateStudentGrade = SRGBController.prototype.calculateStudentGrade
|
||||
|
||||
test 'stores the current grade on the student when not including ungraded assignments', ->
|
||||
exampleGrades = createExampleGrades()
|
||||
self = @setupThis(includeUngradedAssignments: false)
|
||||
@stub(CourseGradeCalculator, 'calculate').returns(exampleGrades)
|
||||
student = Ember.Object.create(id: '101', loaded: true)
|
||||
student.set('isLoaded', true)
|
||||
@calculateStudentGrade.call(self, student)
|
||||
equal(student.total_grade, exampleGrades.current)
|
||||
|
||||
test 'stores the final grade on the student when including ungraded assignments', ->
|
||||
exampleGrades = createExampleGrades()
|
||||
self = @setupThis(includeUngradedAssignments: true)
|
||||
@stub(CourseGradeCalculator, 'calculate').returns(exampleGrades)
|
||||
student = Ember.Object.create(id: '101', loaded: true)
|
||||
student.set('isLoaded', true)
|
||||
@calculateStudentGrade.call(self, student)
|
||||
equal(student.total_grade, exampleGrades.final)
|
||||
|
||||
test 'stores the current grade from the selected grading period when not including ungraded assignments', ->
|
||||
exampleGrades = createExampleGrades()
|
||||
self = @setupThis('selectedGradingPeriod.id': 701, includeUngradedAssignments: false)
|
||||
@stub(CourseGradeCalculator, 'calculate').returns(exampleGrades)
|
||||
student = Ember.Object.create(id: '101', loaded: true)
|
||||
student.set('isLoaded', true)
|
||||
@calculateStudentGrade.call(self, student)
|
||||
equal(student.total_grade, exampleGrades.gradingPeriods[701].current)
|
||||
|
||||
test 'stores the final grade from the selected grading period when including ungraded assignments', ->
|
||||
exampleGrades = createExampleGrades()
|
||||
self = @setupThis('selectedGradingPeriod.id': 701, includeUngradedAssignments: true)
|
||||
@stub(CourseGradeCalculator, 'calculate').returns(exampleGrades)
|
||||
student = Ember.Object.create(id: '101', loaded: true)
|
||||
student.set('isLoaded', true)
|
||||
@calculateStudentGrade.call(self, student)
|
||||
equal(student.total_grade, exampleGrades.gradingPeriods[701].final)
|
||||
|
||||
QUnit.module 'screenreader_gradebook_controller: notes computed props',
|
||||
setup: ->
|
||||
|
@ -519,20 +673,20 @@ define [
|
|||
Ember.run =>
|
||||
@srgb.set('showNotesColumn', true)
|
||||
@srgb.set('shouldCreateNotes', false)
|
||||
deepEqual @srgb.get('notesParams'), "column[hidden]": false
|
||||
deepEqual @srgb.get('notesParams'), 'column[hidden]': false
|
||||
|
||||
Ember.run =>
|
||||
@srgb.set('showNotesColumn', false)
|
||||
@srgb.set('shouldCreateNotes', false)
|
||||
deepEqual @srgb.get('notesParams'), "column[hidden]": true
|
||||
deepEqual @srgb.get('notesParams'), 'column[hidden]': true
|
||||
|
||||
Ember.run =>
|
||||
@srgb.set('showNotesColumn', true)
|
||||
@srgb.set('shouldCreateNotes', true)
|
||||
deepEqual @srgb.get('notesParams'),
|
||||
"column[title]": "Notes"
|
||||
"column[position]": 1
|
||||
"column[teacher_notes]": true
|
||||
'column[title]': 'Notes'
|
||||
'column[position]': 1
|
||||
'column[teacher_notes]': true
|
||||
|
||||
test 'notesVerb', ->
|
||||
Ember.run =>
|
||||
|
@ -552,13 +706,14 @@ define [
|
|||
teardown.call this
|
||||
|
||||
test 'calculates invalidGroupsWarningPhrases properly', ->
|
||||
equal @srgb.get('invalidGroupsWarningPhrases'), "Note: Score does not include assignments from the group Invalid AG because it has no points possible."
|
||||
equal @srgb.get('invalidGroupsWarningPhrases'),
|
||||
'Note: Score does not include assignments from the group Invalid AG because it has no points possible.'
|
||||
|
||||
test 'sets showInvalidGroupWarning to false if groups are not weighted', ->
|
||||
Ember.run =>
|
||||
@srgb.set('weightingScheme', "equal")
|
||||
@srgb.set('weightingScheme', 'equal')
|
||||
equal @srgb.get('showInvalidGroupWarning'), false
|
||||
@srgb.set('weightingScheme', "percent")
|
||||
@srgb.set('weightingScheme', 'percent')
|
||||
equal @srgb.get('showInvalidGroupWarning'), true
|
||||
|
||||
|
||||
|
|
|
@ -3,11 +3,6 @@ define ['../main', 'ember'], (Application, Ember) ->
|
|||
App = null
|
||||
Ember.run.join ->
|
||||
App = Application.create
|
||||
LOG_ACTIVE_GENERATION: yes
|
||||
LOG_MODULE_RESOLVER: yes
|
||||
LOG_TRANSITIONS: yes
|
||||
LOG_TRANSITIONS_INTERNAL: yes
|
||||
LOG_VIEW_LOOKUPS: yes
|
||||
rootElement: '#fixtures'
|
||||
App.Router.reopen history: 'none'
|
||||
App.setupForTesting()
|
||||
|
|
|
@ -1,4 +1,21 @@
|
|||
# This class both creates the slickgrid instance, and acts as the data source for that instance.
|
||||
#
|
||||
# Copyright (C) 2011 - 2017 Instructure, Inc.
|
||||
#
|
||||
# This file is part of Canvas.
|
||||
#
|
||||
# Canvas is free software: you can redistribute it and/or modify it under
|
||||
# the terms of the GNU Affero General Public License as published by the Free
|
||||
# Software Foundation, version 3 of the License.
|
||||
#
|
||||
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
# details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License along
|
||||
# with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
define [
|
||||
'jquery'
|
||||
'underscore'
|
||||
|
@ -18,6 +35,7 @@ define [
|
|||
'i18n!gradebook'
|
||||
'compiled/gradebook/GradebookTranslations'
|
||||
'jsx/gradebook/CourseGradeCalculator'
|
||||
'jsx/gradebook/EffectiveDueDates'
|
||||
'jsx/gradebook/GradingSchemeHelper'
|
||||
'compiled/userSettings'
|
||||
'spin.js'
|
||||
|
@ -41,6 +59,7 @@ define [
|
|||
'compiled/gradebook/GradebookKeyboardNav'
|
||||
'jsx/gradebook/shared/helpers/assignmentHelper'
|
||||
'compiled/api/gradingPeriodsApi'
|
||||
'compiled/api/gradingPeriodSetsApi'
|
||||
'jst/_avatar' #needed by row_student_name
|
||||
'jquery.ajaxJSON'
|
||||
'jquery.instructure_date_and_time'
|
||||
|
@ -60,13 +79,13 @@ define [
|
|||
], (
|
||||
$, _, Backbone, tz, DataLoader, React, ReactDOM, LongTextEditor, KeyboardNavDialog, KeyboardNavTemplate, Slick,
|
||||
TotalColumnHeaderView, round, InputFilterView, i18nObj, I18n, GRADEBOOK_TRANSLATIONS, CourseGradeCalculator,
|
||||
GradingSchemeHelper, UserSettings, Spinner, SubmissionDetailsDialog, AssignmentGroupWeightsDialog,
|
||||
GradeDisplayWarningDialog, PostGradesFrameDialog, SubmissionCell, GradebookHeaderMenu, NumberCompare, natcompare,
|
||||
htmlEscape, PostGradesStore, PostGradesApp, SubmissionStateMap, ColumnHeaderTemplate, GroupTotalCellTemplate,
|
||||
RowStudentNameTemplate, SectionMenuView, GradingPeriodMenuView, GradebookKeyboardNav, assignmentHelper,
|
||||
GradingPeriodsAPI
|
||||
EffectiveDueDates, GradingSchemeHelper, UserSettings, Spinner, SubmissionDetailsDialog,
|
||||
AssignmentGroupWeightsDialog, GradeDisplayWarningDialog, PostGradesFrameDialog, SubmissionCell,
|
||||
GradebookHeaderMenu, NumberCompare, natcompare, htmlEscape, PostGradesStore, PostGradesApp, SubmissionStateMap,
|
||||
ColumnHeaderTemplate, GroupTotalCellTemplate, RowStudentNameTemplate, SectionMenuView, GradingPeriodMenuView,
|
||||
GradebookKeyboardNav, assignmentHelper, GradingPeriodsApi, GradingPeriodSetsApi
|
||||
) ->
|
||||
|
||||
# This class both creates the slickgrid instance, and acts as the data source for that instance.
|
||||
class Gradebook
|
||||
columnWidths =
|
||||
assignment:
|
||||
|
@ -102,11 +121,15 @@ define [
|
|||
@options.settings['show_inactive_enrollments'] == "true"
|
||||
@totalColumnInFront = UserSettings.contextGet 'total_column_in_front'
|
||||
@numberOfFrozenCols = if @totalColumnInFront then 3 else 2
|
||||
@gradingPeriodsEnabled = @options.multiple_grading_periods_enabled
|
||||
@gradingPeriods = GradingPeriodsAPI.deserializePeriods(@options.active_grading_periods)
|
||||
@hasGradingPeriods = @options.has_grading_periods
|
||||
@gradingPeriods = GradingPeriodsApi.deserializePeriods(@options.active_grading_periods)
|
||||
if @options.grading_period_set
|
||||
@gradingPeriodSet = GradingPeriodSetsApi.deserializeSet(@options.grading_period_set)
|
||||
else
|
||||
@gradingPeriodSet = null
|
||||
@gradingPeriodToShow = @getGradingPeriodToShow()
|
||||
@submissionStateMap = new SubmissionStateMap
|
||||
gradingPeriodsEnabled: @gradingPeriodsEnabled
|
||||
hasGradingPeriods: @hasGradingPeriods
|
||||
selectedGradingPeriodID: @gradingPeriodToShow
|
||||
isAdmin: _.contains(ENV.current_user_roles, "admin")
|
||||
@gradebookColumnSizeSettings = @options.gradebook_column_size_settings
|
||||
|
@ -120,7 +143,7 @@ define [
|
|||
$.subscribe 'currentGradingPeriod/change', @updateCurrentGradingPeriod
|
||||
|
||||
assignmentGroupsParams = { exclude_response_fields: @fieldsToExcludeFromAssignments }
|
||||
if @gradingPeriodsEnabled && @gradingPeriodToShow && @gradingPeriodToShow != '0' && @gradingPeriodToShow != ''
|
||||
if @hasGradingPeriods && @gradingPeriodToShow && @gradingPeriodToShow != '0' && @gradingPeriodToShow != ''
|
||||
$.extend(assignmentGroupsParams, {grading_period_id: @gradingPeriodToShow})
|
||||
|
||||
$('li.external-tools-dialog > a[data-url], button.external-tools-dialog').on 'click keyclick', (event) ->
|
||||
|
@ -135,7 +158,7 @@ define [
|
|||
submissionParams =
|
||||
response_fields: ['id', 'user_id', 'url', 'score', 'grade', 'submission_type', 'submitted_at', 'assignment_id', 'grade_matches_current_submission', 'attachments', 'late', 'workflow_state', 'excused']
|
||||
exclude_response_fields: ['preview_url']
|
||||
submissionParams['grading_period_id'] = @gradingPeriodToShow if @gradingPeriodsEnabled && @gradingPeriodToShow && @gradingPeriodToShow != '0' && @gradingPeriodToShow != ''
|
||||
submissionParams['grading_period_id'] = @gradingPeriodToShow if @hasGradingPeriods && @gradingPeriodToShow && @gradingPeriodToShow != '0' && @gradingPeriodToShow != ''
|
||||
dataLoader = DataLoader.loadGradebookData(
|
||||
assignmentGroupsURL: @options.assignment_groups_url
|
||||
assignmentGroupsParams: assignmentGroupsParams
|
||||
|
@ -235,7 +258,7 @@ define [
|
|||
_.contains(activePeriodIds, gradingPeriodId)
|
||||
|
||||
getGradingPeriodToShow: () =>
|
||||
return null unless @gradingPeriodsEnabled
|
||||
return null unless @hasGradingPeriods
|
||||
currentPeriodId = UserSettings.contextGet('gradebook_current_grading_period')
|
||||
if currentPeriodId && (@isAllGradingPeriods(currentPeriodId) || @gradingPeriodIsActive(currentPeriodId))
|
||||
currentPeriodId
|
||||
|
@ -719,7 +742,7 @@ define [
|
|||
templateOpts.warning = @totalGradeWarning
|
||||
templateOpts.lastColumn = true
|
||||
templateOpts.showPointsNotPercent = @displayPointTotals()
|
||||
templateOpts.hideTooltip = @weightedGroups() and not @totalGradeWarning
|
||||
templateOpts.hideTooltip = @weightedGrades() and not @totalGradeWarning
|
||||
GroupTotalCellTemplate templateOpts
|
||||
|
||||
htmlContentFormatter: (row, col, val, columnDef, student) ->
|
||||
|
@ -732,7 +755,7 @@ define [
|
|||
|
||||
submissionsForStudent: (student) =>
|
||||
allSubmissions = (value for key, value of student when key.match /^assignment_(?!group)/)
|
||||
return allSubmissions unless @gradingPeriodsEnabled
|
||||
return allSubmissions unless @hasGradingPeriods
|
||||
return allSubmissions if !@gradingPeriodToShow or @isAllGradingPeriods(@gradingPeriodToShow)
|
||||
|
||||
_.filter allSubmissions, (submission) =>
|
||||
|
@ -741,17 +764,26 @@ define [
|
|||
|
||||
calculateStudentGrade: (student) =>
|
||||
if student.loaded and student.initialized
|
||||
finalOrCurrent = if @include_ungraded_assignments then 'final' else 'current'
|
||||
result = CourseGradeCalculator.calculate(
|
||||
hasGradingPeriods = @gradingPeriodSet and @effectiveDueDates
|
||||
|
||||
grades = CourseGradeCalculator.calculate(
|
||||
@submissionsForStudent(student),
|
||||
@assignmentGroups,
|
||||
@options.group_weighting_scheme
|
||||
@options.group_weighting_scheme,
|
||||
@gradingPeriodSet if hasGradingPeriods,
|
||||
EffectiveDueDates.scopeToUser(@effectiveDueDates, student.id) if hasGradingPeriods
|
||||
)
|
||||
for group in result.group_sums
|
||||
student["assignment_group_#{group.group.id}"] = group[finalOrCurrent]
|
||||
for submissionData in group[finalOrCurrent].submissions
|
||||
|
||||
if @gradingPeriodToShow && !@isAllGradingPeriods(@gradingPeriodToShow)
|
||||
grades = grades.gradingPeriods[@gradingPeriodToShow]
|
||||
|
||||
finalOrCurrent = if @include_ungraded_assignments then 'final' else 'current'
|
||||
|
||||
for assignmentGroupId, grade of grades.assignmentGroups
|
||||
student["assignment_group_#{assignmentGroupId}"] = grade[finalOrCurrent]
|
||||
for submissionData in grade[finalOrCurrent].submissions
|
||||
submissionData.submission.drop = submissionData.drop
|
||||
student["total_grade"] = result[finalOrCurrent]
|
||||
student["total_grade"] = grades[finalOrCurrent]
|
||||
|
||||
@addDroppedClass(student)
|
||||
|
||||
|
@ -1044,7 +1076,7 @@ define [
|
|||
|
||||
initHeader: =>
|
||||
@drawSectionSelectButton() if @sections_enabled
|
||||
@drawGradingPeriodSelectButton() if @gradingPeriodsEnabled
|
||||
@drawGradingPeriodSelectButton() if @hasGradingPeriods
|
||||
|
||||
$settingsMenu = $('.gradebook_dropdown')
|
||||
showConcludedEnrollmentsEl = $settingsMenu.find("#show_concluded_enrollments")
|
||||
|
@ -1217,11 +1249,11 @@ define [
|
|||
weightedGroups: =>
|
||||
@options.group_weighting_scheme == "percent"
|
||||
|
||||
weightedGrades: =>
|
||||
@options.group_weighting_scheme == "percent" || @gradingPeriodSet?.weighted || false
|
||||
|
||||
displayPointTotals: =>
|
||||
if @weightedGroups()
|
||||
false
|
||||
else
|
||||
@options.show_total_grade_as_points
|
||||
@options.show_total_grade_as_points and not @weightedGrades()
|
||||
|
||||
switchTotalDisplay: =>
|
||||
@options.show_total_grade_as_points = not @options.show_total_grade_as_points
|
||||
|
@ -1667,7 +1699,7 @@ define [
|
|||
currentPeriodId == "0"
|
||||
|
||||
hideAggregateColumns: ->
|
||||
return false unless @gradingPeriodsEnabled
|
||||
return false unless @hasGradingPeriods
|
||||
return false if @options.all_grading_periods_totals
|
||||
selectedPeriodId = @getGradingPeriodToShow()
|
||||
@isAllGradingPeriods(selectedPeriodId)
|
||||
|
|
|
@ -29,12 +29,14 @@ define [
|
|||
'jst/KeyboardNavDialog'
|
||||
'vendor/slickgrid'
|
||||
'compiled/api/gradingPeriodsApi'
|
||||
'compiled/api/gradingPeriodSetsApi'
|
||||
'compiled/util/round'
|
||||
'compiled/views/InputFilterView'
|
||||
'i18nObj'
|
||||
'i18n!gradezilla'
|
||||
'compiled/gradezilla/GradebookTranslations'
|
||||
'jsx/gradebook/CourseGradeCalculator'
|
||||
'jsx/gradebook/EffectiveDueDates'
|
||||
'jsx/gradebook/GradingSchemeHelper'
|
||||
'compiled/userSettings'
|
||||
'spin.js'
|
||||
|
@ -79,15 +81,16 @@ define [
|
|||
'compiled/jquery.kylemenu'
|
||||
'compiled/jquery/fixDialogButtons'
|
||||
'jsx/context_cards/StudentContextCardTrigger'
|
||||
], ($, _, Backbone, tz, DataLoader, React, ReactDOM, LongTextEditor, KeyboardNavDialog, KeyboardNavTemplate, Slick,
|
||||
GradingPeriodsAPI, round, InputFilterView, i18nObj, I18n, GRADEBOOK_TRANSLATIONS, CourseGradeCalculator,
|
||||
GradingSchemeHelper, UserSettings, Spinner, SubmissionDetailsDialog, AssignmentGroupWeightsDialog,
|
||||
GradeDisplayWarningDialog, PostGradesFrameDialog, SubmissionCell, GradebookHeaderMenu, NumberCompare, natcompare,
|
||||
htmlEscape, AssignmentColumnHeader, AssignmentGroupColumnHeader, StudentColumnHeader, TotalGradeColumnHeader,
|
||||
GradebookMenu, ViewOptionsMenu, ActionMenu, PostGradesStore, PostGradesApp, SubmissionStateMap,
|
||||
GroupTotalCellTemplate, RowStudentNameTemplate, SectionMenuView, GradingPeriodMenuView, GradebookKeyboardNav,
|
||||
assignmentHelper ) ->
|
||||
|
||||
], (
|
||||
$, _, Backbone, tz, DataLoader, React, ReactDOM, LongTextEditor, KeyboardNavDialog, KeyboardNavTemplate, Slick,
|
||||
GradingPeriodsApi, GradingPeriodSetsApi, round, InputFilterView, i18nObj, I18n, GRADEBOOK_TRANSLATIONS,
|
||||
CourseGradeCalculator, EffectiveDueDates, GradingSchemeHelper, UserSettings, Spinner, SubmissionDetailsDialog,
|
||||
AssignmentGroupWeightsDialog, GradeDisplayWarningDialog, PostGradesFrameDialog, SubmissionCell,
|
||||
GradebookHeaderMenu, NumberCompare, natcompare, htmlEscape, AssignmentColumnHeader, AssignmentGroupColumnHeader,
|
||||
StudentColumnHeader, TotalGradeColumnHeader, GradebookMenu, ViewOptionsMenu, ActionMenu, PostGradesStore,
|
||||
PostGradesApp, SubmissionStateMap, GroupTotalCellTemplate, RowStudentNameTemplate, SectionMenuView,
|
||||
GradingPeriodMenuView, GradebookKeyboardNav, assignmentHelper
|
||||
) ->
|
||||
renderComponent = (reactClass, mountPoint, props = {}, children = null) ->
|
||||
component = React.createElement(reactClass, props, children)
|
||||
ReactDOM.render(component, mountPoint)
|
||||
|
@ -127,11 +130,15 @@ define [
|
|||
@options.settings['show_inactive_enrollments'] == "true"
|
||||
@totalColumnInFront = UserSettings.contextGet 'total_column_in_front'
|
||||
@numberOfFrozenCols = if @totalColumnInFront then 3 else 2
|
||||
@gradingPeriodsEnabled = @options.multiple_grading_periods_enabled
|
||||
@gradingPeriods = GradingPeriodsAPI.deserializePeriods(@options.active_grading_periods)
|
||||
@hasGradingPeriods = @options.has_grading_periods
|
||||
@gradingPeriods = GradingPeriodsApi.deserializePeriods(@options.active_grading_periods)
|
||||
if @options.grading_period_set
|
||||
@gradingPeriodSet = GradingPeriodSetsApi.deserializeSet(@options.grading_period_set)
|
||||
else
|
||||
@gradingPeriodSet = null
|
||||
@gradingPeriodToShow = @getGradingPeriodToShow()
|
||||
@submissionStateMap = new SubmissionStateMap
|
||||
gradingPeriodsEnabled: @gradingPeriodsEnabled
|
||||
hasGradingPeriods: @hasGradingPeriods
|
||||
selectedGradingPeriodID: @gradingPeriodToShow
|
||||
isAdmin: _.contains(ENV.current_user_roles, "admin")
|
||||
@gradebookColumnSizeSettings = @options.gradebook_column_size_settings
|
||||
|
@ -145,7 +152,7 @@ define [
|
|||
$.subscribe 'currentGradingPeriod/change', @updateCurrentGradingPeriod
|
||||
|
||||
assignmentGroupsParams = { exclude_response_fields: @fieldsToExcludeFromAssignments }
|
||||
if @gradingPeriodsEnabled && @gradingPeriodToShow && @gradingPeriodToShow != '0' && @gradingPeriodToShow != ''
|
||||
if @hasGradingPeriods && @gradingPeriodToShow && @gradingPeriodToShow != '0' && @gradingPeriodToShow != ''
|
||||
$.extend(assignmentGroupsParams, {grading_period_id: @gradingPeriodToShow})
|
||||
|
||||
$('li.external-tools-dialog > a[data-url], button.external-tools-dialog').on 'click keyclick', (event) ->
|
||||
|
@ -158,7 +165,7 @@ define [
|
|||
submissionParams =
|
||||
response_fields: ['id', 'user_id', 'url', 'score', 'grade', 'submission_type', 'submitted_at', 'assignment_id', 'grade_matches_current_submission', 'attachments', 'late', 'workflow_state', 'excused']
|
||||
exclude_response_fields: ['preview_url']
|
||||
submissionParams['grading_period_id'] = @gradingPeriodToShow if @gradingPeriodsEnabled && @gradingPeriodToShow && @gradingPeriodToShow != '0' && @gradingPeriodToShow != ''
|
||||
submissionParams['grading_period_id'] = @gradingPeriodToShow if @hasGradingPeriods && @gradingPeriodToShow && @gradingPeriodToShow != '0' && @gradingPeriodToShow != ''
|
||||
dataLoader = DataLoader.loadGradebookData(
|
||||
assignmentGroupsURL: @options.assignment_groups_url
|
||||
assignmentGroupsParams: assignmentGroupsParams
|
||||
|
@ -270,7 +277,7 @@ define [
|
|||
_.contains(activePeriodIds, gradingPeriodId)
|
||||
|
||||
getGradingPeriodToShow: () =>
|
||||
return null unless @gradingPeriodsEnabled
|
||||
return null unless @hasGradingPeriods
|
||||
currentPeriodId = UserSettings.contextGet('gradebook_current_grading_period')
|
||||
if currentPeriodId && (@isAllGradingPeriods(currentPeriodId) || @gradingPeriodIsActive(currentPeriodId))
|
||||
currentPeriodId
|
||||
|
@ -737,7 +744,7 @@ define [
|
|||
templateOpts.warning = @totalGradeWarning
|
||||
templateOpts.lastColumn = true
|
||||
templateOpts.showPointsNotPercent = @displayPointTotals()
|
||||
templateOpts.hideTooltip = @weightedGroups() and not @totalGradeWarning
|
||||
templateOpts.hideTooltip = @weightedGrades() and not @totalGradeWarning
|
||||
GroupTotalCellTemplate templateOpts
|
||||
|
||||
htmlContentFormatter: (row, col, val, columnDef, student) ->
|
||||
|
@ -750,7 +757,7 @@ define [
|
|||
|
||||
submissionsForStudent: (student) =>
|
||||
allSubmissions = (value for key, value of student when key.match /^assignment_(?!group)/)
|
||||
return allSubmissions unless @gradingPeriodsEnabled
|
||||
return allSubmissions unless @hasGradingPeriods
|
||||
return allSubmissions if !@gradingPeriodToShow or @isAllGradingPeriods(@gradingPeriodToShow)
|
||||
|
||||
_.filter allSubmissions, (submission) =>
|
||||
|
@ -759,17 +766,26 @@ define [
|
|||
|
||||
calculateStudentGrade: (student) =>
|
||||
if student.loaded and student.initialized
|
||||
finalOrCurrent = if @include_ungraded_assignments then 'final' else 'current'
|
||||
result = CourseGradeCalculator.calculate(
|
||||
hasGradingPeriods = @gradingPeriodSet and @effectiveDueDates
|
||||
|
||||
grades = CourseGradeCalculator.calculate(
|
||||
@submissionsForStudent(student),
|
||||
@assignmentGroups,
|
||||
@options.group_weighting_scheme
|
||||
@options.group_weighting_scheme,
|
||||
@gradingPeriodSet if hasGradingPeriods,
|
||||
EffectiveDueDates.scopeToUser(@effectiveDueDates, student.id) if hasGradingPeriods
|
||||
)
|
||||
for group in result.group_sums
|
||||
student["assignment_group_#{group.group.id}"] = group[finalOrCurrent]
|
||||
for submissionData in group[finalOrCurrent].submissions
|
||||
|
||||
if @gradingPeriodToShow && !@isAllGradingPeriods(@gradingPeriodToShow)
|
||||
grades = grades.gradingPeriods[@gradingPeriodToShow]
|
||||
|
||||
finalOrCurrent = if @include_ungraded_assignments then 'final' else 'current'
|
||||
|
||||
for assignmentGroupId, grade of grades.assignmentGroups
|
||||
student["assignment_group_#{assignmentGroupId}"] = grade[finalOrCurrent]
|
||||
for submissionData in grade[finalOrCurrent].submissions
|
||||
submissionData.submission.drop = submissionData.drop
|
||||
student["total_grade"] = result[finalOrCurrent]
|
||||
student["total_grade"] = grades[finalOrCurrent]
|
||||
|
||||
@addDroppedClass(student)
|
||||
|
||||
|
@ -1062,7 +1078,7 @@ define [
|
|||
initHeader: =>
|
||||
@renderGradebookMenus()
|
||||
@drawSectionSelectButton() if @sections_enabled
|
||||
@drawGradingPeriodSelectButton() if @gradingPeriodsEnabled
|
||||
@drawGradingPeriodSelectButton() if @hasGradingPeriods
|
||||
|
||||
$settingsMenu = $('.gradebook_dropdown')
|
||||
showConcludedEnrollmentsEl = $settingsMenu.find("#show_concluded_enrollments")
|
||||
|
@ -1185,11 +1201,11 @@ define [
|
|||
weightedGroups: =>
|
||||
@options.group_weighting_scheme == "percent"
|
||||
|
||||
weightedGrades: =>
|
||||
@options.group_weighting_scheme == "percent" || @gradingPeriodSet?.weighted || false
|
||||
|
||||
displayPointTotals: =>
|
||||
if @weightedGroups()
|
||||
false
|
||||
else
|
||||
@options.show_total_grade_as_points
|
||||
@options.show_total_grade_as_points and not @weightedGrades()
|
||||
|
||||
switchTotalDisplay: =>
|
||||
@options.show_total_grade_as_points = not @options.show_total_grade_as_points
|
||||
|
@ -1648,7 +1664,7 @@ define [
|
|||
currentPeriodId == "0"
|
||||
|
||||
hideAggregateColumns: ->
|
||||
return false unless @gradingPeriodsEnabled
|
||||
return false unless @hasGradingPeriods
|
||||
return false if @options.all_grading_periods_totals
|
||||
selectedPeriodId = @getGradingPeriodToShow()
|
||||
@isAllGradingPeriods(selectedPeriodId)
|
||||
|
|
|
@ -30,7 +30,7 @@ define [
|
|||
constructor: (params) ->
|
||||
@dateRange = params['date_range']
|
||||
@data = params['data']
|
||||
@multipleGradingPeriodsEnabled = params.multipleGradingPeriodsEnabled
|
||||
@hasGradingPeriods = params.hasGradingPeriods
|
||||
@gradingPeriods = params.gradingPeriods
|
||||
@userIsAdmin = params.userIsAdmin
|
||||
@dueDateRequired = params.postToSIS && ENV.DUE_DATE_REQUIRED_FOR_ACCOUNT
|
||||
|
@ -79,7 +79,7 @@ define [
|
|||
dueDateRequired: @dueDateRequired,
|
||||
}
|
||||
|
||||
if @multipleGradingPeriodsEnabled && !@userIsAdmin && @data.persisted == false
|
||||
if @hasGradingPeriods && !@userIsAdmin && @data.persisted == false
|
||||
datetimesToValidate.push {
|
||||
date: dueAt,
|
||||
range: "grading_period_range",
|
||||
|
|
|
@ -159,7 +159,7 @@ define [
|
|||
dateValidator = new DateValidator(
|
||||
date_range: _.extend({}, validRange)
|
||||
data: data
|
||||
multipleGradingPeriodsEnabled: !!ENV.MULTIPLE_GRADING_PERIODS_ENABLED
|
||||
hasGradingPeriods: !!ENV.HAS_GRADING_PERIODS
|
||||
gradingPeriods: GradingPeriodsAPI.deserializePeriods(ENV.active_grading_periods)
|
||||
userIsAdmin: @currentUserIsAdmin(),
|
||||
data
|
||||
|
|
|
@ -44,7 +44,7 @@ define [
|
|||
defaultSectionId: @model.defaultDueDateSectionId,
|
||||
selectedGroupSetId: @model.assignment.get("group_category_id"),
|
||||
gradingPeriods: @gradingPeriods,
|
||||
multipleGradingPeriodsEnabled: @multipleGradingPeriodsEnabled,
|
||||
hasGradingPeriods: @hasGradingPeriods,
|
||||
isOnlyVisibleToOverrides: @model.assignment.isOnlyVisibleToOverrides(),
|
||||
dueAt: tz.parse(@model.assignment.get("due_at"))
|
||||
})
|
||||
|
@ -53,7 +53,7 @@ define [
|
|||
|
||||
gradingPeriods: GradingPeriodsAPI.deserializePeriods(ENV.active_grading_periods)
|
||||
|
||||
multipleGradingPeriodsEnabled: !!ENV.MULTIPLE_GRADING_PERIODS_ENABLED
|
||||
hasGradingPeriods: !!ENV.HAS_GRADING_PERIODS
|
||||
|
||||
validateBeforeSave: (data, errors) =>
|
||||
return errors unless data
|
||||
|
@ -69,7 +69,7 @@ define [
|
|||
dateValidator = new DateValidator({
|
||||
date_range: _.extend({}, ENV.VALID_DATE_RANGE)
|
||||
data: override
|
||||
multipleGradingPeriodsEnabled: @multipleGradingPeriodsEnabled
|
||||
hasGradingPeriods: @hasGradingPeriods
|
||||
gradingPeriods: @gradingPeriods
|
||||
userIsAdmin: _.contains(ENV.current_user_roles, "admin"),
|
||||
postToSIS: @options.postToSIS || data.postToSIS == '1'
|
||||
|
|
|
@ -104,7 +104,7 @@ define [
|
|||
filterResults: =>
|
||||
term = $('#search_term').val()
|
||||
gradingPeriod = null
|
||||
if ENV.MULTIPLE_GRADING_PERIODS_ENABLED
|
||||
if ENV.HAS_GRADING_PERIODS
|
||||
gradingPeriodIndex = $("#grading_period_selector").val()
|
||||
gradingPeriod = @gradingPeriods[parseInt(gradingPeriodIndex)] if gradingPeriodIndex != "all"
|
||||
@saveSelectedGradingPeriod(gradingPeriod)
|
||||
|
|
|
@ -235,11 +235,10 @@ class ApplicationController < ActionController::Base
|
|||
end
|
||||
helper_method :k12?
|
||||
|
||||
def multiple_grading_periods?
|
||||
account_and_grading_periods_allowed? ||
|
||||
context_grading_periods_enabled?
|
||||
def grading_periods?
|
||||
!!@context.try(:grading_periods?)
|
||||
end
|
||||
helper_method :multiple_grading_periods?
|
||||
helper_method :grading_periods?
|
||||
|
||||
def master_courses?
|
||||
@domain_root_account && @domain_root_account.feature_enabled?(:master_courses)
|
||||
|
@ -277,18 +276,6 @@ class ApplicationController < ActionController::Base
|
|||
end
|
||||
private :tool_dimensions
|
||||
|
||||
def account_and_grading_periods_allowed?
|
||||
@context.is_a?(Account) &&
|
||||
@context.feature_allowed?(:multiple_grading_periods)
|
||||
end
|
||||
private :account_and_grading_periods_allowed?
|
||||
|
||||
def context_grading_periods_enabled?
|
||||
@context.present? &&
|
||||
@context.feature_enabled?(:multiple_grading_periods)
|
||||
end
|
||||
private :context_grading_periods_enabled?
|
||||
|
||||
# Reject the request by halting the execution of the current handler
|
||||
# and returning a helpful error message (and HTTP status code).
|
||||
#
|
||||
|
@ -2050,7 +2037,7 @@ class ApplicationController < ActionController::Base
|
|||
},
|
||||
:POST_TO_SIS => Assignment.sis_grade_export_enabled?(@context),
|
||||
:PERMISSIONS => permissions,
|
||||
:MULTIPLE_GRADING_PERIODS_ENABLED => @context.feature_enabled?(:multiple_grading_periods),
|
||||
:HAS_GRADING_PERIODS => @context.grading_periods?,
|
||||
:VALID_DATE_RANGE => CourseDateRange.new(@context),
|
||||
:assignment_menu_tools => external_tools_display_hashes(:assignment_menu),
|
||||
:discussion_topic_menu_tools => external_tools_display_hashes(:discussion_topic_menu),
|
||||
|
@ -2061,7 +2048,7 @@ class ApplicationController < ActionController::Base
|
|||
|
||||
conditional_release_js_env(includes: :active_rules)
|
||||
|
||||
if @context.feature_enabled?(:multiple_grading_periods)
|
||||
if @context.grading_periods?
|
||||
js_env(:active_grading_periods => GradingPeriod.json_for(@context, @current_user))
|
||||
end
|
||||
end
|
||||
|
|
|
@ -37,7 +37,7 @@ class AssignmentGroupsApiController < ApplicationController
|
|||
#
|
||||
# @argument grading_period_id [Integer]
|
||||
# The id of the grading period in which assignment groups are being requested
|
||||
# (Requires the Multiple Grading Periods account feature turned on)
|
||||
# (Requires grading periods to exist on the account)
|
||||
#
|
||||
# @returns AssignmentGroup
|
||||
def show
|
||||
|
@ -45,7 +45,7 @@ class AssignmentGroupsApiController < ApplicationController
|
|||
includes = Array(params[:include])
|
||||
override_dates = value_to_boolean(params[:override_assignment_dates] || true)
|
||||
assignments = @assignment_group.visible_assignments(@current_user)
|
||||
if params[:grading_period_id].present? && multiple_grading_periods?
|
||||
if params[:grading_period_id].present?
|
||||
assignments = GradingPeriod.for(@context).find_by(id: params[:grading_period_id]).assignments(assignments)
|
||||
end
|
||||
if assignments.any? && includes.include?('submission')
|
||||
|
|
|
@ -115,14 +115,14 @@ class AssignmentGroupsController < ApplicationController
|
|||
#
|
||||
# @argument grading_period_id [Integer]
|
||||
# The id of the grading period in which assignment groups are being requested
|
||||
# (Requires the Multiple Grading Periods feature turned on.)
|
||||
# (Requires grading periods to exist.)
|
||||
#
|
||||
# @argument scope_assignments_to_student [Boolean]
|
||||
# If true, all assignments returned will apply to the current user in the
|
||||
# specified grading period. If assignments apply to other students in the
|
||||
# specified grading period, but not the current user, they will not be
|
||||
# returned. (Requires the grading_period_id argument and the Multiple Grading
|
||||
# Periods feature turned on. In addition, the current user must be a student.)
|
||||
# returned. (Requires the grading_period_id argument and grading periods to
|
||||
# exist. In addition, the current user must be a student.)
|
||||
#
|
||||
# @returns [AssignmentGroup]
|
||||
def index
|
||||
|
@ -295,8 +295,7 @@ class AssignmentGroupsController < ApplicationController
|
|||
end
|
||||
|
||||
def filter_by_grading_period?
|
||||
return false if all_grading_periods_selected?
|
||||
params[:grading_period_id].present? && multiple_grading_periods?
|
||||
params[:grading_period_id].present? && !all_grading_periods_selected?
|
||||
end
|
||||
|
||||
def all_grading_periods_selected?
|
||||
|
@ -390,7 +389,7 @@ class AssignmentGroupsController < ApplicationController
|
|||
|
||||
assignments = assignments.with_student_submission_count.all
|
||||
|
||||
if params[:grading_period_id].present? && multiple_grading_periods?
|
||||
if filter_by_grading_period?
|
||||
assignments = filter_assignments_by_grading_period(assignments, context)
|
||||
end
|
||||
|
||||
|
@ -427,7 +426,7 @@ class AssignmentGroupsController < ApplicationController
|
|||
end
|
||||
|
||||
def can_reorder_assignments?(assignments, group)
|
||||
return true unless @context.feature_enabled?(:multiple_grading_periods)
|
||||
return true unless @context.grading_periods?
|
||||
return true if @context.account_membership_allows(@current_user)
|
||||
|
||||
effective_due_dates = EffectiveDueDates.for_course(@context, assignments)
|
||||
|
|
|
@ -443,7 +443,7 @@ class AssignmentsController < ApplicationController
|
|||
GROUP_CATEGORIES: group_categories,
|
||||
HAS_GRADED_SUBMISSIONS: @assignment.graded_submissions_exist?,
|
||||
KALTURA_ENABLED: !!feature_enabled?(:kaltura),
|
||||
MULTIPLE_GRADING_PERIODS_ENABLED: @context.feature_enabled?(:multiple_grading_periods),
|
||||
HAS_GRADING_PERIODS: @context.grading_periods?,
|
||||
PLAGIARISM_DETECTION_PLATFORM: @context.root_account.feature_enabled?(:plagiarism_detection_platform),
|
||||
POST_TO_SIS: post_to_sis,
|
||||
SIS_NAME: AssignmentUtil.post_to_sis_friendly_name(@assignment),
|
||||
|
@ -475,7 +475,7 @@ class AssignmentsController < ApplicationController
|
|||
hash[:SELECTED_CONFIG_TOOL_ID] = selected_tool ? selected_tool.id : nil
|
||||
hash[:SELECTED_CONFIG_TOOL_TYPE] = selected_tool ? selected_tool.class.to_s : nil
|
||||
|
||||
if @context.feature_enabled?(:multiple_grading_periods)
|
||||
if @context.grading_periods?
|
||||
hash[:active_grading_periods] = GradingPeriod.json_for(@context, @current_user)
|
||||
end
|
||||
append_sis_data(hash)
|
||||
|
|
|
@ -359,14 +359,14 @@ class CoursesController < ApplicationController
|
|||
# - "current_grading_period_scores": Optional information to include with
|
||||
# each Course. When current_grading_period_scores is given and total_scores
|
||||
# is given, any student enrollments will also include the fields
|
||||
# 'multiple_grading_periods_enabled',
|
||||
# 'has_grading_periods',
|
||||
# 'totals_for_all_grading_periods_option', 'current_grading_period_title',
|
||||
# 'current_grading_period_id', current_period_computed_current_score',
|
||||
# 'current_period_computed_final_score',
|
||||
# 'current_period_computed_current_grade', and
|
||||
# 'current_period_computed_final_grade' (see Enrollment documentation for
|
||||
# more information on these fields). In addition, when this argument is
|
||||
# passed, the course will have a 'multiple_grading_periods_enabled' attribute
|
||||
# passed, the course will have a 'has_grading_periods' attribute
|
||||
# on it. This argument is ignored if the course is configured to hide final
|
||||
# grades or if the total_scores argument is not included.
|
||||
# - "term": Optional information to include with each Course. When
|
||||
|
@ -474,14 +474,14 @@ class CoursesController < ApplicationController
|
|||
# - "current_grading_period_scores": Optional information to include with
|
||||
# each Course. When current_grading_period_scores is given and total_scores
|
||||
# is given, any student enrollments will also include the fields
|
||||
# 'multiple_grading_periods_enabled',
|
||||
# 'has_grading_periods',
|
||||
# 'totals_for_all_grading_periods_option', 'current_grading_period_title',
|
||||
# 'current_grading_period_id', current_period_computed_current_score',
|
||||
# 'current_period_computed_final_score',
|
||||
# 'current_period_computed_current_grade', and
|
||||
# 'current_period_computed_final_grade' (see Enrollment documentation for
|
||||
# more information on these fields). In addition, when this argument is
|
||||
# passed, the course will have a 'multiple_grading_periods_enabled' attribute
|
||||
# passed, the course will have a 'has_grading_periods' attribute
|
||||
# on it. This argument is ignored if the course is configured to hide final
|
||||
# grades or if the total_scores argument is not included.
|
||||
# - "term": Optional information to include with each Course. When
|
||||
|
@ -2474,9 +2474,9 @@ class CoursesController < ApplicationController
|
|||
|
||||
# @API Get effective due dates
|
||||
# For each assignment in the course, returns each assigned student's ID
|
||||
# and their corresponding due date along with some Multiple Grading Periods
|
||||
# data. Returns a collection with keys representing assignment IDs and values
|
||||
# as a collection containing keys representing student IDs and values representing
|
||||
# and their corresponding due date along with some grading period data.
|
||||
# Returns a collection with keys representing assignment IDs and values as a
|
||||
# collection containing keys representing student IDs and values representing
|
||||
# the student's effective due_at, the grading_period_id of which the due_at falls
|
||||
# in, and whether or not the grading period is closed (in_closed_grading_period)
|
||||
#
|
||||
|
@ -2805,7 +2805,7 @@ class CoursesController < ApplicationController
|
|||
end
|
||||
|
||||
def can_change_group_weighting_scheme?
|
||||
return true unless @course.feature_enabled?(:multiple_grading_periods)
|
||||
return true unless @course.grading_periods?
|
||||
return true if @course.account_membership_allows(@current_user)
|
||||
!@course.any_assignment_in_closed_grading_period?
|
||||
end
|
||||
|
|
|
@ -448,7 +448,7 @@ class DiscussionTopicsController < ApplicationController
|
|||
GROUP_CATEGORIES: categories.
|
||||
reject(&:student_organized?).
|
||||
map { |category| { id: category.id, name: category.name } },
|
||||
MULTIPLE_GRADING_PERIODS_ENABLED: @context.feature_enabled?(:multiple_grading_periods),
|
||||
HAS_GRADING_PERIODS: @context.grading_periods?,
|
||||
SECTION_LIST: sections.map { |section| { id: section.id, name: section.name } }
|
||||
}
|
||||
|
||||
|
@ -479,7 +479,7 @@ class DiscussionTopicsController < ApplicationController
|
|||
js_hash[:CANCEL_TO] = cancel_redirect_url
|
||||
append_sis_data(js_hash)
|
||||
|
||||
if @context.feature_enabled?(:multiple_grading_periods)
|
||||
if @context.grading_periods?
|
||||
gp_context = @context.is_a?(Group) ? @context.context : @context
|
||||
js_hash[:active_grading_periods] = GradingPeriod.json_for(gp_context, @current_user)
|
||||
end
|
||||
|
|
|
@ -207,8 +207,8 @@
|
|||
# "example": "B-",
|
||||
# "type": "string"
|
||||
# },
|
||||
# "multiple_grading_periods_enabled": {
|
||||
# "description": "optional: Indicates whether the course the enrollment belongs to has the Multiple Grading Periods feature enabled. (applies only to student enrollments, and only available in course endpoints)",
|
||||
# "has_grading_periods": {
|
||||
# "description": "optional: Indicates whether the course the enrollment belongs to has grading periods set up. (applies only to student enrollments, and only available in course endpoints)",
|
||||
# "example": true,
|
||||
# "type": "boolean"
|
||||
# },
|
||||
|
@ -218,32 +218,32 @@
|
|||
# "type": "boolean"
|
||||
# },
|
||||
# "current_grading_period_title": {
|
||||
# "description": "optional: The name of the currently active grading period, if one exists. If the course the enrollment belongs to does not have Multiple Grading Periods enabled, or if no currently active grading period exists, the value will be null. (applies only to student enrollments, and only available in course endpoints)",
|
||||
# "description": "optional: The name of the currently active grading period, if one exists. If the course the enrollment belongs to does not have grading periods, or if no currently active grading period exists, the value will be null. (applies only to student enrollments, and only available in course endpoints)",
|
||||
# "example": "Fall Grading Period",
|
||||
# "type": "string"
|
||||
# },
|
||||
# "current_grading_period_id": {
|
||||
# "description": "optional: The id of the currently active grading period, if one exists. If the course the enrollment belongs to does not have Multiple Grading Periods enabled, or if no currently active grading period exists, the value will be null. (applies only to student enrollments, and only available in course endpoints)",
|
||||
# "description": "optional: The id of the currently active grading period, if one exists. If the course the enrollment belongs to does not have grading periods, or if no currently active grading period exists, the value will be null. (applies only to student enrollments, and only available in course endpoints)",
|
||||
# "example": 5,
|
||||
# "type": "integer"
|
||||
# },
|
||||
# "current_period_computed_current_score": {
|
||||
# "description": "optional: The student's score in the course for the current grading period, ignoring ungraded assignments. If the course the enrollment belongs to does not have Multiple Grading Periods enabled, or if no currently active grading period exists, the value will be null. (applies only to student enrollments, and only available in course endpoints)",
|
||||
# "description": "optional: The student's score in the course for the current grading period, ignoring ungraded assignments. If the course the enrollment belongs to does not have grading periods, or if no currently active grading period exists, the value will be null. (applies only to student enrollments, and only available in course endpoints)",
|
||||
# "example": 95.80,
|
||||
# "type": "number"
|
||||
# },
|
||||
# "current_period_computed_final_score": {
|
||||
# "description": "optional: The student's score in the course for the current grading period, including ungraded assignments with a score of 0. If the course the enrollment belongs to does not have Multiple Grading Periods enabled, or if no currently active grading period exists, the value will be null. (applies only to student enrollments, and only available in course endpoints)",
|
||||
# "description": "optional: The student's score in the course for the current grading period, including ungraded assignments with a score of 0. If the course the enrollment belongs to does not have grading periods, or if no currently active grading period exists, the value will be null. (applies only to student enrollments, and only available in course endpoints)",
|
||||
# "example": 85.25,
|
||||
# "type": "number"
|
||||
# },
|
||||
# "current_period_computed_current_grade": {
|
||||
# "description": "optional: The letter grade equivalent of current_period_computed_current_score, if available. If the course the enrollment belongs to does not have Multiple Grading Periods enabled, or if no currently active grading period exists, the value will be null. (applies only to student enrollments, and only available in course endpoints)",
|
||||
# "description": "optional: The letter grade equivalent of current_period_computed_current_score, if available. If the course the enrollment belongs to does not have grading periods, or if no currently active grading period exists, the value will be null. (applies only to student enrollments, and only available in course endpoints)",
|
||||
# "example": "A",
|
||||
# "type": "string"
|
||||
# },
|
||||
# "current_period_computed_final_grade": {
|
||||
# "description": "optional: The letter grade equivalent of current_period_computed_final_score, if available. If the course the enrollment belongs to does not have Multiple Grading Periods enabled, or if no currently active grading period exists, the value will be null. (applies only to student enrollments, and only available in course endpoints)",
|
||||
# "description": "optional: The letter grade equivalent of current_period_computed_final_score, if available. If the course the enrollment belongs to does not have grading periods, or if no currently active grading period exists, the value will be null. (applies only to student enrollments, and only available in course endpoints)",
|
||||
# "example": "B",
|
||||
# "type": "string"
|
||||
# }
|
||||
|
@ -262,7 +262,6 @@ class EnrollmentsApiController < ApplicationController
|
|||
:inactive_role => 'Cannot create an enrollment with this role because it is inactive.',
|
||||
:base_type_mismatch => 'The specified type must match the base type for the role',
|
||||
:concluded_course => 'Can\'t add an enrollment to a concluded course.',
|
||||
:multiple_grading_periods_disabled => 'Multiple Grading Periods feature is disabled. Cannot filter by grading_period_id with this feature disabled',
|
||||
:insufficient_sis_permissions => 'Insufficient permissions to filter by SIS fields'
|
||||
}
|
||||
|
||||
|
@ -371,20 +370,10 @@ class EnrollmentsApiController < ApplicationController
|
|||
|
||||
if params[:grading_period_id].present?
|
||||
if @context.is_a? User
|
||||
unless @context.account.feature_enabled?(:multiple_grading_periods)
|
||||
render_create_errors([@@errors[:multiple_grading_periods_disabled]])
|
||||
return false
|
||||
end
|
||||
|
||||
grading_period = @context.courses.lazy.map do |course|
|
||||
GradingPeriod.for(course).find_by(id: params[:grading_period_id])
|
||||
end.detect(&:present?)
|
||||
else
|
||||
unless multiple_grading_periods?
|
||||
render_create_errors([@@errors[:multiple_grading_periods_disabled]])
|
||||
return false
|
||||
end
|
||||
|
||||
grading_period = GradingPeriod.for(@context).find_by(id: params[:grading_period_id])
|
||||
end
|
||||
|
||||
|
|
|
@ -1,11 +0,0 @@
|
|||
module Filters::GradingPeriods
|
||||
def check_feature_flag
|
||||
unless multiple_grading_periods?
|
||||
if api_request?
|
||||
render json: {message: t('Page not found')}, status: :not_found
|
||||
else
|
||||
render status: 404, template: "shared/errors/404_message"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -36,7 +36,7 @@ class GradebooksController < ApplicationController
|
|||
MAX_POST_GRADES_TOOLS = 10
|
||||
|
||||
def grade_summary
|
||||
set_current_grading_period if multiple_grading_periods?
|
||||
set_current_grading_period if grading_periods?
|
||||
@presenter = grade_summary_presenter
|
||||
# do this as the very first thing, if the current user is a
|
||||
# teacher in the course and they are not trying to view another
|
||||
|
@ -59,9 +59,10 @@ class GradebooksController < ApplicationController
|
|||
add_crumb(@presenter.student_name, named_context_url(@context, :context_student_grades_url,
|
||||
@presenter.student_id))
|
||||
gp_id = nil
|
||||
if multiple_grading_periods?
|
||||
if grading_periods?
|
||||
@grading_periods = active_grading_periods_json
|
||||
gp_id = @current_grading_period_id unless view_all_grading_periods?
|
||||
effective_due_dates = EffectiveDueDates.new(@context).to_hash
|
||||
end
|
||||
|
||||
@exclude_total = exclude_total?(@context)
|
||||
|
@ -89,7 +90,6 @@ class GradebooksController < ApplicationController
|
|||
|
||||
grading_period = @grading_periods && @grading_periods.find { |period| period[:id] == gp_id }
|
||||
|
||||
|
||||
ags_json = light_weight_ags_json(@presenter.groups, {student: @presenter.student})
|
||||
|
||||
grading_scheme = @context.grading_standard.try(:data) ||
|
||||
|
@ -100,7 +100,10 @@ class GradebooksController < ApplicationController
|
|||
group_weighting_scheme: @context.group_weighting_scheme,
|
||||
show_total_grade_as_points: @context.settings[:show_total_grade_as_points],
|
||||
grading_scheme: grading_scheme,
|
||||
grading_period_set: grading_period_group_json,
|
||||
grading_period: grading_period,
|
||||
grading_periods: @grading_periods,
|
||||
effective_due_dates: effective_due_dates,
|
||||
exclude_total: @exclude_total,
|
||||
student_outcome_gradebook_enabled: @context.feature_enabled?(:student_outcome_gradebook),
|
||||
student_id: @presenter.student_id)
|
||||
|
@ -124,7 +127,7 @@ class GradebooksController < ApplicationController
|
|||
assignment_groups.map do |ag|
|
||||
visible_assignments = ag.visible_assignments(opts[:student] || @current_user).to_a
|
||||
|
||||
if multiple_grading_periods? && @current_grading_period_id && !view_all_grading_periods?
|
||||
if grading_periods? && @current_grading_period_id && !view_all_grading_periods?
|
||||
current_period = GradingPeriod.for(@context).find_by(id: @current_grading_period_id)
|
||||
visible_assignments = current_period.assignments_for_student(visible_assignments, opts[:student])
|
||||
end
|
||||
|
@ -193,7 +196,7 @@ class GradebooksController < ApplicationController
|
|||
def show
|
||||
if authorized_action(@context, @current_user, [:manage_grades, :view_all_grades])
|
||||
@last_exported_gradebook_csv = GradebookCsv.last_successful_export(course: @context, user: @current_user)
|
||||
set_current_grading_period if multiple_grading_periods?
|
||||
set_current_grading_period if grading_periods?
|
||||
set_js_env
|
||||
@course_is_concluded = @context.completed?
|
||||
@post_grades_tools = post_grades_tools
|
||||
|
@ -289,27 +292,34 @@ class GradebooksController < ApplicationController
|
|||
@current_grading_period_id == 0
|
||||
end
|
||||
|
||||
def grading_period_group
|
||||
return @grading_period_group if defined? @grading_period_group
|
||||
|
||||
@grading_period_group = active_grading_periods.first&.grading_period_group
|
||||
end
|
||||
|
||||
def active_grading_periods
|
||||
@active_grading_periods ||= GradingPeriod.for(@context).sort_by(&:start_date)
|
||||
end
|
||||
|
||||
def grading_period_group_json
|
||||
return @grading_period_group_json if defined? @grading_period_group_json
|
||||
return @grading_period_group_json = nil unless grading_period_group.present?
|
||||
|
||||
@grading_period_group_json = grading_period_group
|
||||
.as_json
|
||||
.fetch(:grading_period_group)
|
||||
.merge(grading_periods: active_grading_periods_json)
|
||||
end
|
||||
|
||||
def active_grading_periods_json
|
||||
@agp_json ||= GradingPeriod.periods_json(active_grading_periods, @current_user)
|
||||
end
|
||||
|
||||
def latest_end_date_of_admin_created_grading_periods_in_the_past
|
||||
periods = active_grading_periods.select do |period|
|
||||
admin_created = period.account_group?
|
||||
admin_created && period.end_date.past?
|
||||
end
|
||||
periods.map(&:end_date).compact.sort.last
|
||||
end
|
||||
private :latest_end_date_of_admin_created_grading_periods_in_the_past
|
||||
|
||||
def set_js_env
|
||||
@gradebook_is_editable = @context.grants_right?(@current_user, session, :manage_grades)
|
||||
per_page = Setting.get('api_max_per_page', '50').to_i
|
||||
teacher_notes = @context.custom_gradebook_columns.not_deleted.where(:teacher_notes=> true).first
|
||||
teacher_notes = @context.custom_gradebook_columns.not_deleted.where(teacher_notes: true).first
|
||||
ag_includes = [:assignments, :assignment_visibility]
|
||||
chunk_size = if @context.assignments.published.count < Setting.get('gradebook2.assignments_threshold', '20').to_i
|
||||
Setting.get('gradebook2.submissions_chunk_size', '35').to_i
|
||||
|
@ -317,82 +327,88 @@ class GradebooksController < ApplicationController
|
|||
Setting.get('gradebook2.many_submissions_chunk_size', '10').to_i
|
||||
end
|
||||
js_env STUDENT_CONTEXT_CARDS_ENABLED: @domain_root_account.feature_enabled?(:student_context_cards)
|
||||
js_env :GRADEBOOK_OPTIONS => {
|
||||
:gradezilla => @context.root_account.feature_enabled?(:gradezilla),
|
||||
:chunk_size => chunk_size,
|
||||
:assignment_groups_url => api_v1_course_assignment_groups_url(
|
||||
js_env GRADEBOOK_OPTIONS: {
|
||||
gradezilla: @context.root_account.feature_enabled?(:gradezilla),
|
||||
chunk_size: chunk_size,
|
||||
assignment_groups_url: api_v1_course_assignment_groups_url(
|
||||
@context,
|
||||
include: ag_includes,
|
||||
override_assignment_dates: "false",
|
||||
exclude_assignment_submission_types: ['wiki_page']
|
||||
),
|
||||
:sections_url => api_v1_course_sections_url(@context),
|
||||
:course_url => api_v1_course_url(@context),
|
||||
:effective_due_dates_url => api_v1_course_effective_due_dates_url(@context),
|
||||
:enrollments_url => custom_course_enrollments_api_url(per_page: per_page),
|
||||
:enrollments_with_concluded_url =>
|
||||
sections_url: api_v1_course_sections_url(@context),
|
||||
course_url: api_v1_course_url(@context),
|
||||
effective_due_dates_url: api_v1_course_effective_due_dates_url(@context),
|
||||
enrollments_url: custom_course_enrollments_api_url(per_page: per_page),
|
||||
enrollments_with_concluded_url:
|
||||
custom_course_enrollments_api_url(include_concluded: true, per_page: per_page),
|
||||
:enrollments_with_inactive_url =>
|
||||
enrollments_with_inactive_url:
|
||||
custom_course_enrollments_api_url(include_inactive: true, per_page: per_page),
|
||||
:enrollments_with_concluded_and_inactive_url =>
|
||||
enrollments_with_concluded_and_inactive_url:
|
||||
custom_course_enrollments_api_url(include_concluded: true, include_inactive: true, per_page: per_page),
|
||||
:students_url => custom_course_users_api_url(per_page: per_page),
|
||||
:students_with_concluded_enrollments_url =>
|
||||
students_url: custom_course_users_api_url(per_page: per_page),
|
||||
students_with_concluded_enrollments_url:
|
||||
custom_course_users_api_url(include_concluded: true, per_page: per_page),
|
||||
:students_with_inactive_enrollments_url =>
|
||||
students_with_inactive_enrollments_url:
|
||||
custom_course_users_api_url(include_inactive: true, per_page: per_page),
|
||||
:students_with_concluded_and_inactive_enrollments_url =>
|
||||
students_with_concluded_and_inactive_enrollments_url:
|
||||
custom_course_users_api_url(include_concluded: true, include_inactive: true, per_page: per_page),
|
||||
:submissions_url => api_v1_course_student_submissions_url(@context, :grouped => '1'),
|
||||
:outcome_links_url => api_v1_course_outcome_group_links_url(@context, :outcome_style => :full),
|
||||
:outcome_rollups_url => api_v1_course_outcome_rollups_url(@context, :per_page => 100),
|
||||
:change_grade_url => api_v1_course_assignment_submission_url(@context, ":assignment", ":submission", :include =>[:visibility]),
|
||||
:context_url => named_context_url(@context, :context_url),
|
||||
:download_assignment_submissions_url => named_context_url(@context, :context_assignment_submissions_url, "{{ assignment_id }}", :zip => 1),
|
||||
:re_upload_submissions_url => named_context_url(@context, :submissions_upload_context_gradebook_url, "{{ assignment_id }}"),
|
||||
:context_id => @context.id.to_s,
|
||||
:context_code => @context.asset_string,
|
||||
:context_sis_id => @context.sis_source_id,
|
||||
:group_weighting_scheme => @context.group_weighting_scheme,
|
||||
:grading_standard => @context.grading_standard_enabled? && (@context.grading_standard.try(:data) || GradingStandard.default_grading_standard),
|
||||
:course_is_concluded => @context.completed?,
|
||||
:course_name => @context.name,
|
||||
:gradebook_is_editable => @gradebook_is_editable,
|
||||
:context_allows_gradebook_uploads => @context.allows_gradebook_uploads?,
|
||||
:gradebook_import_url => new_course_gradebook_upload_path(@context),
|
||||
:setting_update_url => api_v1_course_settings_url(@context),
|
||||
:show_total_grade_as_points => @context.settings[:show_total_grade_as_points],
|
||||
:publish_to_sis_enabled => @context.allows_grade_publishing_by(@current_user) && @gradebook_is_editable,
|
||||
:publish_to_sis_url => context_url(@context, :context_details_url, :anchor => 'tab-grade-publishing'),
|
||||
:speed_grader_enabled => @context.allows_speed_grader?,
|
||||
:multiple_grading_periods_enabled => multiple_grading_periods?,
|
||||
:active_grading_periods => active_grading_periods_json,
|
||||
:latest_end_date_of_admin_created_grading_periods_in_the_past => latest_end_date_of_admin_created_grading_periods_in_the_past,
|
||||
:current_grading_period_id => @current_grading_period_id,
|
||||
:outcome_gradebook_enabled => @context.feature_enabled?(:outcome_gradebook),
|
||||
:custom_columns_url => api_v1_course_custom_gradebook_columns_url(@context),
|
||||
:custom_column_url => api_v1_course_custom_gradebook_column_url(@context, ":id"),
|
||||
:custom_column_data_url => api_v1_course_custom_gradebook_column_data_url(@context, ":id", per_page: per_page),
|
||||
:custom_column_datum_url => api_v1_course_custom_gradebook_column_datum_url(@context, ":id", ":user_id"),
|
||||
:reorder_custom_columns_url => api_v1_custom_gradebook_columns_reorder_url(@context),
|
||||
:teacher_notes => teacher_notes && custom_gradebook_column_json(teacher_notes, @current_user, session),
|
||||
:change_gradebook_version_url => context_url(@context, :change_gradebook_version_context_gradebook_url, :version => 2),
|
||||
:export_gradebook_csv_url => course_gradebook_csv_url,
|
||||
:gradebook_csv_progress => @last_exported_gradebook_csv.try(:progress),
|
||||
:attachment_url => @last_exported_gradebook_csv.try(:attachment).try(:download_url),
|
||||
:attachment => @last_exported_gradebook_csv.try(:attachment),
|
||||
:sis_app_url => Setting.get('sis_app_url', nil),
|
||||
:sis_app_token => Setting.get('sis_app_token', nil),
|
||||
:list_students_by_sortable_name_enabled => @context.list_students_by_sortable_name?,
|
||||
:gradebook_column_size_settings => @current_user.preferences[:gradebook_column_size],
|
||||
:gradebook_column_size_settings_url => change_gradebook_column_size_course_gradebook_url,
|
||||
:gradebook_column_order_settings => @current_user.preferences[:gradebook_column_order].try(:[], @context.id),
|
||||
:gradebook_column_order_settings_url => save_gradebook_column_order_course_gradebook_url,
|
||||
:all_grading_periods_totals => @context.feature_enabled?(:all_grading_periods_totals),
|
||||
:sections => sections_json(@context.active_course_sections, @current_user, session),
|
||||
:settings_update_url => api_v1_course_gradebook_settings_update_url(@context),
|
||||
:settings => @current_user.preferences.fetch(:gradebook_settings, {}).fetch(@context.id, {}),
|
||||
:version => params.fetch(:version, nil)
|
||||
submissions_url: api_v1_course_student_submissions_url(@context, grouped: '1'),
|
||||
outcome_links_url: api_v1_course_outcome_group_links_url(@context, outcome_style: :full),
|
||||
outcome_rollups_url: api_v1_course_outcome_rollups_url(@context, per_page: 100),
|
||||
change_grade_url:
|
||||
api_v1_course_assignment_submission_url(@context, ":assignment", ":submission", include: [:visibility]),
|
||||
context_url: named_context_url(@context, :context_url),
|
||||
download_assignment_submissions_url:
|
||||
named_context_url(@context, :context_assignment_submissions_url, "{{ assignment_id }}", zip: 1),
|
||||
re_upload_submissions_url:
|
||||
named_context_url(@context, :submissions_upload_context_gradebook_url, "{{ assignment_id }}"),
|
||||
context_id: @context.id.to_s,
|
||||
context_code: @context.asset_string,
|
||||
context_sis_id: @context.sis_source_id,
|
||||
group_weighting_scheme: @context.group_weighting_scheme,
|
||||
grading_standard: (
|
||||
@context.grading_standard_enabled? &&
|
||||
(@context.grading_standard.try(:data) || GradingStandard.default_grading_standard)
|
||||
),
|
||||
course_is_concluded: @context.completed?,
|
||||
course_name: @context.name,
|
||||
gradebook_is_editable: @gradebook_is_editable,
|
||||
context_allows_gradebook_uploads: @context.allows_gradebook_uploads?,
|
||||
gradebook_import_url: new_course_gradebook_upload_path(@context),
|
||||
setting_update_url: api_v1_course_settings_url(@context),
|
||||
show_total_grade_as_points: @context.settings[:show_total_grade_as_points],
|
||||
publish_to_sis_enabled: @context.allows_grade_publishing_by(@current_user) && @gradebook_is_editable,
|
||||
publish_to_sis_url: context_url(@context, :context_details_url, anchor: 'tab-grade-publishing'),
|
||||
speed_grader_enabled: @context.allows_speed_grader?,
|
||||
has_grading_periods: grading_periods?,
|
||||
active_grading_periods: active_grading_periods_json,
|
||||
grading_period_set: grading_period_group_json,
|
||||
current_grading_period_id: @current_grading_period_id,
|
||||
outcome_gradebook_enabled: @context.feature_enabled?(:outcome_gradebook),
|
||||
custom_columns_url: api_v1_course_custom_gradebook_columns_url(@context),
|
||||
custom_column_url: api_v1_course_custom_gradebook_column_url(@context, ":id"),
|
||||
custom_column_data_url: api_v1_course_custom_gradebook_column_data_url(@context, ":id", per_page: per_page),
|
||||
custom_column_datum_url: api_v1_course_custom_gradebook_column_datum_url(@context, ":id", ":user_id"),
|
||||
reorder_custom_columns_url: api_v1_custom_gradebook_columns_reorder_url(@context),
|
||||
teacher_notes: teacher_notes && custom_gradebook_column_json(teacher_notes, @current_user, session),
|
||||
change_gradebook_version_url: context_url(@context, :change_gradebook_version_context_gradebook_url, version: 2),
|
||||
export_gradebook_csv_url: course_gradebook_csv_url,
|
||||
gradebook_csv_progress: @last_exported_gradebook_csv.try(:progress),
|
||||
attachment_url: @last_exported_gradebook_csv.try(:attachment).try(:download_url),
|
||||
attachment: @last_exported_gradebook_csv.try(:attachment),
|
||||
sis_app_url: Setting.get('sis_app_url', nil),
|
||||
sis_app_token: Setting.get('sis_app_token', nil),
|
||||
list_students_by_sortable_name_enabled: @context.list_students_by_sortable_name?,
|
||||
gradebook_column_size_settings: @current_user.preferences[:gradebook_column_size],
|
||||
gradebook_column_size_settings_url: change_gradebook_column_size_course_gradebook_url,
|
||||
gradebook_column_order_settings: @current_user.preferences[:gradebook_column_order].try(:[], @context.id),
|
||||
gradebook_column_order_settings_url: save_gradebook_column_order_course_gradebook_url,
|
||||
all_grading_periods_totals: @context.feature_enabled?(:all_grading_periods_totals),
|
||||
sections: sections_json(@context.active_course_sections, @current_user, session),
|
||||
settings_update_url: api_v1_course_gradebook_settings_update_url(@context),
|
||||
settings: @current_user.preferences.fetch(:gradebook_settings, {}).fetch(@context.id, {}),
|
||||
version: params.fetch(:version, nil)
|
||||
}
|
||||
end
|
||||
|
||||
|
@ -752,7 +768,7 @@ class GradebooksController < ApplicationController
|
|||
return true if context.hide_final_grades
|
||||
|
||||
all_grading_periods_selected =
|
||||
multiple_grading_periods? && view_all_grading_periods?
|
||||
grading_periods? && view_all_grading_periods?
|
||||
hide_all_grading_periods_totals = !context.feature_enabled?(:all_grading_periods_totals)
|
||||
all_grading_periods_selected && hide_all_grading_periods_totals
|
||||
end
|
||||
|
@ -787,7 +803,7 @@ class GradebooksController < ApplicationController
|
|||
options = {}
|
||||
return options unless @context.present?
|
||||
|
||||
if @current_grading_period_id.present? && !view_all_grading_periods? && multiple_grading_periods?
|
||||
if @current_grading_period_id.present? && !view_all_grading_periods? && grading_periods?
|
||||
options[:grading_period_id] = @current_grading_period_id
|
||||
end
|
||||
|
||||
|
|
|
@ -1,9 +1,6 @@
|
|||
class GradingPeriodSetsController < ApplicationController
|
||||
include ::Filters::GradingPeriods
|
||||
|
||||
before_action :require_user
|
||||
before_action :get_context
|
||||
before_action :check_feature_flag
|
||||
before_action :check_manage_rights, except: [:index]
|
||||
before_action :check_read_rights, except: [:update, :create, :destroy]
|
||||
|
||||
|
@ -42,7 +39,11 @@ class GradingPeriodSetsController < ApplicationController
|
|||
end
|
||||
|
||||
def update
|
||||
old_term_ids = grading_period_set.enrollment_terms.pluck(:id)
|
||||
grading_period_set.enrollment_terms = enrollment_terms
|
||||
# we need to recompute scores for enrollment terms that were removed since the line above
|
||||
# will not run callbacks for the removed enrollment terms
|
||||
EnrollmentTerm.where(id: old_term_ids - enrollment_terms.map(&:id)).each(&:recompute_course_scores)
|
||||
|
||||
respond_to do |format|
|
||||
if grading_period_set.update(set_params)
|
||||
|
@ -74,7 +75,7 @@ class GradingPeriodSetsController < ApplicationController
|
|||
end
|
||||
|
||||
def set_params
|
||||
params.require(:grading_period_set).permit(:title)
|
||||
params.require(:grading_period_set).permit(:title, :weighted)
|
||||
end
|
||||
|
||||
def check_read_rights
|
||||
|
|
|
@ -61,11 +61,8 @@
|
|||
# }
|
||||
#
|
||||
class GradingPeriodsController < ApplicationController
|
||||
include ::Filters::GradingPeriods
|
||||
|
||||
before_action :require_user
|
||||
before_action :get_context
|
||||
before_action :check_feature_flag
|
||||
|
||||
# @API List grading periods
|
||||
# @beta
|
||||
|
@ -240,6 +237,7 @@ class GradingPeriodsController < ApplicationController
|
|||
|
||||
set_subquery = GradingPeriodGroup.active.select(:account_id).where(id: params[:set_id])
|
||||
@context = Account.active.where(id: set_subquery).take
|
||||
render json: {message: t('Page not found')}, status: :not_found unless @context
|
||||
end
|
||||
|
||||
# model level validations
|
||||
|
@ -325,11 +323,6 @@ class GradingPeriodsController < ApplicationController
|
|||
def index_permissions
|
||||
can_create_grading_periods = @context.is_a?(Account) &&
|
||||
@context.root_account? && @context.grants_right?(@current_user, :manage)
|
||||
can_toggle_grading_periods = @domain_root_account.grants_right?(@current_user, :manage) ||
|
||||
@context.feature_allowed?(:multiple_grading_periods, exclude_enabled: true)
|
||||
{
|
||||
can_create_grading_periods: can_create_grading_periods,
|
||||
can_toggle_grading_periods: can_toggle_grading_periods
|
||||
}.as_json
|
||||
{can_create_grading_periods: can_create_grading_periods}.as_json
|
||||
end
|
||||
end
|
||||
|
|
|
@ -30,7 +30,7 @@ class GradingStandardsController < ApplicationController
|
|||
GRADING_STANDARDS_URL: context_url(@context, :context_grading_standards_url),
|
||||
GRADING_PERIOD_SETS_URL: api_v1_account_grading_period_sets_url(@context),
|
||||
ENROLLMENT_TERMS_URL: api_v1_enrollment_terms_url(@context),
|
||||
MULTIPLE_GRADING_PERIODS: multiple_grading_periods?,
|
||||
HAS_GRADING_PERIODS: grading_periods?,
|
||||
DEFAULT_GRADING_STANDARD_DATA: GradingStandard.default_grading_standard,
|
||||
CONTEXT_SETTINGS_URL: context_url(@context, :context_settings_url)
|
||||
}
|
||||
|
|
|
@ -320,12 +320,12 @@ class Quizzes::QuizzesController < ApplicationController
|
|||
:quiz_max_combination_count => QUIZ_MAX_COMBINATION_COUNT,
|
||||
:SHOW_QUIZ_ALT_TEXT_WARNING => true,
|
||||
:VALID_DATE_RANGE => CourseDateRange.new(@context),
|
||||
:MULTIPLE_GRADING_PERIODS_ENABLED => @context.feature_enabled?(:multiple_grading_periods),
|
||||
:HAS_GRADING_PERIODS => @context.grading_periods?,
|
||||
:MAX_NAME_LENGTH_REQUIRED_FOR_ACCOUNT => max_name_length_required_for_account,
|
||||
:MAX_NAME_LENGTH => max_name_length
|
||||
}
|
||||
|
||||
if @context.feature_enabled?(:multiple_grading_periods)
|
||||
if @context.grading_periods?
|
||||
hash[:active_grading_periods] = GradingPeriod.json_for(@context, @current_user)
|
||||
end
|
||||
|
||||
|
|
|
@ -245,7 +245,7 @@ class SubmissionsApiController < ApplicationController
|
|||
#
|
||||
# @argument grading_period_id [Integer]
|
||||
# The id of the grading period in which submissions are being requested
|
||||
# (Requires the Multiple Grading Periods account feature turned on)
|
||||
# (Requires grading periods to exist on the account)
|
||||
#
|
||||
# @argument order [String, "id"|"graded_at"]
|
||||
# The order submissions will be returned in. Defaults to "id". Doesn't
|
||||
|
@ -330,7 +330,7 @@ class SubmissionsApiController < ApplicationController
|
|||
assignment_scope = assignment_scope.where(:id => requested_assignment_ids)
|
||||
end
|
||||
|
||||
if params[:grading_period_id].present? && multiple_grading_periods?
|
||||
if params[:grading_period_id].present?
|
||||
assignments = GradingPeriod.active.find(params[:grading_period_id]).assignments(assignment_scope)
|
||||
else
|
||||
assignments = assignment_scope.to_a
|
||||
|
|
|
@ -175,7 +175,12 @@ class UsersController < ApplicationController
|
|||
add_crumb(@current_user.short_name, crumb_url)
|
||||
add_crumb(t('crumbs.grades', 'Grades'), grades_path)
|
||||
|
||||
current_active_enrollments = @user.enrollments.current.preload(:course, :enrollment_state).shard(@user).to_a
|
||||
current_active_enrollments = @user.
|
||||
enrollments.
|
||||
current.
|
||||
preload(:course, :enrollment_state, :scores).
|
||||
shard(@user).
|
||||
to_a
|
||||
|
||||
@presenter = GradesPresenter.new(current_active_enrollments)
|
||||
|
||||
|
@ -197,19 +202,11 @@ class UsersController < ApplicationController
|
|||
enrollment = Enrollment.active.find(params[:enrollment_id])
|
||||
return render_unauthorized_action unless enrollment.grants_right?(@current_user, session, :read_grades)
|
||||
|
||||
course = enrollment.course
|
||||
grading_period_id = params[:grading_period_id].to_i
|
||||
grading_period = GradingPeriod.for(course).find_by(id: grading_period_id)
|
||||
grading_periods = {
|
||||
course.id => {
|
||||
periods: [grading_period],
|
||||
selected_period_id: grading_period_id
|
||||
}
|
||||
grading_period_id = generate_grading_period_id(params[:grading_period_id])
|
||||
render json: {
|
||||
grade: enrollment.computed_current_score(grading_period_id: grading_period_id),
|
||||
hide_final_grades: enrollment.course.hide_final_grades?
|
||||
}
|
||||
calculator = grade_calculator([enrollment.user_id], course, grading_periods)
|
||||
totals = calculator.compute_scores.first[:current]
|
||||
totals[:hide_final_grades] = course.hide_final_grades?
|
||||
render json: totals
|
||||
end
|
||||
|
||||
def oauth
|
||||
|
@ -2119,6 +2116,12 @@ class UsersController < ApplicationController
|
|||
|
||||
private
|
||||
|
||||
def generate_grading_period_id(period_id)
|
||||
# nil and '' will get converted to 0 in the .to_i call
|
||||
id = period_id.to_i
|
||||
id == 0 ? nil : id
|
||||
end
|
||||
|
||||
def render_new_user_tutorial_statuses(user)
|
||||
render(json: { new_user_tutorial_statuses: { collapsed: user.new_user_tutorial_statuses }})
|
||||
end
|
||||
|
@ -2137,47 +2140,30 @@ class UsersController < ApplicationController
|
|||
presenter.observed_enrollments.group_by { |enrollment| enrollment[:course_id] }
|
||||
|
||||
grouped_observed_enrollments.each do |course_id, enrollments|
|
||||
grading_period_id = generate_grading_period_id(
|
||||
grading_periods[course_id].try(:selected_period_id)
|
||||
)
|
||||
grades[:observed_enrollments][course_id] = {}
|
||||
|
||||
if grading_periods[course_id].present?
|
||||
user_ids = enrollments.map(&:user_id)
|
||||
course = enrollments.first.course
|
||||
grades[:observed_enrollments][course_id] = grades_from_grade_calculator(user_ids, course, grading_periods)
|
||||
else
|
||||
grades[:observed_enrollments][course_id] = grades_from_enrollments(enrollments)
|
||||
end
|
||||
grades[:observed_enrollments][course_id] = grades_from_enrollments(
|
||||
enrollments,
|
||||
grading_period_id: grading_period_id
|
||||
)
|
||||
end
|
||||
|
||||
presenter.student_enrollments.each do |enrollment_course_pair|
|
||||
course = enrollment_course_pair.first
|
||||
enrollment = enrollment_course_pair.second
|
||||
|
||||
if grading_periods[course.id].present?
|
||||
computed_score = grades_from_grade_calculator([enrollment.user_id], course, grading_periods)[enrollment.user_id]
|
||||
grades[:student_enrollments][course.id] = computed_score
|
||||
else
|
||||
computed_score = enrollment.computed_current_score
|
||||
grades[:student_enrollments][course.id] = computed_score
|
||||
end
|
||||
presenter.student_enrollments.each do |course, enrollment|
|
||||
grading_period_id = generate_grading_period_id(
|
||||
grading_periods[course.id].try(:[], :selected_period_id)
|
||||
)
|
||||
computed_score = enrollment.computed_current_score(grading_period_id: grading_period_id)
|
||||
grades[:student_enrollments][course.id] = computed_score
|
||||
end
|
||||
grades
|
||||
end
|
||||
|
||||
def grades_from_grade_calculator(user_ids, course, grading_periods)
|
||||
calculator = grade_calculator(user_ids, course, grading_periods)
|
||||
grades = {}
|
||||
calculator.compute_scores.each_with_index do |score, index|
|
||||
computed_score = score[:current][:grade]
|
||||
user_id = user_ids[index]
|
||||
grades[user_id] = computed_score
|
||||
end
|
||||
grades
|
||||
end
|
||||
|
||||
def grades_from_enrollments(enrollments)
|
||||
def grades_from_enrollments(enrollments, grading_period_id: nil)
|
||||
grades = {}
|
||||
enrollments.each do |enrollment|
|
||||
computed_score = enrollment.computed_current_score
|
||||
computed_score = enrollment.computed_current_score(grading_period_id: grading_period_id)
|
||||
grades[enrollment.user_id] = computed_score
|
||||
end
|
||||
grades
|
||||
|
@ -2191,7 +2177,7 @@ class UsersController < ApplicationController
|
|||
grading_periods = {}
|
||||
|
||||
courses.each do |course|
|
||||
next unless course.feature_enabled?(:multiple_grading_periods)
|
||||
next unless course.grading_periods?
|
||||
|
||||
course_periods = GradingPeriod.for(course)
|
||||
grading_period_specified = grading_period_id &&
|
||||
|
@ -2212,19 +2198,6 @@ class UsersController < ApplicationController
|
|||
grading_periods
|
||||
end
|
||||
|
||||
def grade_calculator(user_ids, course, grading_periods)
|
||||
if course.feature_enabled?(:multiple_grading_periods) &&
|
||||
grading_periods[course.id][:selected_period_id] != 0
|
||||
|
||||
grading_period = grading_periods[course.id][:periods].find do |period|
|
||||
period.id == grading_periods[course.id][:selected_period_id]
|
||||
end
|
||||
GradeCalculator.new(user_ids, course, grading_period: grading_period)
|
||||
else
|
||||
GradeCalculator.new(user_ids, course)
|
||||
end
|
||||
end
|
||||
|
||||
def create_user
|
||||
run_login_hooks
|
||||
# Look for an incomplete registration with this pseudonym
|
||||
|
|
|
@ -26,7 +26,7 @@ define([
|
|||
syncWithBackbone: React.PropTypes.func.isRequired,
|
||||
sections: React.PropTypes.array.isRequired,
|
||||
defaultSectionId: React.PropTypes.string.isRequired,
|
||||
multipleGradingPeriodsEnabled: React.PropTypes.bool.isRequired,
|
||||
hasGradingPeriods: React.PropTypes.bool.isRequired,
|
||||
gradingPeriods: React.PropTypes.array.isRequired,
|
||||
isOnlyVisibleToOverrides: React.PropTypes.bool.isRequired,
|
||||
dueAt: function(props) {
|
||||
|
@ -350,7 +350,7 @@ define([
|
|||
let validGroups = this.valuesWithOmission({object: this.groupsForSelectedSet(), keysToOmit: this.chosenGroupIds()})
|
||||
let validSections = this.valuesWithOmission({object: this.state.sections, keysToOmit: this.chosenSectionIds()})
|
||||
let validNoops = this.valuesWithOmission({object: this.state.noops, keysToOmit: this.chosenNoops()})
|
||||
if (this.props.multipleGradingPeriodsEnabled && !_.contains(ENV.current_user_roles, "admin")) {
|
||||
if (this.props.hasGradingPeriods && !_.contains(ENV.current_user_roles, "admin")) {
|
||||
({validStudents, validGroups, validSections} =
|
||||
this.filterDropdownOptionsForMultipleGradingPeriods(validStudents, validGroups, validSections))
|
||||
}
|
||||
|
@ -431,7 +431,7 @@ define([
|
|||
|
||||
disableInputs(row) {
|
||||
const rowIsNewOrUserIsAdmin = !row.persisted || _.contains(ENV.current_user_roles, "admin")
|
||||
if (!this.props.multipleGradingPeriodsEnabled || rowIsNewOrUserIsAdmin) {
|
||||
if (!this.props.hasGradingPeriods || rowIsNewOrUserIsAdmin) {
|
||||
return false
|
||||
}
|
||||
|
||||
|
|
|
@ -18,109 +18,105 @@
|
|||
|
||||
define([
|
||||
'underscore'
|
||||
], function (_) {
|
||||
const sum = function (collection) {
|
||||
return _.reduce(collection, function (sum, value) {
|
||||
return sum + value;
|
||||
}, 0);
|
||||
};
|
||||
], (_) => {
|
||||
function sum (collection) {
|
||||
return _.reduce(collection, (total, value) => total + value, 0);
|
||||
}
|
||||
|
||||
const sumBy = function (collection, attr) {
|
||||
function sumBy (collection, attr) {
|
||||
const values = _.map(collection, attr);
|
||||
return sum(values);
|
||||
};
|
||||
}
|
||||
|
||||
const partition = function (collection, partitionFn) {
|
||||
function partition (collection, partitionFn) {
|
||||
const grouped = _.groupBy(collection, partitionFn);
|
||||
return [grouped[true] || [], grouped[false] || []];
|
||||
};
|
||||
return [grouped.true || [], grouped.false || []];
|
||||
}
|
||||
|
||||
const parseScore = function (score) {
|
||||
function parseScore (score) {
|
||||
const result = parseFloat(score);
|
||||
return (result && isFinite(result)) ? result : 0;
|
||||
};
|
||||
}
|
||||
|
||||
function sortPairsDescending ([scoreA, submissionA], [scoreB, submissionB]) {
|
||||
const scoreDiff = scoreB - scoreA;
|
||||
if (scoreDiff !== 0) {
|
||||
return scoreDiff;
|
||||
}
|
||||
// To ensure stable sorting, use the assignment id as a secondary sort.
|
||||
return submissionA.assignment_id - submissionB.assignment_id;
|
||||
}
|
||||
|
||||
// Some browser sorting functions (such as in V8) are not stable.
|
||||
// This function ensures that the same submission will be dropped regardless
|
||||
// of browser.
|
||||
const stableSubmissionSort = function (sortFn, getAssignmentIdFn) {
|
||||
return function (a, b) {
|
||||
const ret = sortFn(a, b);
|
||||
if (ret === 0) {
|
||||
return getAssignmentIdFn(a) - getAssignmentIdFn(b);
|
||||
} else {
|
||||
return ret;
|
||||
}
|
||||
};
|
||||
};
|
||||
function sortPairsAscending ([scoreA, submissionA], [scoreB, submissionB]) {
|
||||
const scoreDiff = scoreA - scoreB;
|
||||
if (scoreDiff !== 0) {
|
||||
return scoreDiff;
|
||||
}
|
||||
// To ensure stable sorting, use the assignment id as a secondary sort.
|
||||
return submissionA.assignment_id - submissionB.assignment_id;
|
||||
}
|
||||
|
||||
const sortDescending = function ([a, xx], [b, yy]) {
|
||||
return b - a;
|
||||
};
|
||||
const sortAscending = function ([a, xx], [b, yy]) {
|
||||
return a - b;
|
||||
};
|
||||
const getAssignmentIdFn = function ([score, submission]) {
|
||||
return submission.submission.assignment_id;
|
||||
};
|
||||
function sortSubmissionsAscending (submissionA, submissionB) {
|
||||
const scoreDiff = submissionA.score - submissionB.score;
|
||||
if (scoreDiff !== 0) {
|
||||
return scoreDiff;
|
||||
}
|
||||
// To ensure stable sorting, use the assignment id as a secondary sort.
|
||||
return submissionA.assignment_id - submissionB.assignment_id;
|
||||
}
|
||||
|
||||
const getSubmissionGrade = function ({ score, total }) {
|
||||
function getSubmissionGrade ({ score, total }) {
|
||||
return score / total;
|
||||
};
|
||||
}
|
||||
|
||||
const estimateQHigh = function (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;
|
||||
} else {
|
||||
return grades[grades.length - 1];
|
||||
}
|
||||
};
|
||||
|
||||
const dropPointed = function (submissions, cannotDrop, keepHighest, keepLowest) {
|
||||
const totals = _.map(submissions, 'total');
|
||||
const maxTotal = Math.max.apply(Math, totals);
|
||||
return grades[grades.length - 1];
|
||||
}
|
||||
|
||||
const keepHelper = function (submissions, keep, bigFSort) {
|
||||
keep = Math.max(1, keep);
|
||||
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];
|
||||
}
|
||||
}
|
||||
|
||||
if (submissions.length <= keep) {
|
||||
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);
|
||||
|
||||
if (submissions.length <= keepCount) {
|
||||
return submissions;
|
||||
}
|
||||
|
||||
const allSubmissions = [...submissions, ...cannotDrop];
|
||||
const [unpointed, pointed] = partition(allSubmissions, function (submission) {
|
||||
return submission.total == 0;
|
||||
});
|
||||
const allSubmissionData = [...submissions, ...cannotDrop];
|
||||
const [unpointed, pointed] = partition(allSubmissionData, submissionDatum => submissionDatum.total === 0);
|
||||
|
||||
const grades = _.map(pointed, getSubmissionGrade).sort();
|
||||
let qHigh = estimateQHigh(pointed, unpointed, grades);
|
||||
let qLow = grades[0];
|
||||
let qMid = (qLow + qHigh) / 2;
|
||||
|
||||
const bigF = function (q, submissions) {
|
||||
const ratedScores = _.map(submissions, function (submission) {
|
||||
return [submission.score - (q * submission.total), submission];
|
||||
});
|
||||
const rankedScores = ratedScores.sort(bigFSort);
|
||||
const keptScores = rankedScores.slice(0, keep);
|
||||
const qKept = sumBy(keptScores, function ([score]) {
|
||||
return score;
|
||||
});
|
||||
const keptSubmissions = _.map(keptScores, function ([score, submission]) {
|
||||
return submission;
|
||||
});
|
||||
const qCantDrop = sumBy(cannotDrop, function (submission) {
|
||||
return submission.score - q * submission.total;
|
||||
});
|
||||
return [qKept + qCantDrop, keptSubmissions];
|
||||
};
|
||||
const bigF = buildBigF(keepCount, cannotDrop, bigFSort);
|
||||
|
||||
let [x, kept] = bigF(qMid, submissions);
|
||||
const threshold = 1 / (2 * keep * Math.pow(maxTotal, 2));
|
||||
let [x, submissionsToKeep] = bigF(qMid, submissions);
|
||||
const threshold = 1 / (2 * keepCount * (maxTotal ** 2));
|
||||
while (qHigh - qLow >= threshold) {
|
||||
if (x < 0) {
|
||||
qHigh = qMid;
|
||||
|
@ -132,22 +128,24 @@ define([
|
|||
break;
|
||||
}
|
||||
|
||||
[x, kept] = bigF(qMid, submissions);
|
||||
[x, submissionsToKeep] = bigF(qMid, submissions);
|
||||
}
|
||||
|
||||
return kept;
|
||||
};
|
||||
return submissionsToKeep;
|
||||
}
|
||||
|
||||
const kept = keepHelper(submissions, keepHighest, stableSubmissionSort(sortDescending, getAssignmentIdFn));
|
||||
return keepHelper(kept, keepLowest, stableSubmissionSort(sortAscending, getAssignmentIdFn));
|
||||
};
|
||||
const submissionsWithLowestDropped = keepHelper(
|
||||
droppableSubmissionData, keepHighest, sortPairsDescending
|
||||
);
|
||||
return keepHelper(
|
||||
submissionsWithLowestDropped, keepLowest, sortPairsAscending
|
||||
);
|
||||
}
|
||||
|
||||
const dropUnpointed = function (submissions, keepHighest, keepLowest) {
|
||||
const sortAscending = function (a, b) { return a.score - b.score };
|
||||
const getAssignmentIdFn = function ({ submission }) { return submission.assignment_id };
|
||||
const sortedSubmissions = submissions.sort(stableSubmissionSort(sortAscending, getAssignmentIdFn));
|
||||
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.
|
||||
//
|
||||
|
@ -159,130 +157,121 @@ define([
|
|||
// 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)
|
||||
const dropAssignments = function (submissions, rules) {
|
||||
rules = rules || {};
|
||||
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 submissions;
|
||||
return allSubmissionData;
|
||||
}
|
||||
|
||||
let cannot_drop = [];
|
||||
let cannotDrop = [];
|
||||
let droppableSubmissionData = allSubmissionData;
|
||||
if (neverDropIds.length > 0) {
|
||||
[cannot_drop, submissions] = partition(submissions, function (submission) {
|
||||
return _.contains(neverDropIds, submission.submission.assignment_id);
|
||||
});
|
||||
[cannotDrop, droppableSubmissionData] = partition(allSubmissionData, submission => (
|
||||
_.contains(neverDropIds, submission.submission.assignment_id)
|
||||
));
|
||||
}
|
||||
|
||||
if (submissions.length === 0) {
|
||||
return cannot_drop;
|
||||
if (droppableSubmissionData.length === 0) {
|
||||
return cannotDrop;
|
||||
}
|
||||
|
||||
dropLowest = Math.min(dropLowest, submissions.length - 1);
|
||||
dropHighest = (dropLowest + dropHighest) >= submissions.length ? 0 : dropHighest;
|
||||
dropLowest = Math.min(dropLowest, droppableSubmissionData.length - 1);
|
||||
dropHighest = (dropLowest + dropHighest) >= droppableSubmissionData.length ? 0 : dropHighest;
|
||||
|
||||
const keepHighest = submissions.length - dropLowest;
|
||||
const keepHighest = droppableSubmissionData.length - dropLowest;
|
||||
const keepLowest = keepHighest - dropHighest;
|
||||
const hasPointed = _.some(submissions, function (submission) { return submission.total > 0 });
|
||||
const hasPointed = _.some(droppableSubmissionData, submission => submission.total > 0);
|
||||
|
||||
let kept;
|
||||
let submissionsToKeep;
|
||||
if (hasPointed) {
|
||||
kept = dropPointed(submissions, cannot_drop, keepHighest, keepLowest);
|
||||
submissionsToKeep = dropPointed(droppableSubmissionData, cannotDrop, keepHighest, keepLowest);
|
||||
} else {
|
||||
kept = dropUnpointed(submissions, keepHighest, keepLowest);
|
||||
submissionsToKeep = dropUnpointed(droppableSubmissionData, keepHighest, keepLowest);
|
||||
}
|
||||
|
||||
kept = [ ...kept, ...cannot_drop];
|
||||
submissionsToKeep = [...submissionsToKeep, ...cannotDrop];
|
||||
|
||||
_.difference(submissions, kept).forEach(function (submission) {
|
||||
submission.drop = true;
|
||||
});
|
||||
_.difference(droppableSubmissionData, submissionsToKeep).forEach((submission) => { submission.drop = true });
|
||||
|
||||
return kept;
|
||||
};
|
||||
return submissionsToKeep;
|
||||
}
|
||||
|
||||
const calculateGroupSum = function (group, submissions, includeUngraded) {
|
||||
// remove assignments without visibility from gradeableAssignments
|
||||
const hiddenAssignments = _.chain(submissions).filter('hidden').indexBy('assignment_id').value();
|
||||
const gradeableAssignments = _.reject(group.assignments, function (assignment) {
|
||||
return assignment.omit_from_final_grade ||
|
||||
hiddenAssignments[assignment.id] ||
|
||||
_.isEqual(assignment.submission_types, ['not_graded']);
|
||||
});
|
||||
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 ||
|
||||
hiddenAssignmentsById[assignment.id] ||
|
||||
_.isEqual(assignment.submission_types, ['not_graded'])
|
||||
));
|
||||
const assignments = _.indexBy(gradeableAssignments, 'id');
|
||||
|
||||
// filter out submissions from other assignment groups
|
||||
submissions = _.filter(submissions, function (submission) {
|
||||
return assignments[submission.assignment_id];
|
||||
});
|
||||
// Remove submissions from other assignment groups.
|
||||
let submissions = _.filter(allSubmissions, submission => assignments[submission.assignment_id]);
|
||||
|
||||
// fill in any missing submissions
|
||||
// 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, function ({ assignment_id }) {
|
||||
return assignment_id.toString();
|
||||
});
|
||||
const missingSubmissions = _.difference(_.keys(assignments), submissionAssignmentIds);
|
||||
const submissionStubs = _.map(missingSubmissions, (assignment_id) => {
|
||||
return { assignment_id, 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];
|
||||
}
|
||||
|
||||
// filter out excused assignments
|
||||
// Remove excused submissions.
|
||||
submissions = _.reject(submissions, 'excused');
|
||||
|
||||
const submissionsByAssignment = _.indexBy(submissions, 'assignment_id');
|
||||
|
||||
const submissionData = _.map(submissions, function (submission) {
|
||||
return {
|
||||
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;
|
||||
if (!includeUngraded) {
|
||||
relevantSubmissionData = _.filter(submissionData, function (submission) {
|
||||
return submission.submitted && !submission.pending_review;
|
||||
});
|
||||
relevantSubmissionData = _.filter(submissionData, submission => (
|
||||
submission.submitted && !submission.pending_review
|
||||
));
|
||||
}
|
||||
|
||||
const kept = dropAssignments(relevantSubmissionData, group.rules);
|
||||
const score = sum(_.chain(kept).map('score').map(parseScore).value());
|
||||
const possible = sumBy(kept, 'total');
|
||||
const submissionsToKeep = dropAssignments(relevantSubmissionData, group.rules);
|
||||
const score = sum(_.chain(submissionsToKeep).map('score').map(parseScore).value());
|
||||
const possible = sumBy(submissionsToKeep, 'total');
|
||||
|
||||
return {
|
||||
possible,
|
||||
score,
|
||||
weight: group.group_weight,
|
||||
possible,
|
||||
submission_count: _.filter(submissionData, 'submitted').length,
|
||||
submissions: _.map(submissionData, function (submission) {
|
||||
return {
|
||||
drop: submission.drop,
|
||||
percent: parseScore(submission.score / submission.total),
|
||||
possible: submission.total,
|
||||
score: parseScore(submission.score),
|
||||
submission: submission.submission,
|
||||
submitted: submission.submitted
|
||||
};
|
||||
})
|
||||
submissions: _.map(submissionData, submissionDatum => (
|
||||
{
|
||||
drop: submissionDatum.drop,
|
||||
percent: parseScore(submissionDatum.score / submissionDatum.total),
|
||||
score: parseScore(submissionDatum.score),
|
||||
possible: submissionDatum.total,
|
||||
submission: submissionDatum.submission,
|
||||
submitted: submissionDatum.submitted
|
||||
}
|
||||
))
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
// Each submission requires the following properties:
|
||||
// * score: number
|
||||
// * points_possible: non-negative integer
|
||||
// * assignment_id: Canvas id
|
||||
// * assignment_group_id: Canvas id
|
||||
// * assignment_id: <Canvas id>
|
||||
// * assignment_group_id: <Canvas id>
|
||||
// * excused: boolean
|
||||
//
|
||||
// To represent assignments which the student has not yet submitted, set the
|
||||
// score of the related submission to `null`.
|
||||
// Ungraded submissions will have a score of `null`.
|
||||
//
|
||||
// An assignment group requires the following properties:
|
||||
// * id: Canvas id
|
||||
|
@ -296,21 +285,37 @@ define([
|
|||
// * never_drop: [array of assignment ids]
|
||||
//
|
||||
// `assignments` is an array of objects with the following properties:
|
||||
// * id: Canvas id
|
||||
// * id: <Canvas id>
|
||||
// * points_possible: non-negative number
|
||||
// * submission_types: [array of strings]
|
||||
//
|
||||
// The weighting scheme is one of [`percent`, `points`]
|
||||
// An AssignmentGroup Grade has the following shape:
|
||||
// {
|
||||
// score: number|null
|
||||
// possible: number|null
|
||||
// submission_count: non-negative number
|
||||
// submissions: [array of Submissions]
|
||||
// }
|
||||
//
|
||||
// When weightingScheme is `percent`, assignment group weights are used.
|
||||
// Otherwise, no weighting is applied.
|
||||
const calculate = function (submissions, assignmentGroup, weightingScheme) {
|
||||
// Return value is an AssignmentGroup Grade Set.
|
||||
// An AssignmentGroup Grade Set has the following shape:
|
||||
// {
|
||||
// assignmentGroupId: <Canvas id>
|
||||
// assignmentGroupWeight: number
|
||||
// current: <AssignmentGroup Grade *see above>
|
||||
// final: <AssignmentGroup Grade *see above>
|
||||
// scoreUnit: 'points'
|
||||
// }
|
||||
function calculate (allSubmissions, assignmentGroup) {
|
||||
const submissions = _.uniq(allSubmissions, 'assignment_id');
|
||||
return {
|
||||
group: assignmentGroup,
|
||||
current: calculateGroupSum(assignmentGroup, submissions, false),
|
||||
final: calculateGroupSum(assignmentGroup, submissions, true)
|
||||
assignmentGroupId: assignmentGroup.id,
|
||||
assignmentGroupWeight: assignmentGroup.group_weight,
|
||||
current: calculateGroupGrade(assignmentGroup, submissions, false),
|
||||
final: calculateGroupGrade(assignmentGroup, submissions, true),
|
||||
scoreUnit: 'points'
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
calculate
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (C) 2016 Instructure, Inc.
|
||||
* Copyright (C) 2016 - 2017 Instructure, Inc.
|
||||
*
|
||||
* This file is part of Canvas.
|
||||
*
|
||||
|
@ -20,51 +20,188 @@ define([
|
|||
'underscore',
|
||||
'compiled/util/round',
|
||||
'jsx/gradebook/AssignmentGroupGradeCalculator'
|
||||
], function (_, round, AssignmentGroupGradeCalculator) {
|
||||
const sum = function (collection) {
|
||||
return _.reduce(collection, function (sum, value) {
|
||||
return sum + value;
|
||||
}, 0);
|
||||
};
|
||||
], (_, round, AssignmentGroupGradeCalculator) => {
|
||||
function sum (collection) {
|
||||
return _.reduce(collection, (total, value) => (total + value), 0);
|
||||
}
|
||||
|
||||
const sumBy = function (collection, attr) {
|
||||
function sumBy (collection, attr) {
|
||||
const values = _.map(collection, attr);
|
||||
return sum(values);
|
||||
};
|
||||
}
|
||||
|
||||
const getGroupSumWeightedPercent = ({ score, possible, weight }) => {
|
||||
return (score / possible) * weight;
|
||||
};
|
||||
function getWeightedPercent ({ score, possible, weight }) {
|
||||
return score ? (score / possible) * weight : 0;
|
||||
}
|
||||
|
||||
const calculateTotal = function (groupSums, includeUngraded, weightingScheme) {
|
||||
groupSums = _.map(groupSums, function (groupSum) {
|
||||
const sumVersion = includeUngraded ? groupSum.final : groupSum.current;
|
||||
return { ...sumVersion, weight: groupSum.group.group_weight };
|
||||
function combineAssignmentGroupGrades (assignmentGroupGrades, includeUngraded, options) {
|
||||
const scopedAssignmentGroupGrades = _.map(assignmentGroupGrades, (assignmentGroupGrade) => {
|
||||
const gradeVersion = includeUngraded ? assignmentGroupGrade.final : assignmentGroupGrade.current;
|
||||
return { ...gradeVersion, weight: assignmentGroupGrade.assignmentGroupWeight };
|
||||
});
|
||||
|
||||
if (weightingScheme === 'percent') {
|
||||
const relevantGroupSums = _.filter(groupSums, 'possible');
|
||||
let finalGrade = sum(_.map(relevantGroupSums, getGroupSumWeightedPercent));
|
||||
const fullWeight = sumBy(relevantGroupSums, 'weight');
|
||||
if (options.weightAssignmentGroups) {
|
||||
const relevantGroupGrades = _.filter(scopedAssignmentGroupGrades, 'possible');
|
||||
const fullWeight = sumBy(relevantGroupGrades, 'weight');
|
||||
|
||||
let finalGrade = sum(_.map(relevantGroupGrades, getWeightedPercent));
|
||||
if (fullWeight === 0) {
|
||||
finalGrade = null;
|
||||
} else if (fullWeight < 100) {
|
||||
finalGrade = finalGrade * 100 / fullWeight;
|
||||
finalGrade = (finalGrade * 100) / fullWeight;
|
||||
}
|
||||
|
||||
const submissionCount = sumBy(relevantGroupSums, 'submission_count');
|
||||
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 };
|
||||
} else {
|
||||
return {
|
||||
score: sumBy(groupSums, 'score'),
|
||||
possible: sumBy(groupSums, 'possible')
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
score: sumBy(scopedAssignmentGroupGrades, 'score'),
|
||||
possible: sumBy(scopedAssignmentGroupGrades, 'possible')
|
||||
}
|
||||
}
|
||||
|
||||
function combineGradingPeriodGrades (gradingPeriodGradesByPeriodId, includeUngraded) {
|
||||
const scopedGradingPeriodGrades = _.map(gradingPeriodGradesByPeriodId, (gradingPeriodGrade) => {
|
||||
const gradeVersion = includeUngraded ? gradingPeriodGrade.final : gradingPeriodGrade.current;
|
||||
return { ...gradeVersion, weight: gradingPeriodGrade.gradingPeriodWeight };
|
||||
});
|
||||
|
||||
const weightedScores = _.map(scopedGradingPeriodGrades, getWeightedPercent);
|
||||
const totalWeight = sumBy(scopedGradingPeriodGrades, 'weight');
|
||||
const totalScore = totalWeight === 0 ? 0 : (sum(weightedScores) * 100) / Math.min(totalWeight, 100);
|
||||
|
||||
return {
|
||||
score: round(totalScore, 2),
|
||||
possible: 100
|
||||
};
|
||||
}
|
||||
|
||||
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 }
|
||||
));
|
||||
}
|
||||
|
||||
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 = {};
|
||||
_.forEach(grades, (grade) => {
|
||||
const previousGrade = map[grade.assignmentGroupId];
|
||||
if (previousGrade) {
|
||||
map[grade.assignmentGroupId] = {
|
||||
...previousGrade,
|
||||
current: {
|
||||
score: previousGrade.current.score + grade.current.score,
|
||||
possible: previousGrade.current.possible + grade.current.possible
|
||||
},
|
||||
final: {
|
||||
score: previousGrade.final.score + grade.final.score,
|
||||
possible: previousGrade.final.possible + grade.final.possible
|
||||
}
|
||||
};
|
||||
} else {
|
||||
map[grade.assignmentGroupId] = grade;
|
||||
}
|
||||
});
|
||||
return map;
|
||||
}
|
||||
|
||||
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 gradingPeriodsById = _.indexBy(gradingPeriods, 'id');
|
||||
const gradingPeriodGradesByPeriodId = {};
|
||||
const periodBasedAssignmentGroupGrades = [];
|
||||
|
||||
_.forEach(gradingPeriods, (gradingPeriod) => {
|
||||
const groupGrades = {};
|
||||
|
||||
(assignmentGroupsByGradingPeriodId[gradingPeriod.id] || []).forEach((assignmentGroup) => {
|
||||
groupGrades[assignmentGroup.id] = AssignmentGroupGradeCalculator.calculate(submissions, assignmentGroup);
|
||||
periodBasedAssignmentGroupGrades.push(groupGrades[assignmentGroup.id]);
|
||||
});
|
||||
|
||||
const groupGradesList = _.values(groupGrades);
|
||||
|
||||
gradingPeriodGradesByPeriodId[gradingPeriod.id] = {
|
||||
gradingPeriodId: gradingPeriod.id,
|
||||
gradingPeriodWeight: gradingPeriodsById[gradingPeriod.id].weight || 0,
|
||||
assignmentGroups: groupGrades,
|
||||
current: combineAssignmentGroupGrades(groupGradesList, false, options),
|
||||
final: combineAssignmentGroupGrades(groupGradesList, true, options),
|
||||
scoreUnit: options.weightAssignmentGroups ? 'percentage' : 'points'
|
||||
};
|
||||
});
|
||||
|
||||
if (options.weightGradingPeriods) {
|
||||
return {
|
||||
assignmentGroups: recombinePeriodBasedAssignmentGroupGrades(periodBasedAssignmentGroupGrades),
|
||||
gradingPeriods: gradingPeriodGradesByPeriodId,
|
||||
current: combineGradingPeriodGrades(gradingPeriodGradesByPeriodId, false, options),
|
||||
final: combineGradingPeriodGrades(gradingPeriodGradesByPeriodId, true, options),
|
||||
scoreUnit: 'percentage'
|
||||
};
|
||||
}
|
||||
|
||||
const allAssignmentGroupGrades = _.map(assignmentGroups, assignmentGroup => (
|
||||
AssignmentGroupGradeCalculator.calculate(submissions, assignmentGroup)
|
||||
));
|
||||
|
||||
return {
|
||||
assignmentGroups: _.indexBy(allAssignmentGroupGrades, grade => grade.assignmentGroupId),
|
||||
gradingPeriods: gradingPeriodGradesByPeriodId,
|
||||
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 => (
|
||||
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:
|
||||
// * score: number
|
||||
|
@ -73,8 +210,7 @@ define([
|
|||
// * assignment_group_id: Canvas id
|
||||
// * excused: boolean
|
||||
//
|
||||
// To represent assignments which the student has not yet submitted, set the
|
||||
// score of the related submission to `null`.
|
||||
// Ungraded submissions will have a score of `null`.
|
||||
//
|
||||
// Each assignment group requires the following properties:
|
||||
// * id: Canvas id
|
||||
|
@ -96,17 +232,89 @@ define([
|
|||
//
|
||||
// When weightingScheme is `percent`, assignment group weights are used.
|
||||
// Otherwise, no weighting is applied.
|
||||
const calculate = function (submissions, assignmentGroups, weightingScheme) {
|
||||
const groupSums = _.map(assignmentGroups, function (group) {
|
||||
return AssignmentGroupGradeCalculator.calculate(submissions, group);
|
||||
});
|
||||
|
||||
return {
|
||||
group_sums: groupSums,
|
||||
current: calculateTotal(groupSums, false, weightingScheme),
|
||||
final: calculateTotal(groupSums, true, weightingScheme)
|
||||
//
|
||||
// Grading period set and effective due dates are optional, but must be used
|
||||
// together.
|
||||
//
|
||||
// `gradingPeriodSet` is an object with at least the following shape:
|
||||
// * gradingPeriods: [array of grading periods *see below]
|
||||
// * weight: non-negative number
|
||||
//
|
||||
// Each grading period requires the following properties:
|
||||
// * id: Canvas id
|
||||
// * weight: non-negative number
|
||||
//
|
||||
// `effectiveDueDates` is an object with at least the following shape:
|
||||
// {
|
||||
// <assignment id (Canvas id)>: {
|
||||
// grading_period_id: <grading period id (Canvas id)>
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// `effectiveDueDates` should generally include an assignment id for most/all
|
||||
// assignments in use for the course and student. The structure above is the
|
||||
// "user-scoped" form of effective due dates, which includes only the
|
||||
// necessary data to perform a grade calculation. Effective due date entries
|
||||
// would otherwise include more information about a student's relationship
|
||||
// with an assignment and related grading periods.
|
||||
//
|
||||
// Grades minimally have the following shape:
|
||||
// {
|
||||
// score: number|null
|
||||
// possible: number|null
|
||||
// }
|
||||
//
|
||||
// AssignmentGroup Grade maps have the following shape:
|
||||
// {
|
||||
// <assignment group id (Canvas id)>: <AssignmentGroup Grade Set *see below>
|
||||
// }
|
||||
//
|
||||
// GradingPeriod Grade Sets have the following shape:
|
||||
// {
|
||||
// gradingPeriodId: <Canvas id>
|
||||
// gradingPeriodWeight: number
|
||||
// assignmentGroups: <AssignmentGroup Grade map>
|
||||
// current: <AssignmentGroup Grade *see below>
|
||||
// final: <AssignmentGroup Grade *see below>
|
||||
// scoreUnit: 'points'|'percent'
|
||||
// }
|
||||
//
|
||||
// GradingPeriod Grade maps have the following shape:
|
||||
// {
|
||||
// <grading period id (Canvas id)>: <GradingPeriod Grade Set *see above>
|
||||
// }
|
||||
//
|
||||
// Each grading period will have a map for assignment group grades, keyed to
|
||||
// the id of assignment groups graded within the grading period. Not every
|
||||
// call to `calculate` will include grading period grades, as some courses do
|
||||
// not use grading periods.
|
||||
//
|
||||
// An AssignmentGroup Grade Set is the returned result from the
|
||||
// AssignmentGroupGradeCalculator.calculate function.
|
||||
//
|
||||
// Return value is a Course Grade Set.
|
||||
// A Course Grade Set has the following shape:
|
||||
// {
|
||||
// assignmentGroups: <AssignmentGroup Grade map *see above>
|
||||
// gradingPeriods: <GradingPeriod Grade map *see above>
|
||||
// current: <AssignmentGroup Grade *see above>
|
||||
// final: <AssignmentGroup Grade *see above>
|
||||
// scoreUnit: 'points'|'percent'
|
||||
// }
|
||||
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
|
||||
);
|
||||
}
|
||||
|
||||
return calculateWithoutGradingPeriods(submissions, assignmentGroups, options);
|
||||
}
|
||||
|
||||
return {
|
||||
calculate
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
define([
|
||||
'underscore'
|
||||
], (_) => {
|
||||
function scopeToUser (dueDateData, userId) {
|
||||
const scopedData = {};
|
||||
_.forEach(dueDateData, (dueDateDataByUserId, assignmentId) => {
|
||||
if (dueDateDataByUserId[userId]) {
|
||||
scopedData[assignmentId] = dueDateDataByUserId[userId];
|
||||
}
|
||||
});
|
||||
return scopedData;
|
||||
}
|
||||
|
||||
return {
|
||||
scopeToUser
|
||||
};
|
||||
});
|
|
@ -18,17 +18,18 @@
|
|||
|
||||
define([
|
||||
'underscore'
|
||||
], function (_) {
|
||||
const scoreToGrade = function (score, gradingScheme) {
|
||||
score = Math.max(score, 0);
|
||||
const letter = _.find(gradingScheme, function (row, i) {
|
||||
], (_) => {
|
||||
function scoreToGrade (score, gradingScheme) {
|
||||
const scoreWithLowerBound = Math.max(score, 0);
|
||||
const letter = _.find(gradingScheme, (row, i) => {
|
||||
const schemeScore = (row[1] * 100).toPrecision(4);
|
||||
// The precision of the lower bound (* 100) must be limited to eliminate
|
||||
// floating-point errors.
|
||||
// e.g. 0.545 * 100 returns 54.50000000000001 in JavaScript.
|
||||
return score >= (row[1] * 100).toPrecision(4) || i === (gradingScheme.length - 1);
|
||||
return scoreWithLowerBound >= schemeScore || i === (gradingScheme.length - 1);
|
||||
});
|
||||
return letter[0];
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
scoreToGrade
|
||||
|
|
|
@ -17,10 +17,10 @@ define([
|
|||
return _.contains(assignment.assignment_visibility, student.id);
|
||||
}
|
||||
|
||||
function cellMapForSubmission(assignment, student, gradingPeriodsEnabled, selectedGradingPeriodID, isAdmin) {
|
||||
function cellMapForSubmission(assignment, student, hasGradingPeriods, selectedGradingPeriodID, isAdmin) {
|
||||
if (!visibleToStudent(assignment, student)) {
|
||||
return { locked: true, hideGrade: true, tooltip: TOOLTIP_KEYS.NONE };
|
||||
} else if (gradingPeriodsEnabled) {
|
||||
} else if (hasGradingPeriods) {
|
||||
return cellMappingsForMultipleGradingPeriods(assignment, student, selectedGradingPeriodID, isAdmin);
|
||||
} else {
|
||||
return { locked: false, hideGrade: false, tooltip: TOOLTIP_KEYS.NONE };
|
||||
|
@ -51,8 +51,8 @@ define([
|
|||
}
|
||||
|
||||
class SubmissionState {
|
||||
constructor({ gradingPeriodsEnabled, selectedGradingPeriodID, isAdmin }) {
|
||||
this.gradingPeriodsEnabled = gradingPeriodsEnabled;
|
||||
constructor({ hasGradingPeriods, selectedGradingPeriodID, isAdmin }) {
|
||||
this.hasGradingPeriods = hasGradingPeriods;
|
||||
this.selectedGradingPeriodID = selectedGradingPeriodID;
|
||||
this.isAdmin = isAdmin;
|
||||
this.submissionCellMap = {};
|
||||
|
@ -74,7 +74,7 @@ define([
|
|||
const params = [
|
||||
assignment,
|
||||
student,
|
||||
this.gradingPeriodsEnabled,
|
||||
this.hasGradingPeriods,
|
||||
this.selectedGradingPeriodID,
|
||||
this.isAdmin
|
||||
];
|
||||
|
|
|
@ -17,10 +17,10 @@ define([
|
|||
return _.contains(assignment.assignment_visibility, student.id);
|
||||
}
|
||||
|
||||
function cellMapForSubmission(assignment, student, gradingPeriodsEnabled, selectedGradingPeriodID, isAdmin) {
|
||||
function cellMapForSubmission(assignment, student, hasGradingPeriods, selectedGradingPeriodID, isAdmin) {
|
||||
if (!visibleToStudent(assignment, student)) {
|
||||
return { locked: true, hideGrade: true, tooltip: TOOLTIP_KEYS.NONE };
|
||||
} else if (gradingPeriodsEnabled) {
|
||||
} else if (hasGradingPeriods) {
|
||||
return cellMappingsForMultipleGradingPeriods(assignment, student, selectedGradingPeriodID, isAdmin);
|
||||
} else {
|
||||
return { locked: false, hideGrade: false, tooltip: TOOLTIP_KEYS.NONE };
|
||||
|
@ -51,8 +51,8 @@ define([
|
|||
}
|
||||
|
||||
class SubmissionState {
|
||||
constructor({ gradingPeriodsEnabled, selectedGradingPeriodID, isAdmin }) {
|
||||
this.gradingPeriodsEnabled = gradingPeriodsEnabled;
|
||||
constructor({ hasGradingPeriods, selectedGradingPeriodID, isAdmin }) {
|
||||
this.hasGradingPeriods = hasGradingPeriods;
|
||||
this.selectedGradingPeriodID = selectedGradingPeriodID;
|
||||
this.isAdmin = isAdmin;
|
||||
this.submissionCellMap = {};
|
||||
|
@ -74,7 +74,7 @@ define([
|
|||
const params = [
|
||||
assignment,
|
||||
student,
|
||||
this.gradingPeriodsEnabled,
|
||||
this.hasGradingPeriods,
|
||||
this.selectedGradingPeriodID,
|
||||
this.isAdmin
|
||||
];
|
||||
|
|
|
@ -15,10 +15,12 @@ define([
|
|||
period: Types.shape({
|
||||
id: Types.string.isRequired,
|
||||
title: Types.string.isRequired,
|
||||
weight: Types.number,
|
||||
startDate: Types.instanceOf(Date).isRequired,
|
||||
endDate: Types.instanceOf(Date).isRequired,
|
||||
closeDate: Types.instanceOf(Date).isRequired
|
||||
}).isRequired,
|
||||
weighted: Types.bool,
|
||||
onEdit: Types.func.isRequired,
|
||||
actionsDisabled: Types.bool,
|
||||
readOnly: Types.bool.isRequired,
|
||||
|
@ -91,13 +93,13 @@ define([
|
|||
}
|
||||
},
|
||||
|
||||
dateWithTimezone(date) {
|
||||
const displayDatetime = DateHelper.formatDatetimeForDisplay(date);
|
||||
|
||||
if(ENV.CONTEXT_TIMEZONE === ENV.TIMEZONE) {
|
||||
return displayDatetime;
|
||||
} else {
|
||||
return `${displayDatetime} ${tz.format(date, '%Z')}`;
|
||||
renderWeight() {
|
||||
if (this.props.weighted) {
|
||||
return (
|
||||
<div className="GradingPeriodList__period__attribute col-xs-12 col-md-8 col-lg-2">
|
||||
<span ref="weight">{ I18n.t("Weight:") } { I18n.n(this.props.period.weight, {percentage: true}) }</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -105,18 +107,19 @@ define([
|
|||
return (
|
||||
<div className="GradingPeriodList__period">
|
||||
<div className="GradingPeriodList__period__attributes grid-row">
|
||||
<div className="GradingPeriodList__period__attribute col-xs-12 col-md-8 col-lg-3">
|
||||
<div className="GradingPeriodList__period__attribute col-xs-12 col-md-8 col-lg-4">
|
||||
<span ref="title">{this.props.period.title}</span>
|
||||
</div>
|
||||
<div className="GradingPeriodList__period__attribute col-xs-12 col-md-8 col-lg-3">
|
||||
<span ref="startDate">{I18n.t("Start Date:")} {this.dateWithTimezone(this.props.period.startDate)}</span>
|
||||
<div className="GradingPeriodList__period__attribute col-xs-12 col-md-8 col-lg-2">
|
||||
<span ref="startDate">{I18n.t("Starts:")} {DateHelper.formatDateForDisplay(this.props.period.startDate)}</span>
|
||||
</div>
|
||||
<div className="GradingPeriodList__period__attribute col-xs-12 col-md-8 col-lg-3">
|
||||
<span ref="endDate">{I18n.t("End Date:")} {this.dateWithTimezone(this.props.period.endDate)}</span>
|
||||
<div className="GradingPeriodList__period__attribute col-xs-12 col-md-8 col-lg-2">
|
||||
<span ref="endDate">{I18n.t("Ends:")} {DateHelper.formatDateForDisplay(this.props.period.endDate)}</span>
|
||||
</div>
|
||||
<div className="GradingPeriodList__period__attribute col-xs-12 col-md-8 col-lg-3">
|
||||
<span ref="closeDate">{I18n.t("Close Date:")} {this.dateWithTimezone(this.props.period.closeDate)}</span>
|
||||
<div className="GradingPeriodList__period__attribute col-xs-12 col-md-8 col-lg-2">
|
||||
<span ref="closeDate">{I18n.t("Closes:")} {DateHelper.formatDateForDisplay(this.props.period.closeDate)}</span>
|
||||
</div>
|
||||
{this.renderWeight()}
|
||||
</div>
|
||||
<div className="GradingPeriodList__period__actions">
|
||||
{this.renderEditButton()}
|
||||
|
|
|
@ -9,7 +9,6 @@ define([
|
|||
|
||||
class AccountTabContainer extends React.Component {
|
||||
static propTypes = {
|
||||
multipleGradingPeriodsEnabled: bool.isRequired,
|
||||
readOnly: bool.isRequired,
|
||||
urls: shape({
|
||||
gradingPeriodSetsURL: string.isRequired,
|
||||
|
@ -20,11 +19,10 @@ define([
|
|||
}
|
||||
|
||||
componentDidMount () {
|
||||
if (!this.props.multipleGradingPeriodsEnabled) return;
|
||||
$(this.tabContainer).children('.ui-tabs-minimal').tabs();
|
||||
}
|
||||
|
||||
renderSetsAndStandards () {
|
||||
render () {
|
||||
return (
|
||||
<div ref={(el) => { this.tabContainer = el; }}>
|
||||
<h1>{I18n.t('Grading')}</h1>
|
||||
|
@ -52,22 +50,6 @@ define([
|
|||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderStandards () {
|
||||
return (
|
||||
<div ref={(el) => { this.gradingStandards = el; }}>
|
||||
<h1>{I18n.t('Grading Schemes')}</h1>
|
||||
<GradingStandardCollection />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
render () {
|
||||
if (this.props.multipleGradingPeriodsEnabled) {
|
||||
return this.renderSetsAndStandards();
|
||||
}
|
||||
return this.renderStandards();
|
||||
}
|
||||
}
|
||||
|
||||
return AccountTabContainer;
|
||||
|
|
|
@ -8,11 +8,11 @@ define([
|
|||
], (React, GradingStandardCollection, GradingPeriodCollection, $, I18n) => {
|
||||
class CourseTabContainer extends React.Component {
|
||||
static propTypes = {
|
||||
multipleGradingPeriodsEnabled: React.PropTypes.bool.isRequired
|
||||
hasGradingPeriods: React.PropTypes.bool.isRequired
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
if (!this.props.multipleGradingPeriodsEnabled) return;
|
||||
if (!this.props.hasGradingPeriods) return;
|
||||
$(this.tabContainer).children('.ui-tabs-minimal').tabs();
|
||||
}
|
||||
|
||||
|
@ -52,7 +52,7 @@ define([
|
|||
}
|
||||
|
||||
render () {
|
||||
if (this.props.multipleGradingPeriodsEnabled) {
|
||||
if (this.props.hasGradingPeriods) {
|
||||
return this.renderSetsAndStandards();
|
||||
}
|
||||
return this.renderStandards();
|
||||
|
|
|
@ -4,16 +4,18 @@ define([
|
|||
'underscore',
|
||||
'jquery',
|
||||
'instructure-ui/Button',
|
||||
'instructure-ui/Checkbox',
|
||||
'i18n!grading_periods',
|
||||
'jsx/grading/EnrollmentTermInput',
|
||||
'compiled/jquery.rails_flash_notifications'
|
||||
], function(React, ReactDOM, _, $, { default: Button }, I18n, EnrollmentTermInput) {
|
||||
], function(React, ReactDOM, _, $, { default: Button }, { default: Checkbox }, I18n, EnrollmentTermInput) {
|
||||
const { array, bool, func, shape, string } = React.PropTypes;
|
||||
|
||||
const buildSet = function(attr = {}) {
|
||||
return {
|
||||
id: attr.id,
|
||||
title: attr.title || "",
|
||||
weighted: attr.weighted || false,
|
||||
enrollmentTermIDs: attr.enrollmentTermIDs || []
|
||||
};
|
||||
};
|
||||
|
@ -30,6 +32,7 @@ define([
|
|||
set: shape({
|
||||
id: string,
|
||||
title: string,
|
||||
weighted: bool,
|
||||
enrollmentTermIDs: array
|
||||
}).isRequired,
|
||||
enrollmentTerms: array.isRequired,
|
||||
|
@ -53,15 +56,18 @@ define([
|
|||
},
|
||||
|
||||
changeTitle(e) {
|
||||
let set = _.clone(this.state.set);
|
||||
set.title = e.target.value;
|
||||
this.setState({ set: set });
|
||||
const set = { ...this.state.set, title: e.target.value };
|
||||
this.setState({ set });
|
||||
},
|
||||
|
||||
changeWeighted(e) {
|
||||
const set = { ...this.state.set, weighted: e.target.checked };
|
||||
this.setState({ set });
|
||||
},
|
||||
|
||||
changeEnrollmentTermIDs(termIDs) {
|
||||
let set = _.clone(this.state.set);
|
||||
set.enrollmentTermIDs = termIDs;
|
||||
this.setState({ set: set });
|
||||
const set = { ...this.state.set, enrollmentTermIDs: termIDs };
|
||||
this.setState({ set });
|
||||
},
|
||||
|
||||
triggerSave: function(e) {
|
||||
|
@ -129,6 +135,16 @@ define([
|
|||
enrollmentTerms = {this.props.enrollmentTerms}
|
||||
selectedIDs = {this.state.set.enrollmentTermIDs}
|
||||
setSelectedEnrollmentTermIDs = {this.changeEnrollmentTermIDs} />
|
||||
|
||||
<div className="ic-Input">
|
||||
<Checkbox
|
||||
ref={(ref) => { this.weightedCheckbox = ref }}
|
||||
label={I18n.t('Weighted grading periods')}
|
||||
value="weighted"
|
||||
checked={this.state.set.weighted}
|
||||
onChange={this.changeWeighted}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -6,63 +6,41 @@ define([
|
|||
'instructure-ui/Button',
|
||||
'i18n!external_tools',
|
||||
'jsx/due_dates/DueDateCalendarPicker',
|
||||
'jsx/shared/helpers/accessibleDateFormat'
|
||||
'jsx/shared/helpers/accessibleDateFormat',
|
||||
'jsx/shared/helpers/numberHelper',
|
||||
'compiled/util/round'
|
||||
], function(React, ReactDOM, update, _, { default: Button }, I18n,
|
||||
DueDateCalendarPicker, accessibleDateFormat) {
|
||||
DueDateCalendarPicker, accessibleDateFormat, numberHelper, round) {
|
||||
|
||||
const Types = React.PropTypes;
|
||||
|
||||
const buildPeriod = function(attr) {
|
||||
function roundWeight (val) {
|
||||
const value = numberHelper.parse(val);
|
||||
return isNaN(value) ? null : round(value, 2);
|
||||
};
|
||||
|
||||
function buildPeriod (attr) {
|
||||
return {
|
||||
id: attr.id,
|
||||
title: attr.title,
|
||||
weight: roundWeight(attr.weight),
|
||||
startDate: attr.startDate,
|
||||
endDate: attr.endDate,
|
||||
closeDate: attr.closeDate
|
||||
};
|
||||
};
|
||||
|
||||
const hasDistinctCloseDate = ({ endDate, closeDate }) => {
|
||||
return closeDate && !_.isEqual(endDate, closeDate);
|
||||
};
|
||||
|
||||
const mergePeriod = (form, attr) => {
|
||||
return update(form.state.period, {$merge: attr});
|
||||
}
|
||||
|
||||
const changeTitle = function(e) {
|
||||
let period = mergePeriod(this, {title: e.target.value});
|
||||
this.setState({period: period});
|
||||
};
|
||||
|
||||
const changeStartDate = function(date) {
|
||||
let period = mergePeriod(this, {startDate: date});
|
||||
this.setState({period: period});
|
||||
};
|
||||
|
||||
const changeEndDate = function(date) {
|
||||
let attr = {endDate: date};
|
||||
if (!this.state.preserveCloseDate && !hasDistinctCloseDate(this.state.period)) {
|
||||
attr.closeDate = date;
|
||||
}
|
||||
let period = mergePeriod(this, attr);
|
||||
this.setState({period: period});
|
||||
};
|
||||
|
||||
const changeCloseDate = function(date) {
|
||||
let period = mergePeriod(this, {closeDate: date});
|
||||
this.setState({period: period, preserveCloseDate: !!date});
|
||||
};
|
||||
|
||||
let GradingPeriodForm = React.createClass({
|
||||
propTypes: {
|
||||
period: Types.shape({
|
||||
id: Types.string.isRequired,
|
||||
title: Types.string.isRequired,
|
||||
weight: Types.number,
|
||||
startDate: Types.instanceOf(Date).isRequired,
|
||||
endDate: Types.instanceOf(Date).isRequired,
|
||||
closeDate: Types.instanceOf(Date)
|
||||
}),
|
||||
weighted: Types.bool.isRequired,
|
||||
disabled: Types.bool.isRequired,
|
||||
onSave: Types.func.isRequired,
|
||||
onCancel: Types.func.isRequired
|
||||
|
@ -72,7 +50,7 @@ define([
|
|||
let period = buildPeriod(this.props.period || {});
|
||||
return {
|
||||
period: period,
|
||||
preserveCloseDate: hasDistinctCloseDate(period)
|
||||
preserveCloseDate: this.hasDistinctCloseDate(period)
|
||||
};
|
||||
},
|
||||
|
||||
|
@ -93,6 +71,43 @@ define([
|
|||
}
|
||||
},
|
||||
|
||||
hasDistinctCloseDate: function ({ endDate, closeDate }) {
|
||||
return closeDate && !_.isEqual(endDate, closeDate);
|
||||
},
|
||||
|
||||
mergePeriod: function (attr) {
|
||||
return update(this.state.period, {$merge: attr});
|
||||
},
|
||||
|
||||
changeTitle: function (e) {
|
||||
const period = this.mergePeriod({title: e.target.value});
|
||||
this.setState({period});
|
||||
},
|
||||
|
||||
changeWeight: function (e) {
|
||||
const period = this.mergePeriod({weight: roundWeight(e.target.value)});
|
||||
this.setState({period});
|
||||
},
|
||||
|
||||
changeStartDate: function (date) {
|
||||
const period = this.mergePeriod({startDate: date});
|
||||
this.setState({period});
|
||||
},
|
||||
|
||||
changeEndDate: function (date) {
|
||||
let attr = {endDate: date};
|
||||
if (!this.state.preserveCloseDate && !this.hasDistinctCloseDate(this.state.period)) {
|
||||
attr.closeDate = date;
|
||||
}
|
||||
const period = this.mergePeriod(attr);
|
||||
this.setState({period});
|
||||
},
|
||||
|
||||
changeCloseDate: function (date) {
|
||||
const period = this.mergePeriod({closeDate: date});
|
||||
this.setState({period: period, preserveCloseDate: !!date});
|
||||
},
|
||||
|
||||
hackTheDatepickers: function() {
|
||||
// This can be replaced when we have an extensible datepicker
|
||||
let $form = ReactDOM.findDOMNode(this);
|
||||
|
@ -147,6 +162,28 @@ define([
|
|||
);
|
||||
},
|
||||
|
||||
renderWeightInput: function () {
|
||||
if (!this.props.weighted) return null;
|
||||
return (
|
||||
<div className="ic-Form-control">
|
||||
<label className="ic-Label" htmlFor="weight">
|
||||
{I18n.t('Grading Period Weight')}
|
||||
</label>
|
||||
<div className="input-append">
|
||||
<input
|
||||
id="weight"
|
||||
ref={(ref) => { this.weightInput = ref }}
|
||||
type="text"
|
||||
className="span1"
|
||||
defaultValue={I18n.n(this.state.period.weight)}
|
||||
onChange={this.changeWeight}
|
||||
/>
|
||||
<span className="add-on">%</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return (
|
||||
<div className='GradingPeriodForm'>
|
||||
|
@ -163,7 +200,7 @@ define([
|
|||
className='ic-Input'
|
||||
title={I18n.t('Grading Period Title')}
|
||||
defaultValue={this.state.period.title}
|
||||
onChange={changeTitle.bind(this)}
|
||||
onChange={this.changeTitle}
|
||||
type='text'
|
||||
/>
|
||||
</div>
|
||||
|
@ -178,7 +215,7 @@ define([
|
|||
dateValue = {this.state.period.startDate}
|
||||
ref = "startDate"
|
||||
dateType = "due_at"
|
||||
handleUpdate = {changeStartDate.bind(this)}
|
||||
handleUpdate = {this.changeStartDate}
|
||||
rowKey = "start-date"
|
||||
labelledBy = "start-date-label"
|
||||
isFancyMidnight = {false}
|
||||
|
@ -195,7 +232,7 @@ define([
|
|||
dateValue = {this.state.period.endDate}
|
||||
ref = "endDate"
|
||||
dateType = "due_at"
|
||||
handleUpdate = {changeEndDate.bind(this)}
|
||||
handleUpdate = {this.changeEndDate}
|
||||
rowKey = "end-date"
|
||||
labelledBy = "end-date-label"
|
||||
isFancyMidnight = {true}
|
||||
|
@ -212,12 +249,14 @@ define([
|
|||
dateValue = {this.state.period.closeDate}
|
||||
ref = "closeDate"
|
||||
dateType = "due_at"
|
||||
handleUpdate = {changeCloseDate.bind(this)}
|
||||
handleUpdate = {this.changeCloseDate}
|
||||
rowKey = "close-date"
|
||||
labelledBy = "close-date-label"
|
||||
isFancyMidnight = {true}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{this.renderWeightInput()}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -33,11 +33,15 @@ define([
|
|||
!isNaN(date.getTime());
|
||||
};
|
||||
|
||||
const validatePeriods = function(periods) {
|
||||
const validatePeriods = function(periods, weighted) {
|
||||
if (_.any(periods, (period) => { return !(period.title || "").trim() })) {
|
||||
return [I18n.t('All grading periods must have a title')];
|
||||
}
|
||||
|
||||
if (weighted && _.any(periods, (period) => { return isNaN(period.weight) || period.weight < 0 })) {
|
||||
return [I18n.t('All weights must be greater than or equal to 0')];
|
||||
}
|
||||
|
||||
let validDates = _.all(periods, (period) => {
|
||||
return isValidDate(period.startDate) &&
|
||||
isValidDate(period.endDate) &&
|
||||
|
@ -101,7 +105,8 @@ define([
|
|||
|
||||
set: shape({
|
||||
id: string.isRequired,
|
||||
title: string.isRequired
|
||||
title: string.isRequired,
|
||||
weighted: bool
|
||||
}).isRequired,
|
||||
|
||||
urls: shape({
|
||||
|
@ -121,6 +126,7 @@ define([
|
|||
getInitialState() {
|
||||
return {
|
||||
title: this.props.set.title,
|
||||
weighted: this.props.set.weighted || false,
|
||||
gradingPeriods: sortPeriods(this.props.gradingPeriods),
|
||||
newPeriod: {
|
||||
period: null,
|
||||
|
@ -199,7 +205,7 @@ define([
|
|||
|
||||
saveNewPeriod(period) {
|
||||
let periods = this.state.gradingPeriods.concat([period]);
|
||||
let validations = validatePeriods(periods);
|
||||
let validations = validatePeriods(periods, this.state.weighted);
|
||||
if (_.isEmpty(validations)) {
|
||||
this.setNewPeriod({saving: true});
|
||||
gradingPeriodsApi.batchUpdate(this.props.set.id, periods)
|
||||
|
@ -236,7 +242,7 @@ define([
|
|||
let periods = _.reject(this.state.gradingPeriods, function(_period) {
|
||||
return period.id === _period.id;
|
||||
}).concat([period]);
|
||||
let validations = validatePeriods(periods);
|
||||
let validations = validatePeriods(periods, this.state.weighted);
|
||||
if (_.isEmpty(validations)) {
|
||||
this.setEditPeriod({ saving: true });
|
||||
gradingPeriodsApi.batchUpdate(this.props.set.id, periods)
|
||||
|
@ -334,6 +340,7 @@ define([
|
|||
className = 'GradingPeriodList__period--editing pad-box'>
|
||||
<GradingPeriodForm ref = "editPeriodForm"
|
||||
period = {period}
|
||||
weighted = {this.state.weighted}
|
||||
disabled = {this.state.editPeriod.saving}
|
||||
onSave = {this.updatePeriod}
|
||||
onCancel = {this.cancelEditPeriod} />
|
||||
|
@ -344,6 +351,7 @@ define([
|
|||
<GradingPeriod key={"show-grading-period-" + period.id}
|
||||
ref={getShowGradingPeriodRef(period)}
|
||||
period={period}
|
||||
weighted={this.state.weighted}
|
||||
actionsDisabled={actionsDisabled}
|
||||
onEdit={this.editPeriod}
|
||||
readOnly={this.props.readOnly}
|
||||
|
@ -387,6 +395,7 @@ define([
|
|||
<div className='GradingPeriodList__new-period--editing border border-rbl border-round-b pad-box'>
|
||||
<GradingPeriodForm key = 'new-grading-period'
|
||||
ref = 'newPeriodForm'
|
||||
weighted = {this.state.weighted}
|
||||
disabled = {this.state.newPeriod.saving}
|
||||
onSave = {this.saveNewPeriod}
|
||||
onCancel = {this.removeNewPeriodForm} />
|
||||
|
|
|
@ -3,11 +3,12 @@ define([
|
|||
'underscore',
|
||||
'jquery',
|
||||
'instructure-ui/Button',
|
||||
'instructure-ui/Checkbox',
|
||||
'i18n!grading_periods',
|
||||
'compiled/api/gradingPeriodSetsApi',
|
||||
'jsx/grading/EnrollmentTermInput',
|
||||
'compiled/jquery.rails_flash_notifications'
|
||||
], function(React, _, $, { default: Button }, I18n, setsApi, EnrollmentTermInput) {
|
||||
], function(React, _, $, { default: Button }, { default: Checkbox }, I18n, setsApi, EnrollmentTermInput) {
|
||||
|
||||
let NewGradingPeriodSetForm = React.createClass({
|
||||
propTypes: {
|
||||
|
@ -24,6 +25,7 @@ define([
|
|||
return {
|
||||
buttonsDisabled: false,
|
||||
title: "",
|
||||
weighted: false,
|
||||
selectedEnrollmentTermIDs: []
|
||||
};
|
||||
},
|
||||
|
@ -55,7 +57,7 @@ define([
|
|||
event.preventDefault();
|
||||
this.setState({ buttonsDisabled: true }, () => {
|
||||
if(this.isValid()) {
|
||||
let set = { title: this.state.title.trim() };
|
||||
let set = { title: this.state.title.trim(), weighted: this.state.weighted };
|
||||
set.enrollmentTermIDs = this.state.selectedEnrollmentTermIDs;
|
||||
setsApi.create(set)
|
||||
.then(this.submitSucceeded)
|
||||
|
@ -80,6 +82,10 @@ define([
|
|||
this.setState({ title: event.target.value });
|
||||
},
|
||||
|
||||
onSetWeightedChange(event) {
|
||||
this.setState({ weighted: event.target.checked });
|
||||
},
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="GradingPeriodSetForm pad-box">
|
||||
|
@ -100,6 +106,15 @@ define([
|
|||
selectedIDs = {this.state.selectedEnrollmentTermIDs}
|
||||
setSelectedEnrollmentTermIDs = {this.setSelectedEnrollmentTermIDs}
|
||||
/>
|
||||
<div className="ic-Input">
|
||||
<Checkbox
|
||||
ref={(ref) => { this.weightedCheckbox = ref }}
|
||||
label={I18n.t('Weighted grading periods')}
|
||||
value="weighted"
|
||||
checked={this.state.weighted}
|
||||
onChange={this.onSetWeightedChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid-row">
|
||||
|
|
|
@ -40,7 +40,7 @@ define([
|
|||
title: nextProps.title,
|
||||
startDate: nextProps.startDate,
|
||||
endDate: nextProps.endDate,
|
||||
weight: nextProps.weight,
|
||||
weight: nextProps.weight
|
||||
});
|
||||
},
|
||||
|
||||
|
|
|
@ -25,8 +25,7 @@ function(React, update, GradingPeriod, $, I18n, _, ConvertCase) {
|
|||
periods: null,
|
||||
readOnly: false,
|
||||
disabled: false,
|
||||
saveDisabled: true,
|
||||
canChangeGradingPeriodsSetting: false
|
||||
saveDisabled: true
|
||||
};
|
||||
},
|
||||
|
||||
|
@ -41,7 +40,6 @@ function(React, update, GradingPeriod, $, I18n, _, ConvertCase) {
|
|||
self.setState({
|
||||
periods: self.deserializePeriods(periods),
|
||||
readOnly: periods.grading_periods_read_only,
|
||||
canChangeGradingPeriodsSetting: periods.can_toggle_grading_periods,
|
||||
disabled: false,
|
||||
saveDisabled: _.isEmpty(periods.grading_periods)
|
||||
});
|
||||
|
@ -200,22 +198,8 @@ function(React, update, GradingPeriod, $, I18n, _, ConvertCase) {
|
|||
});
|
||||
},
|
||||
|
||||
renderLinkToSettingsPage: function () {
|
||||
if (this.state.canChangeGradingPeriodsSetting && this.state.periods && this.state.periods.length <= 1) {
|
||||
return (
|
||||
<span id='disable-feature-message' ref='linkToSettings'>
|
||||
{I18n.t('You can disable this feature ')}
|
||||
<a
|
||||
href={ENV.CONTEXT_SETTINGS_URL + '#tab-features'}
|
||||
aria-label={I18n.t('Feature Options')}
|
||||
> {I18n.t('here.')} </a>
|
||||
</span>);
|
||||
}
|
||||
},
|
||||
|
||||
renderSaveButton: function () {
|
||||
if (periodsAreLoaded(this.state) && !this.state.readOnly && _.all(this.state.periods, period => period.permissions.update)) {
|
||||
let buttonText = this.state.disabled ? I18n.t('Updating') : I18n.t('Save');
|
||||
return (
|
||||
<div className='form-actions'>
|
||||
<button
|
||||
|
@ -224,7 +208,7 @@ function(React, update, GradingPeriod, $, I18n, _, ConvertCase) {
|
|||
disabled={this.state.disabled || this.state.saveDisabled}
|
||||
onClick={this.batchUpdatePeriods}
|
||||
>
|
||||
{buttonText}
|
||||
{this.state.disabled ? I18n.t('Updating') : I18n.t('Save')}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
|
@ -257,9 +241,6 @@ function(React, update, GradingPeriod, $, I18n, _, ConvertCase) {
|
|||
render: function () {
|
||||
return (
|
||||
<div>
|
||||
<div id='messages'>
|
||||
{this.renderLinkToSettingsPage()}
|
||||
</div>
|
||||
<div id='grading_periods' className='content-box'>
|
||||
{this.renderGradingPeriods()}
|
||||
</div>
|
||||
|
|
|
@ -1044,10 +1044,15 @@ class Course < ActiveRecord::Base
|
|||
end
|
||||
end
|
||||
|
||||
def recompute_student_scores(student_ids = nil, grading_period_id: nil)
|
||||
def recompute_student_scores(student_ids = nil, grading_period_id: nil, update_all_grading_period_scores: true)
|
||||
student_ids ||= self.student_ids
|
||||
Rails.logger.info "GRADES: recomputing scores in course=#{global_id} students=#{student_ids.inspect}"
|
||||
GradeCalculator.recompute_final_score(student_ids, self.id, grading_period_id: grading_period_id)
|
||||
Enrollment.recompute_final_score(
|
||||
student_ids,
|
||||
self.id,
|
||||
grading_period_id: grading_period_id,
|
||||
update_all_grading_period_scores: update_all_grading_period_scores
|
||||
)
|
||||
end
|
||||
handle_asynchronously_if_production :recompute_student_scores,
|
||||
:singleton => proc { |c| "recompute_student_scores:#{ c.global_id }" }
|
||||
|
@ -1666,7 +1671,6 @@ class Course < ActiveRecord::Base
|
|||
update_all(:grade_publishing_status => 'error', :grade_publishing_message => "Timed out.")
|
||||
end
|
||||
|
||||
|
||||
def gradebook_to_csv_in_background(filename, user, options = {})
|
||||
progress = progresses.build(tag: 'gradebook_to_csv')
|
||||
progress.save!
|
||||
|
@ -3092,6 +3096,18 @@ class Course < ActiveRecord::Base
|
|||
effective_due_dates.any_in_closed_grading_period?
|
||||
end
|
||||
|
||||
# Does this course have grading periods?
|
||||
# checks for both legacy and account-level grading period groups
|
||||
def grading_periods?
|
||||
return @has_grading_periods unless @has_grading_periods.nil?
|
||||
|
||||
@has_grading_periods = shard.activate do
|
||||
GradingPeriodGroup.active.
|
||||
where("id = ? or course_id = ?", enrollment_term.grading_period_group_id, id).
|
||||
exists?
|
||||
end
|
||||
end
|
||||
|
||||
def quiz_lti_tool
|
||||
query = { tool_id: 'Quizzes 2' }
|
||||
context_external_tools.active.find_by(query) ||
|
||||
|
|
|
@ -35,6 +35,7 @@ class EnrollmentTerm < ActiveRecord::Base
|
|||
|
||||
before_validation :verify_unique_sis_source_id
|
||||
after_save :update_courses_later_if_necessary
|
||||
after_save :recompute_course_scores, if: :grading_period_group_id_has_changed?
|
||||
|
||||
include StickySisFields
|
||||
are_sis_sticky :name, :start_at, :end_at
|
||||
|
@ -64,6 +65,12 @@ class EnrollmentTerm < ActiveRecord::Base
|
|||
self.courses.touch_all
|
||||
end
|
||||
|
||||
def recompute_course_scores(update_all_grading_period_scores: true)
|
||||
courses.active.each do |course|
|
||||
course.recompute_student_scores(update_all_grading_period_scores: update_all_grading_period_scores)
|
||||
end
|
||||
end
|
||||
|
||||
def update_courses_and_states_later(enrollment_type=nil)
|
||||
return if new_record?
|
||||
|
||||
|
@ -186,4 +193,12 @@ class EnrollmentTerm < ActiveRecord::Base
|
|||
scope :not_started, -> { where('enrollment_terms.start_at IS NOT NULL AND enrollment_terms.start_at > ?', Time.now.utc) }
|
||||
scope :not_default, -> { where.not(name: EnrollmentTerm::DEFAULT_TERM_NAME)}
|
||||
scope :by_name, -> { order(best_unicode_collation_key('name')) }
|
||||
|
||||
private
|
||||
|
||||
def grading_period_group_id_has_changed?
|
||||
# migration 20111111214313_add_trust_link_for_default_account
|
||||
# will throw an error without this check
|
||||
respond_to?(:grading_period_group_id_changed?) && grading_period_group_id_changed?
|
||||
end
|
||||
end
|
||||
|
|
|
@ -53,11 +53,12 @@ class FeatureFlag < ActiveRecord::Base
|
|||
def clear_cache
|
||||
if self.context
|
||||
self.class.connection.after_transaction_commit { MultiCache.delete(self.context.feature_flag_cache_key(feature)) }
|
||||
self.context.touch if Feature.definitions[feature].touch_context
|
||||
self.context.touch if Feature.definitions[feature].try(:touch_context)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
private
|
||||
|
||||
def valid_state
|
||||
errors.add(:state, "is not valid in context") unless %w(off on).include?(state) || context.is_a?(Account) && state == 'allowed'
|
||||
end
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
#
|
||||
# Copyright (C) 2015-2016 Instructure, Inc.
|
||||
# Copyright (C) 2015 - 2016 Instructure, Inc.
|
||||
#
|
||||
# This file is part of Canvas.
|
||||
#
|
||||
|
@ -23,6 +23,7 @@ class GradingPeriod < ActiveRecord::Base
|
|||
has_many :scores, -> { active }
|
||||
|
||||
validates :title, :start_date, :end_date, :close_date, :grading_period_group_id, presence: true
|
||||
validates :weight, numericality: true, allow_nil: true
|
||||
validate :start_date_is_before_end_date
|
||||
validate :close_date_is_on_or_after_end_date
|
||||
validate :not_overlapping, unless: :skip_not_overlapping_validator?
|
||||
|
@ -30,7 +31,7 @@ class GradingPeriod < ActiveRecord::Base
|
|||
before_validation :adjust_close_date_for_course_period
|
||||
before_validation :ensure_close_date
|
||||
|
||||
after_save :recompute_scores, if: :dates_changed?
|
||||
after_save :recompute_scores, if: :dates_or_weight_changed?
|
||||
after_destroy :destroy_grading_period_set, if: :last_remaining_legacy_period?
|
||||
after_destroy :destroy_scores
|
||||
|
||||
|
@ -151,7 +152,7 @@ class GradingPeriod < ActiveRecord::Base
|
|||
|
||||
def as_json_with_user_permissions(user)
|
||||
as_json(
|
||||
only: [:id, :title, :start_date, :end_date, :close_date],
|
||||
only: [:id, :title, :start_date, :end_date, :close_date, :weight],
|
||||
permissions: { user: user },
|
||||
methods: [:is_last, :is_closed],
|
||||
).fetch(:grading_period)
|
||||
|
@ -230,13 +231,31 @@ class GradingPeriod < ActiveRecord::Base
|
|||
courses = [grading_period_group.course]
|
||||
else
|
||||
term_ids = grading_period_group.enrollment_terms.pluck(:id)
|
||||
courses = Course.where(enrollment_term_id: term_ids)
|
||||
courses = Course.active.where(enrollment_term_id: term_ids)
|
||||
end
|
||||
|
||||
courses.each do |course|
|
||||
course.recompute_student_scores(
|
||||
# different assignments could fall in this period if time
|
||||
# boundaries changed so we need to recalculate scores.
|
||||
# otherwise, weight must have changed, in which case we
|
||||
# do not need to recompute the grading period scores (we
|
||||
# only need to recompute the overall course score)
|
||||
grading_period_id: time_boundaries_changed? ? id : nil,
|
||||
update_all_grading_period_scores: false
|
||||
)
|
||||
end
|
||||
# Course#recompute_student_scores is asynchronous
|
||||
courses.each { |course| course.recompute_student_scores(grading_period_id: self.id) }
|
||||
end
|
||||
|
||||
def dates_changed?
|
||||
def weight_actually_changed?
|
||||
grading_period_group.weighted && weight_changed?
|
||||
end
|
||||
|
||||
def time_boundaries_changed?
|
||||
start_date_changed? || end_date_changed?
|
||||
end
|
||||
|
||||
def dates_or_weight_changed?
|
||||
time_boundaries_changed? || weight_actually_changed?
|
||||
end
|
||||
end
|
||||
|
|
|
@ -26,31 +26,28 @@ class GradingPeriodGroup < ActiveRecord::Base
|
|||
|
||||
validate :associated_with_course_or_root_account, if: :active?
|
||||
|
||||
after_save :recompute_course_scores, if: :weighted_changed?
|
||||
after_destroy :dissociate_enrollment_terms
|
||||
|
||||
set_policy do
|
||||
given do |user|
|
||||
multiple_grading_periods_enabled? &&
|
||||
(course || root_account).grants_right?(user, :read)
|
||||
end
|
||||
can :read
|
||||
|
||||
given do |user|
|
||||
root_account &&
|
||||
multiple_grading_periods_enabled? &&
|
||||
root_account.associated_user?(user)
|
||||
end
|
||||
can :read
|
||||
|
||||
given do |user|
|
||||
multiple_grading_periods_enabled? &&
|
||||
(course || root_account).grants_right?(user, :manage)
|
||||
end
|
||||
can :update and can :delete
|
||||
|
||||
given do |user|
|
||||
root_account &&
|
||||
multiple_grading_periods_enabled? &&
|
||||
root_account.grants_right?(user, :manage)
|
||||
end
|
||||
can :create
|
||||
|
@ -62,12 +59,14 @@ class GradingPeriodGroup < ActiveRecord::Base
|
|||
root_account.grading_period_groups.active
|
||||
end
|
||||
|
||||
def multiple_grading_periods_enabled?
|
||||
multiple_grading_periods_on_course? || multiple_grading_periods_on_account?
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def recompute_course_scores
|
||||
return course.recompute_student_scores(update_all_grading_period_scores: false) if course_id.present?
|
||||
|
||||
enrollment_terms.each { |term| term.recompute_course_scores(update_all_grading_period_scores: false) }
|
||||
end
|
||||
|
||||
def associated_with_course_or_root_account
|
||||
if course_id.blank? && account_id.blank?
|
||||
errors.add(:course_id, t("cannot be nil when account_id is nil"))
|
||||
|
@ -84,17 +83,6 @@ class GradingPeriodGroup < ActiveRecord::Base
|
|||
end
|
||||
end
|
||||
|
||||
def multiple_grading_periods_on_account?
|
||||
root_account.present? && (
|
||||
root_account.feature_enabled?(:multiple_grading_periods) ||
|
||||
root_account.feature_allowed?(:multiple_grading_periods)
|
||||
)
|
||||
end
|
||||
|
||||
def multiple_grading_periods_on_course?
|
||||
course.present? && course.feature_enabled?(:multiple_grading_periods)
|
||||
end
|
||||
|
||||
def dissociate_enrollment_terms
|
||||
enrollment_terms.update_all(grading_period_group_id: nil)
|
||||
end
|
||||
|
|
|
@ -715,6 +715,10 @@ class Group < ActiveRecord::Base
|
|||
context.feature_enabled?(feature)
|
||||
end
|
||||
|
||||
def grading_periods?
|
||||
!!context.try(:grading_periods?)
|
||||
end
|
||||
|
||||
def serialize_permissions(permissions_hash, user, session)
|
||||
permissions_hash.merge(
|
||||
create_discussion_topic: DiscussionTopic.context_allows_user_to_create?(self, user, session),
|
||||
|
|
|
@ -4,6 +4,7 @@ class GradingPeriodSetSerializer < Canvas::APISerializer
|
|||
|
||||
attributes :id,
|
||||
:title,
|
||||
:weighted,
|
||||
:account_id,
|
||||
:course_id,
|
||||
:grading_periods,
|
||||
|
|
|
@ -54,7 +54,7 @@
|
|||
|
||||
<div id="print-grades-container" class="grid-row middle-xs between-xs">
|
||||
<div class="col-xs-6" >
|
||||
<% if multiple_grading_periods? %>
|
||||
<% if grading_periods? %>
|
||||
<%= render partial: "shared/grading_periods_selector", locals: {selected_period_id: @current_grading_period_id, grading_periods: @grading_periods, enrollment_id: @presenter.student_enrollment.id, all_grading_periods_option: true} %>
|
||||
<% end %>
|
||||
</div>
|
||||
|
|
|
@ -91,7 +91,7 @@
|
|||
<div class="assignment-gradebook-container hidden">
|
||||
<div id="gradebook-toolbar" class="toolbar">
|
||||
<div class="gradebook_dropdowns">
|
||||
<% if multiple_grading_periods? %>
|
||||
<% if grading_periods? %>
|
||||
<span class="multiple-grading-periods-selector-placeholder"></span>
|
||||
<% end %>
|
||||
</div>
|
||||
|
|
|
@ -70,7 +70,7 @@
|
|||
<span data-component="GradebookMenu" data-variant="DefaultGradebook"></span>
|
||||
<span data-component="ViewOptionsMenu"></span>
|
||||
<span data-component="ActionMenu"></span>
|
||||
<% if multiple_grading_periods? %>
|
||||
<% if grading_periods? %>
|
||||
<span class="multiple-grading-periods-selector-placeholder"></span>
|
||||
<% end %>
|
||||
</div>
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
<div class="header-bar">
|
||||
<div class="header-bar-left ic-Form-control assignment-search">
|
||||
<div class="ic-Multi-input">
|
||||
{{#if ENV.MULTIPLE_GRADING_PERIODS_ENABLED}}
|
||||
{{#if ENV.HAS_GRADING_PERIODS}}
|
||||
<select class="ic-Input" id="grading_period_selector" aria-label="{{#t}}Select a Grading Period{{/t}}">
|
||||
<option value="all">{{#t}}All Grading Periods{{/t}}</option>
|
||||
{{#each ENV.active_grading_periods}}
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
class AddWeightedToGradingPeriodGroups < ActiveRecord::Migration[4.2]
|
||||
tag :predeploy
|
||||
|
||||
def self.up
|
||||
add_column :grading_period_groups, :weighted, :boolean
|
||||
end
|
||||
|
||||
def self.down
|
||||
remove_column :grading_period_groups, :weighted
|
||||
end
|
||||
end
|
|
@ -0,0 +1,16 @@
|
|||
class ClearAnyMultipleGradingPeriodsFeatureFlags < ActiveRecord::Migration[4.2]
|
||||
tag :postdeploy
|
||||
|
||||
def self.up
|
||||
DataFixup::ClearAnyMultipleGradingPeriodsFeatureFlags.send_later_if_production_enqueue_args(
|
||||
:run,
|
||||
priority: Delayed::LOWER_PRIORITY,
|
||||
max_attempts: 1,
|
||||
n_strand: "DataFixup::ClearAnyMultipleGradingPeriodsFeatureFlags:#{Shard.current.database_server.id}"
|
||||
)
|
||||
end
|
||||
|
||||
def self.down
|
||||
raise ActiveRecord::IrreversibleMigration
|
||||
end
|
||||
end
|
|
@ -45,7 +45,8 @@ module Api::V1
|
|||
@hash['course_format'] = @course.course_format if @course.course_format.present?
|
||||
@hash['restrict_enrollments_to_course_dates'] = !!@course.restrict_enrollments_to_course_dates
|
||||
if @includes.include?(:current_grading_period_scores)
|
||||
@hash['multiple_grading_periods_enabled'] = @course.feature_enabled?(:multiple_grading_periods)
|
||||
@hash['has_grading_periods'] = @course.grading_periods?
|
||||
@hash['multiple_grading_periods_enabled'] = @hash['has_grading_periods'] # for backwards compatibility
|
||||
end
|
||||
clear_unneeded_fields(@hash)
|
||||
end
|
||||
|
@ -134,14 +135,14 @@ module Api::V1
|
|||
end
|
||||
|
||||
def grading_period_scores(student_enrollments)
|
||||
mgp_enabled = @course.feature_enabled?(:multiple_grading_periods)
|
||||
totals_for_all_grading_periods_option = mgp_enabled &&
|
||||
has_grading_periods = @course.grading_periods?
|
||||
totals_for_all_grading_periods_option = has_grading_periods &&
|
||||
@course.feature_enabled?(:all_grading_periods_totals)
|
||||
current_period = mgp_enabled && GradingPeriod.current_period_for(@course)
|
||||
if mgp_enabled && current_period
|
||||
current_period = has_grading_periods && GradingPeriod.current_period_for(@course)
|
||||
if has_grading_periods && current_period
|
||||
calculated_grading_period_scores(student_enrollments, current_period, totals_for_all_grading_periods_option)
|
||||
else
|
||||
nil_grading_period_scores(student_enrollments, mgp_enabled, totals_for_all_grading_periods_option)
|
||||
nil_grading_period_scores(student_enrollments, has_grading_periods, totals_for_all_grading_periods_option)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -157,7 +158,8 @@ module Api::V1
|
|||
scores = {}
|
||||
student_enrollments.each_with_index do |enrollment, index|
|
||||
scores[enrollment.id] = current_period_scores[index].merge({
|
||||
multiple_grading_periods_enabled: true,
|
||||
has_grading_periods: true,
|
||||
multiple_grading_periods_enabled: true, # for backwards compatibility
|
||||
totals_for_all_grading_periods_option: totals_for_all_grading_periods_option,
|
||||
current_grading_period_title: current_period.title,
|
||||
current_grading_period_id: current_period.id
|
||||
|
@ -167,11 +169,12 @@ module Api::V1
|
|||
end
|
||||
|
||||
|
||||
def nil_grading_period_scores(student_enrollments, mgp_enabled, totals_for_all_grading_periods_option)
|
||||
def nil_grading_period_scores(student_enrollments, has_grading_periods, totals_for_all_grading_periods_option)
|
||||
scores = {}
|
||||
student_enrollments.each do |enrollment|
|
||||
scores[enrollment.id] = {
|
||||
multiple_grading_periods_enabled: mgp_enabled,
|
||||
has_grading_periods: has_grading_periods,
|
||||
multiple_grading_periods_enabled: has_grading_periods, # for backwards compatibility
|
||||
totals_for_all_grading_periods_option: totals_for_all_grading_periods_option,
|
||||
current_grading_period_title: nil,
|
||||
current_grading_period_id: nil,
|
||||
|
|
|
@ -279,8 +279,7 @@ module Api::V1::User
|
|||
return opts[:grading_period] if opts[:grading_period]
|
||||
return nil unless opts[:current_grading_period_scores]
|
||||
|
||||
mgp_enabled = course.feature_enabled?(:multiple_grading_periods)
|
||||
mgp_enabled ? GradingPeriod.current_period_for(course) : nil
|
||||
GradingPeriod.current_period_for(course)
|
||||
end
|
||||
|
||||
def grade_permissions?(user, enrollment)
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
module DataFixup
|
||||
module ClearAnyMultipleGradingPeriodsFeatureFlags
|
||||
def self.run
|
||||
FeatureFlag.where(feature: 'multiple_grading_periods').destroy_all
|
||||
end
|
||||
end
|
||||
end
|
|
@ -50,7 +50,7 @@ class EffectiveDueDates
|
|||
def any_in_closed_grading_period?
|
||||
return @any_in_closed_grading_period unless @any_in_closed_grading_period.nil?
|
||||
|
||||
@any_in_closed_grading_period = grading_periods_enabled? &&
|
||||
@any_in_closed_grading_period = @context.grading_periods? &&
|
||||
to_hash.any? do |_, assignment_due_dates|
|
||||
any_student_in_closed_grading_period?(assignment_due_dates)
|
||||
end
|
||||
|
@ -62,8 +62,8 @@ class EffectiveDueDates
|
|||
assignment_id = assignment_id.id if assignment_id.is_a?(Assignment)
|
||||
return false if assignment_id.nil?
|
||||
|
||||
# false if MGP isn't even enabled
|
||||
return false unless grading_periods_enabled?
|
||||
# false if there aren't even grading periods set up
|
||||
return false unless @context.grading_periods?
|
||||
# if we've already checked all assignments and it was false,
|
||||
# no need to check this one specifically
|
||||
return false if @any_in_closed_grading_period == false
|
||||
|
@ -99,11 +99,6 @@ class EffectiveDueDates
|
|||
true
|
||||
end
|
||||
|
||||
def grading_periods_enabled?
|
||||
return @grading_periods_enabled unless @grading_periods_enabled.nil?
|
||||
@grading_periods_enabled = @context.feature_enabled?(:multiple_grading_periods)
|
||||
end
|
||||
|
||||
def any_student_in_closed_grading_period?(assignment_due_dates)
|
||||
return false unless assignment_due_dates
|
||||
assignment_due_dates.any? { |_, student| student[:in_closed_grading_period] }
|
||||
|
@ -114,11 +109,11 @@ class EffectiveDueDates
|
|||
end
|
||||
|
||||
# This beauty of a method brings together assignment overrides,
|
||||
# due dates, multiple grading periods, course/group enrollments,
|
||||
# etc to calculate each student's effective due date and whether
|
||||
# or not that due date is in a closed grading period. If a
|
||||
# student is not included in this hash, that student cannot see
|
||||
# this assignment. The format of the returned hash is:
|
||||
# due dates, grading periods, course/group enrollments, etc
|
||||
# to calculate each student's effective due date and whether or
|
||||
# not that due date is in a closed grading period. If a student
|
||||
# is not included in this hash, that student cannot see this
|
||||
# assignment. The format of the returned hash is:
|
||||
# {
|
||||
# assignment_id => {
|
||||
# student_id => {
|
||||
|
|
|
@ -243,17 +243,6 @@ END
|
|||
applies_to: 'RootAccount',
|
||||
state: 'hidden'
|
||||
},
|
||||
'multiple_grading_periods' =>
|
||||
{
|
||||
display_name: -> { I18n.t('features.multiple_grading_periods', 'Multiple Grading Periods') },
|
||||
description: -> { I18n.t('enable_multiple_grading_periods', <<-END) },
|
||||
Multiple Grading Periods allows teachers and admins to create grading periods with set
|
||||
cutoff dates. Assignments can be filtered by these grading periods in the gradebook.
|
||||
END
|
||||
applies_to: 'Course',
|
||||
state: 'allowed',
|
||||
root_opt_in: true
|
||||
},
|
||||
'course_catalog' =>
|
||||
{
|
||||
display_name: -> { I18n.t("Public Course Index") },
|
||||
|
@ -390,7 +379,7 @@ END
|
|||
'all_grading_periods_totals' =>
|
||||
{
|
||||
display_name: -> { I18n.t('Display Totals for "All Grading Periods"') },
|
||||
description: -> { I18n.t('Display total grades when the "All Grading Periods" dropdown option is selected (Multiple Grading Periods must be enabled).') },
|
||||
description: -> { I18n.t('Display total grades when the "All Grading Periods" dropdown option is selected (grading periods must exist).') },
|
||||
applies_to: 'Course',
|
||||
state: 'allowed',
|
||||
root_opt_in: true
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
#
|
||||
# Copyright (C) 2011 - 2014 Instructure, Inc.
|
||||
# Copyright (C) 2011 - 2017 Instructure, Inc.
|
||||
#
|
||||
# This file is part of Canvas.
|
||||
#
|
||||
|
@ -65,33 +65,18 @@ class GradeCalculator
|
|||
|
||||
def compute_scores
|
||||
@submissions = @course.submissions.
|
||||
except(:order, :select).
|
||||
for_user(@user_ids).
|
||||
where(assignment_id: @assignments).
|
||||
select("submissions.id, user_id, assignment_id, score, excused, submissions.workflow_state")
|
||||
submissions_by_user = @submissions.group_by(&:user_id)
|
||||
|
||||
result = []
|
||||
except(:order, :select).
|
||||
for_user(@user_ids).
|
||||
where(assignment_id: @assignments).
|
||||
select("submissions.id, user_id, assignment_id, score, excused, submissions.workflow_state")
|
||||
scores_and_group_sums = []
|
||||
@user_ids.each_slice(100) do |batched_ids|
|
||||
load_assignment_visibilities_for_users(batched_ids)
|
||||
batched_ids.each do |user_id|
|
||||
user_submissions = submissions_by_user[user_id] || []
|
||||
user_submissions.select!{|s| assignment_ids_visible_to_user(user_id).include?(s.assignment_id)}
|
||||
current, current_groups = calculate_current_score(user_id, user_submissions)
|
||||
final, final_groups = calculate_final_score(user_id, user_submissions)
|
||||
|
||||
scores = {
|
||||
current: current,
|
||||
current_groups: current_groups,
|
||||
final: final,
|
||||
final_groups: final_groups
|
||||
}
|
||||
|
||||
result << scores
|
||||
end
|
||||
scores_and_group_sums_batch = compute_scores_and_group_sums_for_batch(batched_ids)
|
||||
scores_and_group_sums.concat(scores_and_group_sums_batch)
|
||||
clear_assignment_visibilities_cache
|
||||
end
|
||||
result
|
||||
scores_and_group_sums
|
||||
end
|
||||
|
||||
def compute_and_save_scores
|
||||
|
@ -103,8 +88,114 @@ class GradeCalculator
|
|||
|
||||
private
|
||||
|
||||
def compute_scores_and_group_sums_for_batch(user_ids)
|
||||
user_ids.map do |user_id|
|
||||
group_sums = compute_group_sums_for_user(user_id)
|
||||
scores = compute_scores_for_user(user_id, group_sums)
|
||||
update_changes_hash_for_user(user_id, scores)
|
||||
{
|
||||
current: scores[:current],
|
||||
current_groups: group_sums[:current].index_by { |group| group[:id] },
|
||||
final: scores[:final],
|
||||
final_groups: group_sums[:final].index_by { |group| group[:id] }
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
def compute_group_sums_for_user(user_id)
|
||||
user_submissions = submissions_by_user.fetch(user_id, []).select do |submission|
|
||||
assignment_ids_visible_to_user(user_id).include?(submission.assignment_id)
|
||||
end
|
||||
{
|
||||
current: create_group_sums(user_submissions, user_id, ignore_ungraded: true),
|
||||
final: create_group_sums(user_submissions, user_id, ignore_ungraded: false)
|
||||
}
|
||||
end
|
||||
|
||||
def compute_scores_for_user(user_id, group_sums)
|
||||
if compute_course_scores_from_weighted_grading_periods?
|
||||
scores = calculate_total_from_weighted_grading_periods(user_id)
|
||||
else
|
||||
scores = {
|
||||
current: calculate_total_from_group_scores(group_sums[:current]),
|
||||
final: calculate_total_from_group_scores(group_sums[:final])
|
||||
}
|
||||
end
|
||||
Rails.logger.info "GRADES: calculated: #{scores.inspect}"
|
||||
scores
|
||||
end
|
||||
|
||||
def update_changes_hash_for_user(user_id, scores)
|
||||
@current_updates[user_id] = scores[:current][:grade]
|
||||
@final_updates[user_id] = scores[:final][:grade]
|
||||
end
|
||||
|
||||
def calculate_total_from_weighted_grading_periods(user_id)
|
||||
enrollment = enrollments_by_user[user_id].first
|
||||
grading_period_ids = grading_periods_for_course.map(&:id)
|
||||
# using Enumberable#select because the scores are preloaded
|
||||
grading_period_scores = enrollment.scores.select do |score|
|
||||
grading_period_ids.include?(score.grading_period_id)
|
||||
end
|
||||
scores = apply_grading_period_weights_to_scores(grading_period_scores)
|
||||
scale_and_round_scores(scores, grading_period_scores)
|
||||
end
|
||||
|
||||
def apply_grading_period_weights_to_scores(grading_period_scores)
|
||||
grading_period_scores.each_with_object(
|
||||
{ current: { full_weight: 0.0, grade: 0.0 }, final: { full_weight: 0.0, grade: 0.0 } }
|
||||
) do |score, scores|
|
||||
weight = grading_period_weights[score.grading_period_id] || 0.0
|
||||
scores[:final][:full_weight] += weight
|
||||
scores[:current][:full_weight] += weight if score.current_score
|
||||
scores[:current][:grade] += (score.current_score || 0.0) * (weight / 100.0)
|
||||
scores[:final][:grade] += (score.final_score || 0.0) * (weight / 100.0)
|
||||
end
|
||||
end
|
||||
|
||||
def scale_and_round_scores(scores, grading_period_scores)
|
||||
[:current, :final].each_with_object({ current: {}, final: {} }) do |score_type, adjusted_scores|
|
||||
score = scores[score_type][:grade]
|
||||
full_weight = scores[score_type][:full_weight]
|
||||
score = scale_score_up(score, full_weight) if full_weight < 100
|
||||
if score == 0.0 && score_type == :current && grading_period_scores.none?(&:current_score)
|
||||
score = nil
|
||||
end
|
||||
adjusted_scores[score_type][:grade] = score ? score.round(2) : score
|
||||
end
|
||||
end
|
||||
|
||||
def scale_score_up(score, weight)
|
||||
return 0.0 if weight.zero?
|
||||
(score * 100.0) / weight
|
||||
end
|
||||
|
||||
def compute_course_scores_from_weighted_grading_periods?
|
||||
return @compute_from_weighted_periods if @compute_from_weighted_periods.present?
|
||||
|
||||
if @grading_period || grading_periods_for_course.empty?
|
||||
@compute_from_weighted_periods = false
|
||||
else
|
||||
@compute_from_weighted_periods = grading_periods_for_course.first.grading_period_group.weighted?
|
||||
end
|
||||
end
|
||||
|
||||
def grading_periods_for_course
|
||||
@periods ||= GradingPeriod.for(@course)
|
||||
end
|
||||
|
||||
def grading_period_weights
|
||||
@grading_period_weights ||= grading_periods_for_course.each_with_object({}) do |period, weights|
|
||||
weights[period.id] = period.weight
|
||||
end
|
||||
end
|
||||
|
||||
def submissions_by_user
|
||||
@submissions_by_user ||= @submissions.group_by(&:user_id)
|
||||
end
|
||||
|
||||
def calculate_grading_period_scores
|
||||
GradingPeriod.for(@course).each do |grading_period|
|
||||
grading_periods_for_course.each do |grading_period|
|
||||
# update this grading period score, and do not
|
||||
# update any other scores (grading period or course)
|
||||
# after this one
|
||||
|
@ -132,7 +223,7 @@ class GradeCalculator
|
|||
def enrollments
|
||||
@enrollments ||= Enrollment.shard(@course).active.
|
||||
where(user_id: @user_ids, course_id: @course.id).
|
||||
select(:id, :user_id)
|
||||
select(:id, :user_id).preload(:scores)
|
||||
end
|
||||
|
||||
def joined_enrollment_ids
|
||||
|
@ -225,27 +316,6 @@ class GradeCalculator
|
|||
end
|
||||
end
|
||||
|
||||
# The score ignoring unsubmitted assignments
|
||||
def calculate_current_score(user_id, submissions)
|
||||
calculate_score(submissions, user_id, true)
|
||||
end
|
||||
|
||||
# The final score for the class, so unsubmitted assignments count as zeros
|
||||
def calculate_final_score(user_id, submissions)
|
||||
calculate_score(submissions, user_id, false)
|
||||
end
|
||||
|
||||
def calculate_score(submissions, user_id, ignore_ungraded)
|
||||
group_sums = create_group_sums(submissions, user_id, ignore_ungraded)
|
||||
info = calculate_total_from_group_scores(group_sums)
|
||||
Rails.logger.info "GRADES: calculated: #{info.inspect}"
|
||||
|
||||
updates_hash = ignore_ungraded ? @current_updates : @final_updates
|
||||
updates_hash[user_id] = info[:grade]
|
||||
|
||||
[info, group_sums.index_by { |s| s[:id] }]
|
||||
end
|
||||
|
||||
# returns information about assignments groups in the form:
|
||||
# [
|
||||
# {
|
||||
|
@ -256,7 +326,7 @@ class GradeCalculator
|
|||
# :weight => 50},
|
||||
# ...]
|
||||
# each group
|
||||
def create_group_sums(submissions, user_id, ignore_ungraded=true)
|
||||
def create_group_sums(submissions, user_id, ignore_ungraded: true)
|
||||
visible_assignments = @assignments
|
||||
visible_assignments = visible_assignments.select{|a| assignment_ids_visible_to_user(user_id).include?(a.id)}
|
||||
|
||||
|
|
|
@ -17,7 +17,6 @@
|
|||
#
|
||||
|
||||
class GradebookExporter
|
||||
include GradebookTransformer
|
||||
include GradebookSettingsHelpers
|
||||
|
||||
def initialize(course, user, options = {})
|
||||
|
@ -29,7 +28,7 @@ class GradebookExporter
|
|||
def to_csv
|
||||
enrollment_scope = @course.apply_enrollment_visibility(gradebook_enrollment_scope, @user, nil,
|
||||
include: gradebook_includes)
|
||||
student_enrollments = enrollments_for_csv(enrollment_scope, @options)
|
||||
student_enrollments = enrollments_for_csv(enrollment_scope)
|
||||
|
||||
student_section_names = {}
|
||||
student_enrollments.each do |enrollment|
|
||||
|
@ -53,7 +52,7 @@ class GradebookExporter
|
|||
submissions = {}
|
||||
calc.submissions.each { |s| submissions[[s.user_id, s.assignment_id]] = s }
|
||||
|
||||
assignments = select_in_grading_period calc.assignments, @course, grading_period
|
||||
assignments = select_in_grading_period calc.assignments, grading_period
|
||||
|
||||
assignments = assignments.sort_by do |a|
|
||||
[a.assignment_group_id, a.position || 0, a.due_at || CanvasSort::Last, a.title]
|
||||
|
@ -168,7 +167,8 @@ class GradebookExporter
|
|||
end
|
||||
|
||||
private
|
||||
def enrollments_for_csv(scope, options={})
|
||||
|
||||
def enrollments_for_csv(scope)
|
||||
# user: used for name in csv output
|
||||
# course_section: used for display_name in csv output
|
||||
# user > pseudonyms: used for sis_user_id/unique_id if options[:include_sis_id]
|
||||
|
@ -208,7 +208,7 @@ class GradebookExporter
|
|||
end
|
||||
|
||||
def show_totals?
|
||||
return true if !@course.feature_enabled?(:multiple_grading_periods)
|
||||
return true unless @course.grading_periods?
|
||||
return true if @options[:grading_period_id].try(:to_i) != 0
|
||||
@course.feature_enabled?(:all_grading_periods_totals)
|
||||
end
|
||||
|
@ -223,4 +223,12 @@ class GradebookExporter
|
|||
name = "=\"#{name}\"" if name =~ STARTS_WITH_EQUAL
|
||||
name
|
||||
end
|
||||
|
||||
def select_in_grading_period(assignments, grading_period)
|
||||
if grading_period
|
||||
grading_period.assignments(assignments)
|
||||
else
|
||||
assignments
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -19,8 +19,6 @@
|
|||
require 'csv'
|
||||
|
||||
class GradebookImporter
|
||||
include GradebookTransformer
|
||||
|
||||
ASSIGNMENT_PRELOADED_FIELDS = %i/
|
||||
id title points_possible grading_type updated_at context_id context_type group_category_id
|
||||
created_at due_at only_visible_to_overrides
|
||||
|
@ -321,7 +319,7 @@ class GradebookImporter
|
|||
end
|
||||
|
||||
def prevent_new_assignment_creation?(periods, is_admin)
|
||||
return false unless context.feature_enabled?(:multiple_grading_periods)
|
||||
return false unless context.grading_periods?
|
||||
return false if is_admin
|
||||
|
||||
GradingPeriod.date_in_closed_grading_period?(
|
||||
|
@ -502,7 +500,7 @@ class GradebookImporter
|
|||
|
||||
def gradeable?(submission:, periods:, is_admin:)
|
||||
user_can_grade_submission = submission.grants_right?(@user, :grade)
|
||||
return user_can_grade_submission unless @context.feature_enabled?(:multiple_grading_periods)
|
||||
return user_can_grade_submission unless @context.grading_periods?
|
||||
|
||||
user_can_grade_submission &&
|
||||
(is_admin || !GradingPeriod.date_in_closed_grading_period?(
|
||||
|
|
|
@ -1,28 +0,0 @@
|
|||
#
|
||||
# Copyright (C) 2015 Instructure, Inc.
|
||||
#
|
||||
# This file is part of Canvas.
|
||||
#
|
||||
# Canvas is free software: you can redistribute it and/or modify it under
|
||||
# the terms of the GNU Affero General Public License as published by the Free
|
||||
# Software Foundation, version 3 of the License.
|
||||
#
|
||||
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
# details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License along
|
||||
# with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
module GradebookTransformer
|
||||
private
|
||||
def select_in_grading_period(assignments, course, grading_period)
|
||||
if grading_period && course.feature_enabled?(:multiple_grading_periods)
|
||||
grading_period.assignments(assignments)
|
||||
else
|
||||
assignments
|
||||
end
|
||||
end
|
||||
end
|
|
@ -2,7 +2,7 @@ module SubmittablesGradingPeriodProtection
|
|||
def grading_periods_allow_submittable_create?(submittable, submittable_params, flash_message: false)
|
||||
apply_grading_params(submittable, submittable_params)
|
||||
return true unless submittable.graded?
|
||||
return true unless context_grading_periods_enabled? && !current_user_is_context_admin?
|
||||
return true unless grading_periods? && !current_user_is_context_admin?
|
||||
return true if submittable_params[:only_visible_to_overrides]
|
||||
|
||||
submittable.due_at = submittable_params[:due_at]
|
||||
|
@ -19,7 +19,7 @@ module SubmittablesGradingPeriodProtection
|
|||
submittable_params[:only_visible_to_overrides] if submittable_params.key?(:only_visible_to_overrides)
|
||||
submittable.due_at = submittable_params[:due_at] if submittable_params.key?(:due_at)
|
||||
return true unless submittable.only_visible_to_overrides_changed? || due_at_changed?(submittable)
|
||||
return true unless context_grading_periods_enabled? && !current_user_is_context_admin?
|
||||
return true unless grading_periods? && !current_user_is_context_admin?
|
||||
|
||||
in_closed_grading_period = date_in_closed_grading_period?(submittable.due_at_was)
|
||||
|
||||
|
@ -42,7 +42,7 @@ module SubmittablesGradingPeriodProtection
|
|||
end
|
||||
|
||||
def grading_periods_allow_assignment_overrides_batch_create?(submittable, overrides, flash_message: false)
|
||||
return true unless context_grading_periods_enabled? && !current_user_is_context_admin?
|
||||
return true unless grading_periods? && !current_user_is_context_admin?
|
||||
return true unless overrides.any? {|override| date_in_closed_grading_period?(override[:due_at])}
|
||||
|
||||
apply_error(submittable, :due_at, ERROR_MESSAGES[:set_override_due_at_in_closed], flash_message)
|
||||
|
@ -50,7 +50,7 @@ module SubmittablesGradingPeriodProtection
|
|||
end
|
||||
|
||||
def grading_periods_allow_assignment_overrides_batch_update?(submittable, prepared_batch, flash_message: false)
|
||||
return true unless context_grading_periods_enabled? && !current_user_is_context_admin?
|
||||
return true unless grading_periods? && !current_user_is_context_admin?
|
||||
can_create_overrides?(submittable, prepared_batch[:overrides_to_create], flash_message: flash_message) &&
|
||||
can_update_overrides?(submittable, prepared_batch[:overrides_to_update], flash_message: flash_message) &&
|
||||
can_delete_overrides?(submittable, prepared_batch[:overrides_to_delete], flash_message: flash_message)
|
||||
|
|
|
@ -22,7 +22,9 @@ define([
|
|||
'jquery' /* $ */,
|
||||
'underscore',
|
||||
'jsx/gradebook/CourseGradeCalculator',
|
||||
'jsx/gradebook/EffectiveDueDates',
|
||||
'jsx/gradebook/GradingSchemeHelper',
|
||||
'compiled/api/gradingPeriodSetsApi',
|
||||
'compiled/util/round',
|
||||
'str/htmlEscape',
|
||||
'jquery.ajaxJSON' /* ajaxJSON */,
|
||||
|
@ -31,11 +33,58 @@ define([
|
|||
'jquery.instructure_misc_plugins' /* showIf */,
|
||||
'jquery.templateData' /* fillTemplateData, getTemplateData */,
|
||||
'compiled/jquery/mediaCommentThumbnail', /* mediaCommentThumbnail */
|
||||
'media_comments' /* mediaComment */
|
||||
], function (INST, I18n, $, _, CourseGradeCalculator, GradingSchemeHelper, round, htmlEscape) {
|
||||
'media_comments' /* mediaComment, mediaCommentThumbnail */
|
||||
], function (
|
||||
INST, I18n, $, _, CourseGradeCalculator, EffectiveDueDates, GradingSchemeHelper, gradingPeriodSetsApi, round,
|
||||
htmlEscape
|
||||
) {
|
||||
/* eslint-disable vars-on-top */
|
||||
/* eslint-disable newline-per-chained-call */
|
||||
|
||||
var GradeSummary = {
|
||||
getGradingPeriodIdFromUrl (url) {
|
||||
var matches = url.match(/grading_period_id=(\d*)/);
|
||||
if (matches && matches[1] !== '0') {
|
||||
return matches[1];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
function getGradingPeriodSet () {
|
||||
if (ENV.grading_period_set) {
|
||||
return gradingPeriodSetsApi.deserializeSet(ENV.grading_period_set);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function calculateGrades () {
|
||||
var grades;
|
||||
|
||||
if (ENV.effective_due_dates && ENV.grading_period_set) {
|
||||
grades = CourseGradeCalculator.calculate(
|
||||
ENV.submissions,
|
||||
ENV.assignment_groups,
|
||||
ENV.group_weighting_scheme,
|
||||
getGradingPeriodSet(),
|
||||
EffectiveDueDates.scopeToUser(ENV.effective_due_dates, ENV.student_id)
|
||||
);
|
||||
} else {
|
||||
grades = CourseGradeCalculator.calculate(
|
||||
ENV.submissions,
|
||||
ENV.assignment_groups,
|
||||
ENV.group_weighting_scheme
|
||||
);
|
||||
}
|
||||
|
||||
var selectedGradingPeriodId = GradeSummary.getGradingPeriodIdFromUrl(location.href);
|
||||
if (selectedGradingPeriodId) {
|
||||
return grades.gradingPeriods[selectedGradingPeriodId];
|
||||
}
|
||||
|
||||
return grades;
|
||||
}
|
||||
|
||||
var whatIfAssignments = [];
|
||||
|
||||
function addWhatIfAssignment (assignmentId) {
|
||||
|
@ -55,14 +104,6 @@ define([
|
|||
});
|
||||
}
|
||||
|
||||
function calculateGrades () {
|
||||
return CourseGradeCalculator.calculate(
|
||||
ENV.submissions,
|
||||
listAssignmentGroupsForGradeCalculation(),
|
||||
ENV.group_weighting_scheme
|
||||
);
|
||||
}
|
||||
|
||||
function canBeConvertedToGrade (score, possible) {
|
||||
return possible > 0 && !isNaN(score);
|
||||
}
|
||||
|
@ -86,15 +127,20 @@ define([
|
|||
function calculateTotals (calculatedGrades, currentOrFinal, groupWeightingScheme) {
|
||||
var showTotalGradeAsPoints = ENV.show_total_grade_as_points;
|
||||
|
||||
for (var i = 0; i < calculatedGrades.group_sums.length; i++) {
|
||||
var groupSum = calculatedGrades.group_sums[i];
|
||||
var $groupRow = $('#submission_group-' + groupSum.group.id);
|
||||
var groupGradeInfo = groupSum[currentOrFinal];
|
||||
for (var i = 0; i < ENV.assignment_groups.length; i++) {
|
||||
var assignmentGroupId = ENV.assignment_groups[i].id;
|
||||
var grade = calculatedGrades.assignmentGroups[assignmentGroupId];
|
||||
var $groupRow = $('#submission_group-' + assignmentGroupId);
|
||||
if (grade) {
|
||||
grade = grade[currentOrFinal];
|
||||
} else {
|
||||
grade = { score: 0, possible: 0 };
|
||||
}
|
||||
$groupRow.find('.grade').text(
|
||||
calculateGrade(groupGradeInfo.score, groupGradeInfo.possible)
|
||||
calculateGrade(grade.score, grade.possible)
|
||||
);
|
||||
$groupRow.find('.score_teaser').text(
|
||||
I18n.n(groupGradeInfo.score, {precision: round.DEFAULT}) + ' / ' + I18n.n(groupGradeInfo.possible, {precision: round.DEFAULT})
|
||||
I18n.n(grade.score, {precision: round.DEFAULT}) + ' / ' + I18n.n(grade.possible, {precision: round.DEFAULT})
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -152,11 +198,13 @@ define([
|
|||
|
||||
// mark dropped assignments
|
||||
$('.student_assignment').find('.points_possible').attr('aria-label', '');
|
||||
_.chain(calculatedGrades.group_sums).map(function (groupSum) {
|
||||
return groupSum[currentOrFinal].submissions;
|
||||
}).flatten().each(function (s) {
|
||||
$('#submission_' + s.submission.assignment_id).toggleClass('dropped', !!s.drop);
|
||||
|
||||
_.forEach(calculatedGrades.assignmentGroups, function (grades) {
|
||||
_.forEach(grades[currentOrFinal].submissions, function (submission) {
|
||||
$('#submission_' + submission.submission.assignment_id).toggleClass('dropped', !!submission.drop);
|
||||
});
|
||||
});
|
||||
|
||||
$('.dropped').attr('aria-label', droppedMessage);
|
||||
$('.dropped').attr('title', droppedMessage);
|
||||
|
||||
|
@ -495,10 +543,11 @@ define([
|
|||
});
|
||||
}
|
||||
|
||||
return {
|
||||
_.extend(GradeSummary, {
|
||||
setup: setup,
|
||||
addWhatIfAssignment: addWhatIfAssignment,
|
||||
removeWhatIfAssignment: removeWhatIfAssignment,
|
||||
getGradingPeriodSet: getGradingPeriodSet,
|
||||
listAssignmentGroupsForGradeCalculation: listAssignmentGroupsForGradeCalculation,
|
||||
canBeConvertedToGrade: canBeConvertedToGrade,
|
||||
calculateGrade: calculateGrade,
|
||||
|
@ -506,5 +555,7 @@ define([
|
|||
calculateTotals: calculateTotals,
|
||||
calculatePercentGrade: calculatePercentGrade,
|
||||
formatPercentGrade: formatPercentGrade
|
||||
}
|
||||
});
|
||||
|
||||
return GradeSummary;
|
||||
});
|
||||
|
|
|
@ -63,8 +63,7 @@ module AssignmentGroupsApiSpecHelper
|
|||
)
|
||||
end
|
||||
|
||||
def setup_multiple_grading_periods
|
||||
@course.account.enable_feature!(:multiple_grading_periods)
|
||||
def setup_grading_periods
|
||||
setup_groups
|
||||
@group1_assignment_today = @course.assignments.create!(assignment_group: @group1, due_at: Time.zone.now)
|
||||
@group1_assignment_future = @course.assignments.create!(assignment_group: @group1, due_at: 3.months.from_now)
|
||||
|
@ -297,9 +296,9 @@ describe AssignmentGroupsController, type: :request do
|
|||
end
|
||||
end
|
||||
|
||||
context "multiple grading periods" do
|
||||
context "grading periods" do
|
||||
before :once do
|
||||
setup_multiple_grading_periods
|
||||
setup_grading_periods
|
||||
end
|
||||
|
||||
describe "#index" do
|
||||
|
@ -613,7 +612,7 @@ describe AssignmentGroupsApiController, type: :request do
|
|||
end
|
||||
|
||||
it 'should only return assignments in the given grading period with MGP on' do
|
||||
setup_multiple_grading_periods
|
||||
setup_grading_periods
|
||||
|
||||
json = api_call(:get, "/api/v1/courses/#{@course.id}/assignment_groups/#{@group1.id}?include[]=assignments&grading_period_id=#{@gp_future.id}",
|
||||
controller: 'assignment_groups_api',
|
||||
|
@ -627,10 +626,10 @@ describe AssignmentGroupsApiController, type: :request do
|
|||
expect(json['assignments'].length).to eq 1
|
||||
end
|
||||
|
||||
it 'should not return an error when Multiple Grading Periods is turned on and no grading_period_id is passed in' do
|
||||
setup_multiple_grading_periods
|
||||
it 'should not return an error when there are grading periods and no grading_period_id is passed in' do
|
||||
setup_grading_periods
|
||||
|
||||
json = api_call(:get, "/api/v1/courses/#{@course.id}/assignment_groups/#{@group1.id}?include[]=assignments",
|
||||
api_call(:get, "/api/v1/courses/#{@course.id}/assignment_groups/#{@group1.id}?include[]=assignments",
|
||||
controller: 'assignment_groups_api',
|
||||
action: 'show',
|
||||
format: 'json',
|
||||
|
@ -898,7 +897,6 @@ describe AssignmentGroupsApiController, type: :request do
|
|||
end
|
||||
|
||||
before :once do
|
||||
@course.root_account.enable_feature!(:multiple_grading_periods)
|
||||
@grading_period_group = Factories::GradingPeriodGroupHelper.new.create_for_account(@course.root_account)
|
||||
@grading_period_group.enrollment_terms << @course.enrollment_term
|
||||
Factories::GradingPeriodHelper.new.create_for_group(@grading_period_group, {
|
||||
|
@ -948,12 +946,6 @@ describe AssignmentGroupsApiController, type: :request do
|
|||
expect(@assignment_group.reload.rules).to eq(rules_encoded)
|
||||
end
|
||||
|
||||
it "succeeds when multiple grading periods is disabled" do
|
||||
@course.root_account.disable_feature!(:multiple_grading_periods)
|
||||
call_update.call({ group_weight: 75 }, 200)
|
||||
expect(@assignment_group.reload.group_weight).to eq(75)
|
||||
end
|
||||
|
||||
it "ignores deleted assignments" do
|
||||
@assignment.destroy
|
||||
call_update.call({ group_weight: 75 }, 200)
|
||||
|
|
|
@ -1623,7 +1623,7 @@ describe AssignmentsApiController, type: :request do
|
|||
end
|
||||
end
|
||||
|
||||
context "with multiple grading periods enabled" do
|
||||
context "with grading periods" do
|
||||
def call_create(params, expected_status)
|
||||
api_call_as_user(
|
||||
@current_user,
|
||||
|
@ -1645,7 +1645,6 @@ describe AssignmentsApiController, type: :request do
|
|||
end
|
||||
|
||||
before :once do
|
||||
@course.root_account.enable_feature!(:multiple_grading_periods)
|
||||
grading_period_group = Factories::GradingPeriodGroupHelper.new.create_for_account(@course.root_account)
|
||||
term = @course.enrollment_term
|
||||
term.grading_period_group = grading_period_group
|
||||
|
@ -2508,7 +2507,7 @@ describe AssignmentsApiController, type: :request do
|
|||
end
|
||||
end
|
||||
|
||||
context "with multiple grading periods enabled" do
|
||||
context "with grading periods" do
|
||||
def create_assignment(attr)
|
||||
@course.assignments.create!({ name: "Example Assignment", submission_types: "points" }.merge(attr))
|
||||
end
|
||||
|
@ -2541,7 +2540,6 @@ describe AssignmentsApiController, type: :request do
|
|||
end
|
||||
|
||||
before :once do
|
||||
@course.root_account.enable_feature!(:multiple_grading_periods)
|
||||
grading_period_group = Factories::GradingPeriodGroupHelper.new.create_for_account(@course.root_account)
|
||||
term = @course.enrollment_term
|
||||
term.grading_period_group = grading_period_group
|
||||
|
|
|
@ -945,7 +945,6 @@ describe CoursesController, type: :request do
|
|||
|
||||
context "when an assignment is due in a closed grading period" do
|
||||
before(:once) do
|
||||
@course.root_account.enable_feature!(:multiple_grading_periods)
|
||||
@course.update_attributes(group_weighting_scheme: "equal")
|
||||
@grading_period_group = Factories::GradingPeriodGroupHelper.new.create_for_account(@course.root_account)
|
||||
term = @course.enrollment_term
|
||||
|
@ -1102,10 +1101,6 @@ describe CoursesController, type: :request do
|
|||
})
|
||||
end
|
||||
|
||||
before :each do
|
||||
@course.root_account.enable_feature!(:multiple_grading_periods)
|
||||
end
|
||||
|
||||
it "cannot change apply_assignment_group_weights with a term change" do
|
||||
@term.grading_period_group = @grading_period_group
|
||||
@term.save!
|
||||
|
@ -1146,14 +1141,6 @@ describe CoursesController, type: :request do
|
|||
expect(@course.group_weighting_scheme).to eql("equal")
|
||||
end
|
||||
|
||||
it "succeeds when multiple grading periods is disabled" do
|
||||
@course.root_account.disable_feature!(:multiple_grading_periods)
|
||||
raw_api_call(:put, @path, @params, @new_values)
|
||||
expect(response.code).to eql '200'
|
||||
@course.reload
|
||||
expect(@course.group_weighting_scheme).to eql("percent")
|
||||
end
|
||||
|
||||
it "succeeds when apply_assignment_group_weights is not changed" do
|
||||
@new_values['course']['apply_assignment_group_weights'] = false
|
||||
raw_api_call(:put, @path, @params, @new_values)
|
||||
|
@ -1636,6 +1623,7 @@ describe CoursesController, type: :request do
|
|||
context "include current grading period scores" do
|
||||
let(:grading_period_keys) do
|
||||
[ 'multiple_grading_periods_enabled',
|
||||
'has_grading_periods',
|
||||
'totals_for_all_grading_periods_option',
|
||||
'current_period_computed_current_score',
|
||||
'current_period_computed_final_score',
|
||||
|
@ -1673,33 +1661,29 @@ describe CoursesController, type: :request do
|
|||
@course2.save
|
||||
json_response = courses_api_index_call(includes: ['total_scores', 'current_grading_period_scores'])
|
||||
enrollment_json = enrollment(json_response)
|
||||
expect(enrollment_json).to_not include(*grading_period_keys)
|
||||
expect(enrollment_json).not_to include(*grading_period_keys)
|
||||
end
|
||||
|
||||
it "returns true for 'multiple_grading_periods_enabled' on the enrollment " \
|
||||
"JSON if the course has Multiple Grading Periods enabled" do
|
||||
it "returns true for 'has_grading_periods' on the enrollment " \
|
||||
"JSON if the course has grading periods" do
|
||||
json_response = courses_api_index_call(includes: ['total_scores', 'current_grading_period_scores'])
|
||||
enrollment_json = enrollment(json_response)
|
||||
expect(enrollment_json['multiple_grading_periods_enabled']).to eq(true)
|
||||
expect(enrollment_json['has_grading_periods']).to be true
|
||||
expect(enrollment_json['multiple_grading_periods_enabled']).to be true
|
||||
end
|
||||
|
||||
it "returns false for 'multiple_grading_periods_enabled' if the course has Multiple Grading Periods disabled" do
|
||||
@course2.root_account.disable_feature!(:multiple_grading_periods)
|
||||
json_response = courses_api_index_call(includes: ['total_scores', 'current_grading_period_scores'])
|
||||
enrollment_json = enrollment(json_response)
|
||||
expect(enrollment_json['multiple_grading_periods_enabled']).to eq(false)
|
||||
end
|
||||
|
||||
it "returns a 'multiple_grading_periods_enabled' key at the course-level " \
|
||||
it "returns a 'has_grading_periods' key at the course-level " \
|
||||
"on the JSON response if 'current_grading_period_scores' are requested" do
|
||||
course_json_response = courses_api_index_call(includes: ['total_scores', 'current_grading_period_scores']).first
|
||||
expect(course_json_response).to have_key 'has_grading_periods'
|
||||
expect(course_json_response).to have_key 'multiple_grading_periods_enabled'
|
||||
end
|
||||
|
||||
it "does not return a 'multiple_grading_periods_enabled' key at the course-level " \
|
||||
it "does not return a 'has_grading_periods' key at the course-level " \
|
||||
"on the JSON response if 'current_grading_period_scores' are not requested" do
|
||||
course_json_response = courses_api_index_call.first
|
||||
expect(course_json_response).to_not have_key 'multiple_grading_periods_enabled'
|
||||
expect(course_json_response).not_to have_key 'has_grading_periods'
|
||||
expect(course_json_response).not_to have_key 'multiple_grading_periods_enabled'
|
||||
end
|
||||
|
||||
context "computed scores" do
|
||||
|
@ -3487,7 +3471,6 @@ describe CoursesController, type: :request do
|
|||
let_once(:account) { Account.default }
|
||||
let_once(:test_course) { account.courses.create! }
|
||||
let_once(:grading_period) do
|
||||
account.enable_feature!(:multiple_grading_periods)
|
||||
group = account.grading_period_groups.create!(title: "Score Test Group")
|
||||
group.enrollment_terms << test_course.enrollment_term
|
||||
Factories::GradingPeriodHelper.new.create_presets_for_group(group, :current)
|
||||
|
|
|
@ -774,79 +774,73 @@ describe EnrollmentsApiController, type: :request do
|
|||
)
|
||||
end
|
||||
|
||||
context "multiple grading periods feature flag enabled" do
|
||||
before :once do
|
||||
@course.root_account.enable_feature!(:multiple_grading_periods)
|
||||
describe "user endpoint" do
|
||||
let!(:enroll_student_in_the_course) do
|
||||
student_in_course({course: @course, user: @user})
|
||||
end
|
||||
|
||||
describe "user endpoint" do
|
||||
let!(:enroll_student_in_the_course) do
|
||||
student_in_course({course: @course, user: @user})
|
||||
it "works for users" do
|
||||
@user_params[:grading_period_id] = @first_grading_period.id
|
||||
raw_api_call(:get, @user_path, @user_params)
|
||||
expect(response).to be_ok
|
||||
end
|
||||
|
||||
it "returns an error if the user is not in the grading period" do
|
||||
course = Course.create!
|
||||
grading_period_group = group_helper.legacy_create_for_course(course)
|
||||
grading_period = grading_period_group.grading_periods.create!(
|
||||
title: "unconnected to the user's course",
|
||||
start_date: 2.months.ago,
|
||||
end_date: 2.months.from_now(now)
|
||||
)
|
||||
|
||||
@user_params[:grading_period_id] = grading_period.id
|
||||
raw_api_call(:get, @user_path, @user_params)
|
||||
expect(response).not_to be_ok
|
||||
end
|
||||
|
||||
describe "grade summary" do
|
||||
let!(:grade_assignments) do
|
||||
first = @course.assignments.create! due_at: 1.month.ago
|
||||
last = @course.assignments.create! due_at: 1.month.from_now
|
||||
no_due_at = @course.assignments.create!
|
||||
|
||||
Timecop.freeze(@first_grading_period.end_date - 1.day) do
|
||||
first.grade_student @user, grade: 7, grader: @teacher
|
||||
end
|
||||
last.grade_student @user, grade: 10, grader: @teacher
|
||||
no_due_at.grade_student @user, grade: 1, grader: @teacher
|
||||
end
|
||||
|
||||
it "works for users" do
|
||||
@user_params[:grading_period_id] = @first_grading_period.id
|
||||
raw_api_call(:get, @user_path, @user_params)
|
||||
expect(response).to be_ok
|
||||
end
|
||||
describe "provides a grade summary" do
|
||||
|
||||
it "returns an error if the user is not in the grading period" do
|
||||
course = Course.create!
|
||||
grading_period_group = group_helper.legacy_create_for_course(course)
|
||||
grading_period = grading_period_group.grading_periods.create!(
|
||||
title: "unconnected to the user's course",
|
||||
start_date: 2.months.ago,
|
||||
end_date: 2.months.from_now(now)
|
||||
)
|
||||
it "for assignments due during the first grading period." do
|
||||
@user_params[:grading_period_id] = @first_grading_period.id
|
||||
|
||||
@user_params[:grading_period_id] = grading_period.id
|
||||
raw_api_call(:get, @user_path, @user_params)
|
||||
expect(response).to_not be_ok
|
||||
end
|
||||
|
||||
describe "grade summary" do
|
||||
let!(:grade_assignments) do
|
||||
first = @course.assignments.create! due_at: 1.month.ago
|
||||
last = @course.assignments.create! due_at: 1.month.from_now
|
||||
no_due_at = @course.assignments.create!
|
||||
|
||||
Timecop.freeze(@first_grading_period.end_date - 1.day) do
|
||||
first.grade_student @user, grade: 7, grader: @teacher
|
||||
end
|
||||
last.grade_student @user, grade: 10, grader: @teacher
|
||||
no_due_at.grade_student @user, grade: 1, grader: @teacher
|
||||
raw_api_call(:get, @user_path, @user_params)
|
||||
final_score = JSON.parse(response.body).first["grades"]["final_score"]
|
||||
# ten times assignment's grade of 7
|
||||
expect(final_score).to eq 70
|
||||
end
|
||||
|
||||
describe "provides a grade summary" do
|
||||
it "for assignments due during the last grading period." do
|
||||
@user_params[:grading_period_id] = @last_grading_period.id
|
||||
raw_api_call(:get, @user_path, @user_params)
|
||||
final_score = JSON.parse(response.body).first["grades"]["final_score"]
|
||||
|
||||
it "for assignments due during the first grading period." do
|
||||
@user_params[:grading_period_id] = @first_grading_period.id
|
||||
# ((10 + 1) / 1) * 10 => 110
|
||||
# ((last + no_due_at) / number_of_grading_periods) * 10
|
||||
expect(final_score).to eq 110
|
||||
end
|
||||
|
||||
raw_api_call(:get, @user_path, @user_params)
|
||||
final_score = JSON.parse(response.body).first["grades"]["final_score"]
|
||||
# ten times assignment's grade of 7
|
||||
expect(final_score).to eq 70
|
||||
end
|
||||
it "for all assignments when no grading period is specified." do
|
||||
@user_params[:grading_period_id] = nil
|
||||
raw_api_call(:get, @user_path, @user_params)
|
||||
final_score = JSON.parse(response.body).first["grades"]["final_score"]
|
||||
|
||||
it "for assignments due during the last grading period." do
|
||||
@user_params[:grading_period_id] = @last_grading_period.id
|
||||
raw_api_call(:get, @user_path, @user_params)
|
||||
final_score = JSON.parse(response.body).first["grades"]["final_score"]
|
||||
|
||||
# ((10 + 1) / 1) * 10 => 110
|
||||
# ((last + no_due_at) / number_of_grading_periods) * 10
|
||||
expect(final_score).to eq 110
|
||||
end
|
||||
|
||||
it "for all assignments when no grading period is specified." do
|
||||
@user_params[:grading_period_id] = nil
|
||||
raw_api_call(:get, @user_path, @user_params)
|
||||
final_score = JSON.parse(response.body).first["grades"]["final_score"]
|
||||
|
||||
# ((7 + 10 + 1) / 2) * 10 => 60
|
||||
# ((first + last + no_due_at) / number_of_grading_periods) * 10
|
||||
expect(final_score).to eq 90
|
||||
end
|
||||
# ((7 + 10 + 1) / 2) * 10 => 60
|
||||
# ((first + last + no_due_at) / number_of_grading_periods) * 10
|
||||
expect(final_score).to eq 90
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -878,15 +872,6 @@ describe EnrollmentsApiController, type: :request do
|
|||
expect(student_grade.(json)).to eq 0
|
||||
end
|
||||
end
|
||||
|
||||
context "multiple grading periods feature flag disabled" do
|
||||
it "should return an error message if the multiple grading periods flag is disabled" do
|
||||
@user_params[:grading_period_id] = @first_grading_period.id
|
||||
|
||||
json = api_call(:get, @user_path, @user_params, {}, {}, { expected_status: 403 })
|
||||
expect(json['message']).to eq 'Multiple Grading Periods feature is disabled. Cannot filter by grading_period_id with this feature disabled'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "an account admin" do
|
||||
|
|
|
@ -24,9 +24,7 @@ describe GradingPeriodsController, type: :request do
|
|||
context "A grading period is associated with a course." do
|
||||
before :once do
|
||||
course_with_teacher active_all: true
|
||||
Account.default.set_feature_flag! :multiple_grading_periods, 'on'
|
||||
grading_period_group =
|
||||
@course.grading_period_groups.create!(title: 'A Group')
|
||||
grading_period_group = @course.grading_period_groups.create!(title: 'A Group')
|
||||
@grading_period = grading_period_group.grading_periods.create! do |period|
|
||||
period.title = 'A Period'
|
||||
period.start_date = 1.month.from_now(now)
|
||||
|
@ -35,117 +33,95 @@ describe GradingPeriodsController, type: :request do
|
|||
end
|
||||
end
|
||||
|
||||
context "multiple grading periods feature flag turned on" do
|
||||
describe 'GET show' do
|
||||
def get_show(raw = false)
|
||||
helper = method(raw ? :raw_api_call : :api_call)
|
||||
helper.call(
|
||||
:get,
|
||||
"/api/v1/courses/#{@course.id}/grading_periods/#{@grading_period.id}",
|
||||
{
|
||||
controller: 'grading_periods',
|
||||
action: 'show',
|
||||
format: 'json',
|
||||
course_id: @course.id,
|
||||
id: @grading_period.id,
|
||||
},
|
||||
{}
|
||||
)
|
||||
end
|
||||
|
||||
it "retrieves the grading period specified" do
|
||||
json = get_show
|
||||
period = json['grading_periods'].first
|
||||
expect(period['id']).to eq(@grading_period.id.to_s)
|
||||
expect(period['weight']).to eq(@grading_period.weight)
|
||||
expect(period['title']).to eq(@grading_period.title)
|
||||
expect(period['permissions']).to include(
|
||||
"read" => true,
|
||||
"create" => false,
|
||||
"delete" => true,
|
||||
"update" => true
|
||||
)
|
||||
end
|
||||
|
||||
it "doesn't return deleted grading periods" do
|
||||
@grading_period.destroy
|
||||
get_show(true)
|
||||
expect(response.status).to eq 404
|
||||
end
|
||||
describe 'GET show' do
|
||||
def get_show(raw = false)
|
||||
helper = method(raw ? :raw_api_call : :api_call)
|
||||
helper.call(
|
||||
:get,
|
||||
"/api/v1/courses/#{@course.id}/grading_periods/#{@grading_period.id}",
|
||||
{
|
||||
controller: 'grading_periods',
|
||||
action: 'show',
|
||||
format: 'json',
|
||||
course_id: @course.id,
|
||||
id: @grading_period.id,
|
||||
},
|
||||
{}
|
||||
)
|
||||
end
|
||||
|
||||
describe 'PUT update' do
|
||||
def put_update(params, raw=false)
|
||||
helper = method(raw ? :raw_api_call : :api_call)
|
||||
|
||||
helper.call(
|
||||
:put,
|
||||
"/api/v1/courses/#{@course.id}/grading_periods/#{@grading_period.id}",
|
||||
{
|
||||
controller: 'grading_periods',
|
||||
action: 'update',
|
||||
format: 'json',
|
||||
course_id: @course.id,
|
||||
id: @grading_period.id
|
||||
},
|
||||
{ grading_periods: [params] }
|
||||
)
|
||||
end
|
||||
|
||||
it "updates a grading period successfully" do
|
||||
new_weight = @grading_period.weight + 11.11
|
||||
put_update(weight: new_weight)
|
||||
expect(@grading_period.reload.weight).to eql(new_weight)
|
||||
end
|
||||
|
||||
it "doesn't update deleted grading periods" do
|
||||
@grading_period.destroy
|
||||
put_update({weight: 80}, true)
|
||||
expect(response.status).to eq 404
|
||||
end
|
||||
it "retrieves the grading period specified" do
|
||||
json = get_show
|
||||
period = json['grading_periods'].first
|
||||
expect(period['id']).to eq(@grading_period.id.to_s)
|
||||
expect(period['weight']).to eq(@grading_period.weight)
|
||||
expect(period['title']).to eq(@grading_period.title)
|
||||
expect(period['permissions']).to include(
|
||||
"read" => true,
|
||||
"create" => false,
|
||||
"delete" => true,
|
||||
"update" => true
|
||||
)
|
||||
end
|
||||
|
||||
describe 'DELETE destroy' do
|
||||
def delete_destroy
|
||||
raw_api_call(
|
||||
:delete,
|
||||
"/api/v1/courses/#{@course.id}/grading_periods/#{@grading_period.id}",
|
||||
{
|
||||
controller: 'grading_periods',
|
||||
action: 'destroy',
|
||||
format: 'json',
|
||||
course_id: @course.id,
|
||||
id: @grading_period.id.to_s
|
||||
},
|
||||
)
|
||||
end
|
||||
|
||||
it "deletes a grading period successfully" do
|
||||
delete_destroy
|
||||
|
||||
expect(response.code).to eq '204'
|
||||
expect(@grading_period.reload).to be_deleted
|
||||
end
|
||||
it "doesn't return deleted grading periods" do
|
||||
@grading_period.destroy
|
||||
get_show(true)
|
||||
expect(response.status).to eq 404
|
||||
end
|
||||
end
|
||||
|
||||
context "multiple grading periods feature flag turned off" do
|
||||
before :once do
|
||||
@course.root_account.disable_feature! :multiple_grading_periods
|
||||
end
|
||||
describe 'PUT update' do
|
||||
def put_update(params, raw=false)
|
||||
helper = method(raw ? :raw_api_call : :api_call)
|
||||
|
||||
it "index should return 404" do
|
||||
json = api_call(
|
||||
:get,
|
||||
"/api/v1/courses/#{@course.id}/grading_periods",
|
||||
helper.call(
|
||||
:put,
|
||||
"/api/v1/courses/#{@course.id}/grading_periods/#{@grading_period.id}",
|
||||
{
|
||||
controller: 'grading_periods',
|
||||
action: 'index',
|
||||
action: 'update',
|
||||
format: 'json',
|
||||
course_id: @course.id
|
||||
}, {}, {}, expected_status: 404
|
||||
course_id: @course.id,
|
||||
id: @grading_period.id
|
||||
},
|
||||
{ grading_periods: [params] }
|
||||
)
|
||||
expect(json["message"]).to eq('Page not found')
|
||||
end
|
||||
|
||||
it "updates a grading period successfully" do
|
||||
new_weight = @grading_period.weight + 11.11
|
||||
put_update(weight: new_weight)
|
||||
expect(@grading_period.reload.weight).to eql(new_weight)
|
||||
end
|
||||
|
||||
it "doesn't update deleted grading periods" do
|
||||
@grading_period.destroy
|
||||
put_update({weight: 80}, true)
|
||||
expect(response.status).to eq 404
|
||||
end
|
||||
end
|
||||
|
||||
describe 'DELETE destroy' do
|
||||
def delete_destroy
|
||||
raw_api_call(
|
||||
:delete,
|
||||
"/api/v1/courses/#{@course.id}/grading_periods/#{@grading_period.id}",
|
||||
{
|
||||
controller: 'grading_periods',
|
||||
action: 'destroy',
|
||||
format: 'json',
|
||||
course_id: @course.id,
|
||||
id: @grading_period.id.to_s
|
||||
},
|
||||
)
|
||||
end
|
||||
|
||||
it "deletes a grading period successfully" do
|
||||
delete_destroy
|
||||
|
||||
expect(response.code).to eq '204'
|
||||
expect(@grading_period.reload).to be_deleted
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -422,7 +422,7 @@ describe Quizzes::QuizzesApiController, type: :request do
|
|||
end
|
||||
end
|
||||
|
||||
context "with multiple grading periods enabled" do
|
||||
context "with grading periods" do
|
||||
def call_create(params, expected_status)
|
||||
api_call_as_user(
|
||||
@current_user,
|
||||
|
@ -441,7 +441,6 @@ describe Quizzes::QuizzesApiController, type: :request do
|
|||
|
||||
before :once do
|
||||
teacher_in_course(active_all: true)
|
||||
@course.root_account.enable_feature!(:multiple_grading_periods)
|
||||
grading_period_group = Factories::GradingPeriodGroupHelper.new.create_for_account(@course.root_account)
|
||||
term = @course.enrollment_term
|
||||
term.grading_period_group = grading_period_group
|
||||
|
@ -738,7 +737,7 @@ describe Quizzes::QuizzesApiController, type: :request do
|
|||
end
|
||||
end
|
||||
|
||||
context "with multiple grading periods enabled" do
|
||||
context "with grading periods" do
|
||||
def create_quiz(attr)
|
||||
@course.quizzes.create!({ title: "Example Quiz", quiz_type: "assignment" }.merge(attr))
|
||||
end
|
||||
|
@ -772,7 +771,6 @@ describe Quizzes::QuizzesApiController, type: :request do
|
|||
|
||||
before :once do
|
||||
teacher_in_course(active_all: true)
|
||||
@course.root_account.enable_feature!(:multiple_grading_periods)
|
||||
grading_period_group = Factories::GradingPeriodGroupHelper.new.create_for_account(@course.root_account)
|
||||
term = @course.enrollment_term
|
||||
term.grading_period_group = grading_period_group
|
||||
|
|
|
@ -1795,7 +1795,7 @@ describe 'Submissions API', type: :request do
|
|||
expect(json.detect { |u| u['user_id'] == student2.id }['submissions'].size).to eq 0
|
||||
end
|
||||
|
||||
context "Multiple Grading Periods" do
|
||||
context "with grading periods" do
|
||||
before :once do
|
||||
@student1 = user_factory(active_all: true)
|
||||
@student2 = user_with_pseudonym(:active_all => true)
|
||||
|
@ -1805,7 +1805,6 @@ describe 'Submissions API', type: :request do
|
|||
@course.enroll_student(@student1).accept!
|
||||
@course.enroll_student(@student2).accept!
|
||||
|
||||
@course.account.enable_feature!(:multiple_grading_periods)
|
||||
gpg = Factories::GradingPeriodGroupHelper.new.legacy_create_for_course(@course)
|
||||
@gp1 = gpg.grading_periods.create!(
|
||||
title: 'first',
|
||||
|
|
|
@ -5,68 +5,84 @@ define [
|
|||
], (axios, fakeENV, api) ->
|
||||
deserializedSets = [
|
||||
{
|
||||
id: "1",
|
||||
title: "Fall 2015",
|
||||
id: '1',
|
||||
title: 'Fall 2015',
|
||||
weighted: false,
|
||||
gradingPeriods: [
|
||||
{
|
||||
id: "1",
|
||||
title: "Q1",
|
||||
startDate: new Date("2015-09-01T12:00:00Z"),
|
||||
endDate: new Date("2015-10-31T12:00:00Z"),
|
||||
closeDate: new Date("2015-11-07T12:00:00Z")
|
||||
id: '1',
|
||||
title: 'Q1',
|
||||
startDate: new Date('2015-09-01T12:00:00Z'),
|
||||
endDate: new Date('2015-10-31T12:00:00Z'),
|
||||
closeDate: new Date('2015-11-07T12:00:00Z'),
|
||||
isClosed: true,
|
||||
isLast: false,
|
||||
weight: 43.5
|
||||
},{
|
||||
id: "2",
|
||||
title: "Q2",
|
||||
startDate: new Date("2015-11-01T12:00:00Z"),
|
||||
endDate: new Date("2015-12-31T12:00:00Z"),
|
||||
closeDate: new Date("2016-01-07T12:00:00Z")
|
||||
id: '2',
|
||||
title: 'Q2',
|
||||
startDate: new Date('2015-11-01T12:00:00Z'),
|
||||
endDate: new Date('2015-12-31T12:00:00Z'),
|
||||
closeDate: new Date('2016-01-07T12:00:00Z'),
|
||||
isClosed: false,
|
||||
isLast: true,
|
||||
weight: null
|
||||
}
|
||||
],
|
||||
permissions: { read: true, create: true, update: true, delete: true },
|
||||
createdAt: new Date("2015-12-29T12:00:00Z")
|
||||
createdAt: new Date('2015-12-29T12:00:00Z')
|
||||
},{
|
||||
id: "2",
|
||||
title: "Spring 2016",
|
||||
id: '2',
|
||||
title: 'Spring 2016',
|
||||
weighted: true,
|
||||
gradingPeriods: [],
|
||||
permissions: { read: true, create: true, update: true, delete: true },
|
||||
createdAt: new Date("2015-11-29T12:00:00Z")
|
||||
createdAt: new Date('2015-11-29T12:00:00Z')
|
||||
}
|
||||
]
|
||||
|
||||
serializedSets = {
|
||||
grading_period_sets: [
|
||||
{
|
||||
id: "1",
|
||||
title: "Fall 2015",
|
||||
id: '1',
|
||||
title: 'Fall 2015',
|
||||
weighted: false,
|
||||
grading_periods: [
|
||||
{
|
||||
id: "1",
|
||||
title: "Q1",
|
||||
start_date: new Date("2015-09-01T12:00:00Z"),
|
||||
end_date: new Date("2015-10-31T12:00:00Z"),
|
||||
close_date: new Date("2015-11-07T12:00:00Z")
|
||||
id: '1',
|
||||
title: 'Q1',
|
||||
start_date: new Date('2015-09-01T12:00:00Z'),
|
||||
end_date: new Date('2015-10-31T12:00:00Z'),
|
||||
close_date: new Date('2015-11-07T12:00:00Z'),
|
||||
is_closed: true,
|
||||
is_last: false,
|
||||
weight: 43.5
|
||||
},{
|
||||
id: "2",
|
||||
title: "Q2",
|
||||
start_date: new Date("2015-11-01T12:00:00Z"),
|
||||
end_date: new Date("2015-12-31T12:00:00Z"),
|
||||
close_date: new Date("2016-01-07T12:00:00Z")
|
||||
id: '2',
|
||||
title: 'Q2',
|
||||
start_date: new Date('2015-11-01T12:00:00Z'),
|
||||
end_date: new Date('2015-12-31T12:00:00Z'),
|
||||
close_date: new Date('2016-01-07T12:00:00Z'),
|
||||
is_closed: false,
|
||||
is_last: true,
|
||||
weight: null
|
||||
}
|
||||
],
|
||||
permissions: { read: true, create: true, update: true, delete: true },
|
||||
created_at: "2015-12-29T12:00:00Z"
|
||||
created_at: '2015-12-29T12:00:00Z'
|
||||
},
|
||||
{
|
||||
id: "2",
|
||||
title: "Spring 2016",
|
||||
id: '2',
|
||||
title: 'Spring 2016',
|
||||
weighted: true,
|
||||
grading_periods: [],
|
||||
permissions: { read: true, create: true, update: true, delete: true },
|
||||
created_at: "2015-11-29T12:00:00Z"
|
||||
created_at: '2015-11-29T12:00:00Z'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
QUnit.module "list",
|
||||
QUnit.module 'gradingPeriodSetsApi.list',
|
||||
setup: ->
|
||||
@server = sinon.fakeServer.create()
|
||||
@fakeHeaders =
|
||||
|
@ -77,183 +93,159 @@ define [
|
|||
fakeENV.teardown()
|
||||
@server.restore()
|
||||
|
||||
test "calls the resolved endpoint", ->
|
||||
test 'calls the resolved endpoint', ->
|
||||
@stub($, 'ajaxJSON').returns(new Promise(->))
|
||||
api.list()
|
||||
ok $.ajaxJSON.calledWith('api/grading_period_sets')
|
||||
|
||||
asyncTest "deserializes returned grading period sets", ->
|
||||
@server.respondWith "GET", /grading_period_sets/, [200, {"Content-Type":"application/json", "Link": @fakeHeaders}, JSON.stringify serializedSets]
|
||||
api.list()
|
||||
.then (sets) =>
|
||||
deepEqual sets, deserializedSets
|
||||
start()
|
||||
@server.respond()
|
||||
test 'deserializes returned grading period sets', ->
|
||||
@server.respondWith(
|
||||
'GET',
|
||||
/grading_period_sets/,
|
||||
[200, {'Content-Type':'application/json', 'Link': @fakeHeaders}, JSON.stringify serializedSets]
|
||||
)
|
||||
@server.autoRespond = true
|
||||
promise = api.list()
|
||||
.then (sets) =>
|
||||
deepEqual sets, deserializedSets
|
||||
|
||||
asyncTest "creates a title from the creation date when the set has no title", ->
|
||||
test 'creates a title from the creation date when the set has no title', ->
|
||||
untitledSets =
|
||||
grading_period_sets: [
|
||||
id: "1"
|
||||
id: '1'
|
||||
title: null
|
||||
grading_periods: []
|
||||
permissions: { read: true, create: true, update: true, delete: true }
|
||||
created_at: "2015-11-29T12:00:00Z"
|
||||
created_at: '2015-11-29T12:00:00Z'
|
||||
]
|
||||
jsonString = JSON.stringify(untitledSets)
|
||||
@server.respondWith(
|
||||
"GET",
|
||||
'GET',
|
||||
/grading_period_sets/,
|
||||
[200, { "Content-Type":"application/json", "Link": @fakeHeaders }, jsonString]
|
||||
[200, { 'Content-Type':'application/json', 'Link': @fakeHeaders }, jsonString]
|
||||
)
|
||||
@server.autoRespond = true
|
||||
api.list()
|
||||
.then (sets) =>
|
||||
equal sets[0].title, "Set created Nov 29, 2015"
|
||||
start()
|
||||
@server.respond()
|
||||
|
||||
asyncTest "uses the endDate as the closeDate when a period has no closeDate", ->
|
||||
setsWithoutPeriodCloseDate =
|
||||
grading_period_sets: [
|
||||
id: "1"
|
||||
title: "Fall 2015"
|
||||
grading_periods: [{
|
||||
id: "1",
|
||||
title: "Q1",
|
||||
start_date: new Date("2015-09-01T12:00:00Z"),
|
||||
end_date: new Date("2015-10-31T12:00:00Z"),
|
||||
close_date: null
|
||||
}]
|
||||
permissions: { read: true, create: true, update: true, delete: true }
|
||||
created_at: "2015-11-29T12:00:00Z"
|
||||
]
|
||||
jsonString = JSON.stringify(setsWithoutPeriodCloseDate)
|
||||
@server.respondWith(
|
||||
"GET",
|
||||
/grading_period_sets/,
|
||||
[200, { "Content-Type":"application/json", "Link": @fakeHeaders }, jsonString]
|
||||
)
|
||||
api.list()
|
||||
.then (sets) =>
|
||||
deepEqual sets[0].gradingPeriods[0].closeDate, new Date("2015-10-31T12:00:00Z")
|
||||
start()
|
||||
@server.respond()
|
||||
.then (sets) =>
|
||||
equal sets[0].title, 'Set created Nov 29, 2015'
|
||||
|
||||
deserializedSetCreating = {
|
||||
title: "Fall 2015",
|
||||
enrollmentTermIDs: [ "1", "2" ]
|
||||
title: 'Fall 2015',
|
||||
weighted: null,
|
||||
enrollmentTermIDs: ['1', '2']
|
||||
}
|
||||
|
||||
deserializedSetCreated = {
|
||||
id: "1",
|
||||
title: "Fall 2015",
|
||||
id: '1',
|
||||
title: 'Fall 2015',
|
||||
weighted: false,
|
||||
gradingPeriods: [],
|
||||
enrollmentTermIDs: [ "1", "2" ],
|
||||
enrollmentTermIDs: ['1', '2'],
|
||||
permissions: { read: true, create: true, update: true, delete: true },
|
||||
createdAt: new Date("2015-12-31T12:00:00Z")
|
||||
createdAt: new Date('2015-12-31T12:00:00Z')
|
||||
}
|
||||
|
||||
serializedSetCreating = {
|
||||
grading_period_set: { title: "Fall 2015" },
|
||||
enrollment_term_ids: [ "1", "2" ]
|
||||
grading_period_set: { title: 'Fall 2015', weighted: null },
|
||||
enrollment_term_ids: ['1', '2']
|
||||
}
|
||||
|
||||
serializedSetCreated = {
|
||||
grading_period_set: {
|
||||
id: "1",
|
||||
title: "Fall 2015",
|
||||
enrollment_term_ids: [ "1", "2" ],
|
||||
id: '1',
|
||||
title: 'Fall 2015',
|
||||
weighted: false,
|
||||
enrollment_term_ids: ['1', '2'],
|
||||
grading_periods: [],
|
||||
permissions: { read: true, create: true, update: true, delete: true },
|
||||
created_at: "2015-12-31T12:00:00Z"
|
||||
created_at: '2015-12-31T12:00:00Z'
|
||||
}
|
||||
}
|
||||
|
||||
QUnit.module "create",
|
||||
QUnit.module 'gradingPeriodSetsApi.create',
|
||||
setup: ->
|
||||
fakeENV.setup()
|
||||
ENV.GRADING_PERIOD_SETS_URL = 'api/grading_period_sets'
|
||||
teardown: ->
|
||||
fakeENV.teardown()
|
||||
|
||||
test "calls the resolved endpoint with the serialized grading period set", ->
|
||||
apiSpy = @stub(axios, "post").returns(new Promise(->))
|
||||
test 'calls the resolved endpoint with the serialized grading period set', ->
|
||||
apiSpy = @stub(axios, 'post').returns(new Promise(->))
|
||||
api.create(deserializedSetCreating)
|
||||
ok axios.post.calledWith('api/grading_period_sets', serializedSetCreating)
|
||||
|
||||
asyncTest "deserializes returned grading period sets", ->
|
||||
test 'deserializes returned grading period sets', ->
|
||||
successPromise = new Promise (resolve) => resolve({ data: serializedSetCreated })
|
||||
@stub(axios, "post").returns(successPromise)
|
||||
@stub(axios, 'post').returns(successPromise)
|
||||
api.create(deserializedSetCreating)
|
||||
.then (set) =>
|
||||
deepEqual set, deserializedSetCreated
|
||||
start()
|
||||
.then (set) =>
|
||||
deepEqual set, deserializedSetCreated
|
||||
|
||||
asyncTest "rejects the promise upon errors", ->
|
||||
failurePromise = new Promise (_, reject) => reject("FAIL")
|
||||
@stub(axios, "post").returns(failurePromise)
|
||||
test 'rejects the promise upon errors', ->
|
||||
@stub(axios, 'post').returns(Promise.reject('FAIL'))
|
||||
api.create(deserializedSetCreating).catch (error) =>
|
||||
equal error, "FAIL"
|
||||
start()
|
||||
equal error, 'FAIL'
|
||||
|
||||
deserializedSetUpdating = {
|
||||
id: "1",
|
||||
title: "Fall 2015",
|
||||
enrollmentTermIDs: [ "1", "2" ],
|
||||
id: '1',
|
||||
title: 'Fall 2015',
|
||||
weighted: true,
|
||||
enrollmentTermIDs: ['1', '2'],
|
||||
permissions: { read: true, create: true, update: true, delete: true }
|
||||
}
|
||||
|
||||
serializedSetUpdating = {
|
||||
grading_period_set: { title: "Fall 2015" },
|
||||
enrollment_term_ids: [ "1", "2" ]
|
||||
grading_period_set: { title: 'Fall 2015', weighted: true },
|
||||
enrollment_term_ids: ['1', '2']
|
||||
}
|
||||
|
||||
serializedSetUpdated = {
|
||||
grading_period_set: {
|
||||
id: "1",
|
||||
title: "Fall 2015",
|
||||
enrollment_term_ids: [ "1", "2" ],
|
||||
id: '1',
|
||||
title: 'Fall 2015',
|
||||
weighted: true,
|
||||
enrollment_term_ids: ['1', '2'],
|
||||
grading_periods: [
|
||||
{
|
||||
id: "1",
|
||||
title: "Q1",
|
||||
start_date: new Date("2015-09-01T12:00:00Z"),
|
||||
end_date: new Date("2015-10-31T12:00:00Z"),
|
||||
close_date: new Date("2015-11-07T12:00:00Z")
|
||||
id: '1',
|
||||
title: 'Q1',
|
||||
start_date: new Date('2015-09-01T12:00:00Z'),
|
||||
end_date: new Date('2015-10-31T12:00:00Z'),
|
||||
close_date: new Date('2015-11-07T12:00:00Z'),
|
||||
weight: 40
|
||||
},{
|
||||
id: "2",
|
||||
title: "Q2",
|
||||
start_date: new Date("2015-11-01T12:00:00Z"),
|
||||
end_date: new Date("2015-12-31T12:00:00Z"),
|
||||
close_date: null
|
||||
id: '2',
|
||||
title: 'Q2',
|
||||
start_date: new Date('2015-11-01T12:00:00Z'),
|
||||
end_date: new Date('2015-12-31T12:00:00Z'),
|
||||
close_date: null,
|
||||
weight: 60
|
||||
}
|
||||
],
|
||||
permissions: { read: true, create: true, update: true, delete: true }
|
||||
}
|
||||
}
|
||||
|
||||
QUnit.module "update",
|
||||
QUnit.module 'gradingPeriodSetsApi.update',
|
||||
setup: ->
|
||||
fakeENV.setup()
|
||||
ENV.GRADING_PERIOD_SET_UPDATE_URL = 'api/grading_period_sets/%7B%7B%20id%20%7D%7D'
|
||||
teardown: ->
|
||||
fakeENV.teardown()
|
||||
|
||||
test "calls the resolved endpoint with the serialized grading period set", ->
|
||||
apiSpy = @stub(axios, "patch").returns(new Promise(->))
|
||||
test 'calls the resolved endpoint with the serialized grading period set', ->
|
||||
apiSpy = @stub(axios, 'patch').returns(new Promise(->))
|
||||
api.update(deserializedSetUpdating)
|
||||
ok axios.patch.calledWith('api/grading_period_sets/1', serializedSetUpdating)
|
||||
|
||||
asyncTest "returns the given grading period set", ->
|
||||
successPromise = new Promise (resolve) => resolve({ data: serializedSetUpdated })
|
||||
@stub(axios, "patch").returns(successPromise)
|
||||
test 'returns the given grading period set', ->
|
||||
@stub(axios, 'patch').returns(Promise.resolve({ data: serializedSetUpdated }))
|
||||
api.update(deserializedSetUpdating)
|
||||
.then (set) =>
|
||||
deepEqual set, deserializedSetUpdating
|
||||
start()
|
||||
|
||||
asyncTest "rejects the promise upon errors", ->
|
||||
failurePromise = new Promise (_, reject) => reject("FAIL")
|
||||
@stub(axios, "patch").returns(failurePromise)
|
||||
api.update(deserializedSetUpdating).catch (error) =>
|
||||
equal error, "FAIL"
|
||||
start()
|
||||
test 'rejects the promise upon errors', ->
|
||||
@stub(axios, 'patch').returns(Promise.reject('FAIL'))
|
||||
api.update(deserializedSetUpdating)
|
||||
.catch (error) =>
|
||||
equal error, 'FAIL'
|
||||
|
|
|
@ -6,38 +6,42 @@ define [
|
|||
], (_, axios, fakeENV, api) ->
|
||||
deserializedPeriods = [
|
||||
{
|
||||
id: "1",
|
||||
title: "Q1",
|
||||
startDate: new Date("2015-09-01T12:00:00Z"),
|
||||
endDate: new Date("2015-10-31T12:00:00Z"),
|
||||
closeDate: new Date("2015-11-07T12:00:00Z"),
|
||||
id: '1',
|
||||
title: 'Q1',
|
||||
startDate: new Date('2015-09-01T12:00:00Z'),
|
||||
endDate: new Date('2015-10-31T12:00:00Z'),
|
||||
closeDate: new Date('2015-11-07T12:00:00Z'),
|
||||
isClosed: true,
|
||||
isLast: false
|
||||
isLast: false,
|
||||
weight: 40
|
||||
},{
|
||||
id: "2",
|
||||
title: "Q2",
|
||||
startDate: new Date("2015-11-01T12:00:00Z"),
|
||||
endDate: new Date("2015-12-31T12:00:00Z"),
|
||||
closeDate: new Date("2016-01-07T12:00:00Z"),
|
||||
id: '2',
|
||||
title: 'Q2',
|
||||
startDate: new Date('2015-11-01T12:00:00Z'),
|
||||
endDate: new Date('2015-12-31T12:00:00Z'),
|
||||
closeDate: new Date('2016-01-07T12:00:00Z'),
|
||||
isClosed: true,
|
||||
isLast: true
|
||||
isLast: true,
|
||||
weight: 60
|
||||
}
|
||||
]
|
||||
|
||||
serializedPeriods = {
|
||||
grading_periods: [
|
||||
{
|
||||
id: "1",
|
||||
title: "Q1",
|
||||
start_date: new Date("2015-09-01T12:00:00Z"),
|
||||
end_date: new Date("2015-10-31T12:00:00Z"),
|
||||
close_date: new Date("2015-11-07T12:00:00Z")
|
||||
id: '1',
|
||||
title: 'Q1',
|
||||
start_date: new Date('2015-09-01T12:00:00Z'),
|
||||
end_date: new Date('2015-10-31T12:00:00Z'),
|
||||
close_date: new Date('2015-11-07T12:00:00Z'),
|
||||
weight: 40
|
||||
},{
|
||||
id: "2",
|
||||
title: "Q2",
|
||||
start_date: new Date("2015-11-01T12:00:00Z"),
|
||||
end_date: new Date("2015-12-31T12:00:00Z"),
|
||||
close_date: new Date("2016-01-07T12:00:00Z")
|
||||
id: '2',
|
||||
title: 'Q2',
|
||||
start_date: new Date('2015-11-01T12:00:00Z'),
|
||||
end_date: new Date('2015-12-31T12:00:00Z'),
|
||||
close_date: new Date('2016-01-07T12:00:00Z'),
|
||||
weight: 60
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -45,79 +49,59 @@ define [
|
|||
periodsData = {
|
||||
grading_periods: [
|
||||
{
|
||||
id: "1",
|
||||
title: "Q1",
|
||||
start_date: "2015-09-01T12:00:00Z",
|
||||
end_date: "2015-10-31T12:00:00Z",
|
||||
close_date: "2015-11-07T12:00:00Z",
|
||||
id: '1',
|
||||
title: 'Q1',
|
||||
start_date: '2015-09-01T12:00:00Z',
|
||||
end_date: '2015-10-31T12:00:00Z',
|
||||
close_date: '2015-11-07T12:00:00Z',
|
||||
is_closed: true,
|
||||
is_last: false
|
||||
is_last: false,
|
||||
weight: 40
|
||||
},{
|
||||
id: "2",
|
||||
title: "Q2",
|
||||
start_date: "2015-11-01T12:00:00Z",
|
||||
end_date: "2015-12-31T12:00:00Z",
|
||||
close_date: "2016-01-07T12:00:00Z",
|
||||
id: '2',
|
||||
title: 'Q2',
|
||||
start_date: '2015-11-01T12:00:00Z',
|
||||
end_date: '2015-12-31T12:00:00Z',
|
||||
close_date: '2016-01-07T12:00:00Z',
|
||||
is_closed: true,
|
||||
is_last: true
|
||||
is_last: true,
|
||||
weight: 60
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
QUnit.module "batchUpdate",
|
||||
QUnit.module 'batchUpdate',
|
||||
setup: ->
|
||||
fakeENV.setup()
|
||||
ENV.GRADING_PERIODS_UPDATE_URL = 'api/{{ set_id }}/batch_update'
|
||||
teardown: ->
|
||||
fakeENV.teardown()
|
||||
|
||||
test "calls the resolved endpoint with serialized grading periods", ->
|
||||
apiSpy = @stub(axios, "patch").returns(new Promise(->))
|
||||
test 'calls the resolved endpoint with serialized grading periods', ->
|
||||
apiSpy = @stub(axios, 'patch').returns(new Promise(->))
|
||||
api.batchUpdate(123, deserializedPeriods)
|
||||
ok axios.patch.calledWith('api/123/batch_update', serializedPeriods)
|
||||
|
||||
asyncTest "deserializes returned grading periods", ->
|
||||
successPromise = new Promise (resolve) => resolve({ data: periodsData })
|
||||
@stub(axios, "patch").returns(successPromise)
|
||||
test 'deserializes returned grading periods', ->
|
||||
@stub(axios, 'patch').returns(Promise.resolve({ data: periodsData }))
|
||||
api.batchUpdate(123, deserializedPeriods)
|
||||
.then (periods) =>
|
||||
deepEqual periods, deserializedPeriods
|
||||
start()
|
||||
.then (periods) =>
|
||||
deepEqual periods, deserializedPeriods
|
||||
|
||||
asyncTest "uses the endDate as the closeDate when a period has no closeDate", ->
|
||||
periodsWithoutCloseDate = {
|
||||
grading_periods: [
|
||||
{
|
||||
id: "1",
|
||||
title: "Q1",
|
||||
start_date: new Date("2015-09-01T12:00:00Z"),
|
||||
end_date: new Date("2015-10-31T12:00:00Z"),
|
||||
close_date: null
|
||||
}
|
||||
]
|
||||
}
|
||||
successPromise = new Promise (resolve) => resolve({ data: periodsWithoutCloseDate })
|
||||
@stub(axios, "patch").returns(successPromise)
|
||||
test 'rejects the promise upon errors', ->
|
||||
@stub(axios, 'patch').returns(Promise.reject('FAIL'))
|
||||
api.batchUpdate(123, deserializedPeriods)
|
||||
.then (periods) =>
|
||||
deepEqual periods[0].closeDate, new Date("2015-10-31T12:00:00Z")
|
||||
start()
|
||||
.catch (error) =>
|
||||
equal error, 'FAIL'
|
||||
|
||||
asyncTest "rejects the promise upon errors", ->
|
||||
failurePromise = new Promise (_, reject) => reject("FAIL")
|
||||
@stub(axios, "patch").returns(failurePromise)
|
||||
api.batchUpdate(123, deserializedPeriods).catch (error) =>
|
||||
equal error, "FAIL"
|
||||
start()
|
||||
QUnit.module 'deserializePeriods'
|
||||
|
||||
QUnit.module "deserializePeriods"
|
||||
|
||||
test "returns an empty array if passed undefined", ->
|
||||
test 'returns an empty array if passed undefined', ->
|
||||
propEqual api.deserializePeriods(undefined), []
|
||||
|
||||
test "returns an empty array if passed null", ->
|
||||
test 'returns an empty array if passed null', ->
|
||||
propEqual api.deserializePeriods(null), []
|
||||
|
||||
test "deserializes periods", ->
|
||||
test 'deserializes periods', ->
|
||||
result = api.deserializePeriods(periodsData.grading_periods)
|
||||
propEqual result, deserializedPeriods
|
||||
|
|
|
@ -164,7 +164,7 @@ define [
|
|||
setup: ->
|
||||
fakeENV.setup({
|
||||
GRADEBOOK_OPTIONS: {
|
||||
multiple_grading_periods_enabled: true
|
||||
has_grading_periods: true
|
||||
}
|
||||
})
|
||||
@disableUnavailableMenuActions = GradebookHeaderMenu.prototype.disableUnavailableMenuActions
|
||||
|
@ -224,7 +224,7 @@ define [
|
|||
setup: ->
|
||||
fakeENV.setup({
|
||||
GRADEBOOK_OPTIONS: {
|
||||
multiple_grading_periods_enabled: true
|
||||
has_grading_periods: true
|
||||
},
|
||||
current_user_roles: ['admin']
|
||||
})
|
||||
|
@ -274,7 +274,7 @@ define [
|
|||
setup: ->
|
||||
fakeENV.setup({
|
||||
GRADEBOOK_OPTIONS: {
|
||||
multiple_grading_periods_enabled: true
|
||||
has_grading_periods: true
|
||||
},
|
||||
current_user_roles: ['admin']
|
||||
})
|
||||
|
|
|
@ -1,11 +1,165 @@
|
|||
#
|
||||
# Copyright (C) 2014 - 2017 Instructure, Inc.
|
||||
#
|
||||
# This file is part of Canvas.
|
||||
#
|
||||
# Canvas is free software: you can redistribute it and/or modify it under
|
||||
# the terms of the GNU Affero General Public License as published by the Free
|
||||
# Software Foundation, version 3 of the License.
|
||||
#
|
||||
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
# details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License along
|
||||
# with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
define [
|
||||
'spec/jsx/gradebook/GradeCalculatorSpecHelper'
|
||||
'compiled/gradebook/Gradebook'
|
||||
'jsx/gradebook/DataLoader'
|
||||
'underscore'
|
||||
'timezone'
|
||||
'compiled/SubmissionDetailsDialog'
|
||||
'compiled/util/natcompare'
|
||||
], (Gradebook, DataLoader, _, tz, SubmissionDetailsDialog, natcompare) ->
|
||||
'compiled/SubmissionDetailsDialog'
|
||||
'jsx/gradebook/CourseGradeCalculator'
|
||||
], (
|
||||
GradeCalculatorSpecHelper, Gradebook, DataLoader, _, tz, natcompare, SubmissionDetailsDialog, CourseGradeCalculator
|
||||
) ->
|
||||
exampleGradebookOptions =
|
||||
settings:
|
||||
show_concluded_enrollments: 'true'
|
||||
show_inactive_enrollments: 'true'
|
||||
sections: []
|
||||
|
||||
createExampleGrades = GradeCalculatorSpecHelper.createCourseGradesWithGradingPeriods
|
||||
|
||||
QUnit.module 'Gradebook'
|
||||
|
||||
test 'normalizes the grading period set from the env', ->
|
||||
options = _.extend {}, exampleGradebookOptions,
|
||||
grading_period_set:
|
||||
id: '1501'
|
||||
grading_periods: [{ id: '701', weight: 50 }, { id: '702', weight: 50 }]
|
||||
weighted: true
|
||||
gradingPeriodSet = new Gradebook(options).gradingPeriodSet
|
||||
deepEqual(gradingPeriodSet.id, '1501')
|
||||
equal(gradingPeriodSet.gradingPeriods.length, 2)
|
||||
deepEqual(_.map(gradingPeriodSet.gradingPeriods, 'id'), ['701', '702'])
|
||||
|
||||
test 'sets grading period set to null when not defined in the env', ->
|
||||
gradingPeriodSet = new Gradebook(exampleGradebookOptions).gradingPeriodSet
|
||||
deepEqual(gradingPeriodSet, null)
|
||||
|
||||
QUnit.module 'Gradebook#calculateStudentGrade',
|
||||
setupThis:(options = {}) ->
|
||||
assignments = [{ id: 201, points_possible: 10, omit_from_final_grade: false }]
|
||||
submissions = [{ assignment_id: 201, score: 10 }]
|
||||
defaults = {
|
||||
gradingPeriodToShow: '0'
|
||||
isAllGradingPeriods: Gradebook.prototype.isAllGradingPeriods
|
||||
assignmentGroups: [{ id: 301, group_weight: 60, rules: {}, assignments }]
|
||||
options: { group_weighting_scheme: 'points' }
|
||||
gradingPeriods: [{ id: 701, weight: 50 }, { id: 702, weight: 50 }]
|
||||
gradingPeriodSet:
|
||||
id: '1501'
|
||||
gradingPeriods: [{ id: '701', weight: 50 }, { id: '702', weight: 50 }]
|
||||
weighted: true
|
||||
effectiveDueDates: { 201: { 101: { grading_period_id: '701' } } }
|
||||
submissionsForStudent: () ->
|
||||
submissions
|
||||
addDroppedClass: () ->
|
||||
}
|
||||
_.defaults options, defaults
|
||||
|
||||
setup: ->
|
||||
@calculate = Gradebook.prototype.calculateStudentGrade
|
||||
|
||||
test 'calculates grades using properties from the gradebook', ->
|
||||
self = @setupThis()
|
||||
@stub(CourseGradeCalculator, 'calculate').returns(createExampleGrades())
|
||||
@calculate.call(self, id: '101', loaded: true, initialized: true)
|
||||
args = CourseGradeCalculator.calculate.getCall(0).args
|
||||
equal(args[0], self.submissionsForStudent())
|
||||
equal(args[1], self.assignmentGroups)
|
||||
equal(args[2], self.options.group_weighting_scheme)
|
||||
equal(args[3], self.gradingPeriodSet)
|
||||
|
||||
test 'scopes effective due dates to the user', ->
|
||||
self = @setupThis()
|
||||
@stub(CourseGradeCalculator, 'calculate').returns(createExampleGrades())
|
||||
@calculate.call(self, id: '101', loaded: true, initialized: true)
|
||||
dueDates = CourseGradeCalculator.calculate.getCall(0).args[4]
|
||||
deepEqual(dueDates, 201: { grading_period_id: '701' })
|
||||
|
||||
test 'calculates grades without grading period data when grading period set is null', ->
|
||||
self = @setupThis(gradingPeriodSet: null)
|
||||
@stub(CourseGradeCalculator, 'calculate').returns(createExampleGrades())
|
||||
@calculate.call(self, id: '101', loaded: true, initialized: true)
|
||||
args = CourseGradeCalculator.calculate.getCall(0).args
|
||||
equal(args[0], self.submissionsForStudent())
|
||||
equal(args[1], self.assignmentGroups)
|
||||
equal(args[2], self.options.group_weighting_scheme)
|
||||
equal(typeof args[3], 'undefined')
|
||||
equal(typeof args[4], 'undefined')
|
||||
|
||||
test 'calculates grades without grading period data when effective due dates are not defined', ->
|
||||
self = @setupThis(effectiveDueDates: null)
|
||||
@stub(CourseGradeCalculator, 'calculate').returns(createExampleGrades())
|
||||
@calculate.call(self, id: '101', loaded: true, initialized: true)
|
||||
args = CourseGradeCalculator.calculate.getCall(0).args
|
||||
equal(args[0], self.submissionsForStudent())
|
||||
equal(args[1], self.assignmentGroups)
|
||||
equal(args[2], self.options.group_weighting_scheme)
|
||||
equal(typeof args[3], 'undefined')
|
||||
equal(typeof args[4], 'undefined')
|
||||
|
||||
test 'stores the current grade on the student when not including ungraded assignments', ->
|
||||
exampleGrades = createExampleGrades()
|
||||
self = @setupThis(include_ungraded_assignments: false)
|
||||
@stub(CourseGradeCalculator, 'calculate').returns(exampleGrades)
|
||||
student = { id: '101', loaded: true, initialized: true }
|
||||
@calculate.call(self, student)
|
||||
equal(student.total_grade, exampleGrades.current)
|
||||
|
||||
test 'stores the final grade on the student when including ungraded assignments', ->
|
||||
exampleGrades = createExampleGrades()
|
||||
self = @setupThis(include_ungraded_assignments: true)
|
||||
@stub(CourseGradeCalculator, 'calculate').returns(exampleGrades)
|
||||
student = { id: '101', loaded: true, initialized: true }
|
||||
@calculate.call(self, student)
|
||||
equal(student.total_grade, exampleGrades.final)
|
||||
|
||||
test 'stores the current grade from the selected grading period when not including ungraded assignments', ->
|
||||
exampleGrades = createExampleGrades()
|
||||
self = @setupThis(gradingPeriodToShow: 701, include_ungraded_assignments: false)
|
||||
@stub(CourseGradeCalculator, 'calculate').returns(exampleGrades)
|
||||
student = { id: '101', loaded: true, initialized: true }
|
||||
@calculate.call(self, student)
|
||||
equal(student.total_grade, exampleGrades.gradingPeriods[701].current)
|
||||
|
||||
test 'stores the final grade from the selected grading period when including ungraded assignments', ->
|
||||
exampleGrades = createExampleGrades()
|
||||
self = @setupThis(gradingPeriodToShow: 701, include_ungraded_assignments: true)
|
||||
@stub(CourseGradeCalculator, 'calculate').returns(exampleGrades)
|
||||
student = { id: '101', loaded: true, initialized: true }
|
||||
@calculate.call(self, student)
|
||||
equal(student.total_grade, exampleGrades.gradingPeriods[701].final)
|
||||
|
||||
test 'does not calculate when the student is not loaded', ->
|
||||
self = @setupThis()
|
||||
@stub(CourseGradeCalculator, 'calculate').returns(createExampleGrades())
|
||||
@calculate.call(self, id: '101', loaded: false, initialized: true)
|
||||
notOk(CourseGradeCalculator.calculate.called)
|
||||
|
||||
test 'does not calculate when the student is not initialized', ->
|
||||
self = @setupThis()
|
||||
@stub(CourseGradeCalculator, 'calculate').returns(createExampleGrades())
|
||||
@calculate.call(self, id: '101', loaded: true, initialized: false)
|
||||
notOk(CourseGradeCalculator.calculate.called)
|
||||
|
||||
QUnit.module "Gradebook#localeSort"
|
||||
|
||||
test "delegates to natcompare.strings", ->
|
||||
|
@ -18,9 +172,9 @@ define [
|
|||
Gradebook.prototype.localeSort(0, false)
|
||||
ok natCompareSpy.calledWith('', '')
|
||||
|
||||
QUnit.module "Gradebook#gradeSort"
|
||||
QUnit.module 'Gradebook#gradeSort'
|
||||
|
||||
test "gradeSort - total_grade", ->
|
||||
test 'gradeSort - total_grade', ->
|
||||
gradeSort = (showTotalGradeAsPoints, a, b, field, asc) ->
|
||||
asc = true unless asc?
|
||||
|
||||
|
@ -32,27 +186,27 @@ define [
|
|||
, {total_grade: {score: 10, possible: 20}}
|
||||
, {total_grade: {score: 5, possible: 10}}
|
||||
, 'total_grade') == 0
|
||||
, "total_grade sorts by percent (normally)"
|
||||
, 'total_grade sorts by percent (normally)'
|
||||
|
||||
ok gradeSort(true
|
||||
, {total_grade: {score: 10, possible: 20}}
|
||||
, {total_grade: {score: 5, possible: 10}}
|
||||
, 'total_grade') > 0
|
||||
, "total_grade sorts by score when if show_total_grade_as_points"
|
||||
, 'total_grade sorts by score when if show_total_grade_as_points'
|
||||
|
||||
ok gradeSort(true
|
||||
, {assignment_group_1: {score: 10, possible: 20}}
|
||||
, {assignment_group_1: {score: 5, possible: 10}}
|
||||
, 'assignment_group_1') == 0
|
||||
, "assignment groups are always sorted by percent"
|
||||
, 'assignment groups are always sorted by percent'
|
||||
|
||||
ok gradeSort(false
|
||||
, {assignment1: {score: 5, possible: 10}}
|
||||
, {assignment1: {score: 10, possible: 20}}
|
||||
, 'assignment1') < 0
|
||||
, "other fields are sorted by score"
|
||||
, 'other fields are sorted by score'
|
||||
|
||||
QUnit.module "Gradebook#hideAggregateColumns",
|
||||
QUnit.module 'Gradebook#hideAggregateColumns',
|
||||
gradebookStubs: ->
|
||||
indexedOverrides: Gradebook.prototype.indexedOverrides
|
||||
indexedGradingPeriods: _.indexBy(@gradingPeriods, 'id')
|
||||
|
@ -60,7 +214,7 @@ define [
|
|||
setupThis: (options) ->
|
||||
customOptions = options || {}
|
||||
defaults =
|
||||
gradingPeriodsEnabled: true
|
||||
hasGradingPeriods: true
|
||||
getGradingPeriodToShow: -> '1'
|
||||
options:
|
||||
all_grading_periods_totals: false
|
||||
|
@ -71,13 +225,13 @@ define [
|
|||
@hideAggregateColumns = Gradebook.prototype.hideAggregateColumns
|
||||
teardown: ->
|
||||
|
||||
test 'returns false if multiple grading periods is disabled', ->
|
||||
self = @setupThis(gradingPeriodsEnabled: false, isAllGradingPeriods: -> false)
|
||||
test 'returns false if there are no grading periods', ->
|
||||
self = @setupThis(hasGradingPeriods: false, isAllGradingPeriods: -> false)
|
||||
notOk @hideAggregateColumns.call(self)
|
||||
|
||||
test 'returns false if multiple grading periods is disabled, even if isAllGradingPeriods is true', ->
|
||||
test 'returns false if there are no grading periods, even if isAllGradingPeriods is true', ->
|
||||
self = @setupThis
|
||||
gradingPeriodsEnabled: false
|
||||
hasGradingPeriods: false
|
||||
getGradingPeriodToShow: -> '0'
|
||||
isAllGradingPeriods: -> true
|
||||
|
||||
|
@ -114,9 +268,9 @@ define [
|
|||
@getStoredSortOrder = Gradebook.prototype.getStoredSortOrder
|
||||
@defaultSortType = 'assignment_group'
|
||||
@allAssignmentColumns = [
|
||||
{ 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: 1, name: 'first' } },
|
||||
{ object: { assignment_group: { position: 1 }, position: 2, name: 'second' } },
|
||||
{ object: { assignment_group: { position: 1 }, position: 3, name: 'third' } }
|
||||
]
|
||||
@aggregateColumns = []
|
||||
@parentColumns = []
|
||||
|
@ -147,21 +301,21 @@ define [
|
|||
setup: ->
|
||||
@excludedFields = Gradebook.prototype.fieldsToExcludeFromAssignments
|
||||
|
||||
test "includes 'description' in the response", ->
|
||||
test 'includes "description" in the response', ->
|
||||
ok _.contains(@excludedFields, 'description')
|
||||
|
||||
test "includes 'needs_grading_count' in the response", ->
|
||||
test 'includes "needs_grading_count" in the response', ->
|
||||
ok _.contains(@excludedFields, 'needs_grading_count')
|
||||
|
||||
QUnit.module "Gradebook#submissionsForStudent",
|
||||
QUnit.module 'Gradebook#submissionsForStudent',
|
||||
setupThis: (options = {}) ->
|
||||
effectiveDueDates = {
|
||||
1: { 1: { grading_period_id: "1" } },
|
||||
2: { 1: { grading_period_id: "2" } }
|
||||
1: { 1: { grading_period_id: '1' } },
|
||||
2: { 1: { grading_period_id: '2' } }
|
||||
}
|
||||
|
||||
defaults = {
|
||||
gradingPeriodsEnabled: false,
|
||||
hasGradingPeriods: false,
|
||||
gradingPeriodToShow: null,
|
||||
isAllGradingPeriods: -> false,
|
||||
effectiveDueDates
|
||||
|
@ -170,32 +324,32 @@ define [
|
|||
|
||||
setup: ->
|
||||
@student =
|
||||
id: "1"
|
||||
assignment_1: { assignment_id: "1", user_id: "1", name: "yolo" }
|
||||
assignment_2: { assignment_id: "2", user_id: "1", name: "froyo" }
|
||||
id: '1'
|
||||
assignment_1: { assignment_id: '1', user_id: '1', name: 'yolo' }
|
||||
assignment_2: { assignment_id: '2', user_id: '1', name: 'froyo' }
|
||||
@submissionsForStudent = Gradebook.prototype.submissionsForStudent
|
||||
|
||||
test "returns all submissions for the student (multiple grading periods disabled)", ->
|
||||
test 'returns all submissions for the student when there are no grading periods', ->
|
||||
self = @setupThis()
|
||||
submissions = @submissionsForStudent.call(self, @student)
|
||||
propEqual _.pluck(submissions, "assignment_id"), ["1", "2"]
|
||||
propEqual _.pluck(submissions, 'assignment_id'), ['1', '2']
|
||||
|
||||
test "returns all submissions if 'All Grading Periods' is selected", ->
|
||||
test 'returns all submissions if "All Grading Periods" is selected', ->
|
||||
self = @setupThis(
|
||||
gradingPeriodsEnabled: true,
|
||||
gradingPeriodToShow: "0",
|
||||
hasGradingPeriods: true,
|
||||
gradingPeriodToShow: '0',
|
||||
isAllGradingPeriods: -> true
|
||||
)
|
||||
submissions = @submissionsForStudent.call(self, @student)
|
||||
propEqual _.pluck(submissions, "assignment_id"), ["1", "2"]
|
||||
propEqual _.pluck(submissions, 'assignment_id'), ['1', '2']
|
||||
|
||||
test "only returns submissions due for the student in the selected grading period", ->
|
||||
test 'only returns submissions due for the student in the selected grading period', ->
|
||||
self = @setupThis(
|
||||
gradingPeriodsEnabled: true,
|
||||
gradingPeriodToShow: "2"
|
||||
hasGradingPeriods: true,
|
||||
gradingPeriodToShow: '2'
|
||||
)
|
||||
submissions = @submissionsForStudent.call(self, @student)
|
||||
propEqual _.pluck(submissions, "assignment_id"), ["2"]
|
||||
propEqual _.pluck(submissions, 'assignment_id'), ['2']
|
||||
|
||||
QUnit.module 'Gradebook#studentsUrl',
|
||||
setupThis:(options) ->
|
||||
|
@ -224,9 +378,61 @@ define [
|
|||
self = @setupThis(showConcludedEnrollments: true, showInactiveEnrollments: true)
|
||||
equal @studentsUrl.call(self), 'students_with_concluded_and_inactive_enrollments_url'
|
||||
|
||||
QUnit.module 'Gradebook#weightedGroups',
|
||||
setup: ->
|
||||
@weightedGroups = Gradebook.prototype.weightedGroups
|
||||
|
||||
test 'returns true when group_weighting_scheme is "percent"', ->
|
||||
equal @weightedGroups.call(options: { group_weighting_scheme: 'percent' }), true
|
||||
|
||||
test 'returns false when group_weighting_scheme is not "percent"', ->
|
||||
equal @weightedGroups.call(options: { group_weighting_scheme: 'points' }), false
|
||||
equal @weightedGroups.call(options: { group_weighting_scheme: null }), false
|
||||
|
||||
QUnit.module 'Gradebook#weightedGrades',
|
||||
setupThis:(group_weighting_scheme, gradingPeriodSet) ->
|
||||
{ options: { group_weighting_scheme }, gradingPeriodSet }
|
||||
setup: ->
|
||||
@weightedGrades = Gradebook.prototype.weightedGrades
|
||||
|
||||
test 'returns true when group_weighting_scheme is "percent"', ->
|
||||
self = @setupThis('percent', { weighted: false })
|
||||
equal @weightedGrades.call(self), true
|
||||
|
||||
test 'returns true when the gradingPeriodSet is weighted', ->
|
||||
self = @setupThis('points', { weighted: true })
|
||||
equal @weightedGrades.call(self), true
|
||||
|
||||
test 'returns false when group_weighting_scheme is not "percent" and gradingPeriodSet is not weighted', ->
|
||||
self = @setupThis('points', { weighted: false })
|
||||
equal @weightedGrades.call(self), false
|
||||
|
||||
test 'returns false when group_weighting_scheme is not "percent" and gradingPeriodSet is not defined', ->
|
||||
self = @setupThis('points', null)
|
||||
equal @weightedGrades.call(self), false
|
||||
|
||||
QUnit.module 'Gradebook#displayPointTotals',
|
||||
setupThis:(show_total_grade_as_points, weightedGrades) ->
|
||||
options: { show_total_grade_as_points }
|
||||
weightedGrades: () -> weightedGrades
|
||||
setup: ->
|
||||
@displayPointTotals = Gradebook.prototype.displayPointTotals
|
||||
|
||||
test 'returns true when grades are not weighted and show_total_grade_as_points is true', ->
|
||||
self = @setupThis(true, false)
|
||||
equal @displayPointTotals.call(self), true
|
||||
|
||||
test 'returns false when grades are weighted', ->
|
||||
self = @setupThis(true, true)
|
||||
equal @displayPointTotals.call(self), false
|
||||
|
||||
test 'returns false when show_total_grade_as_points is false', ->
|
||||
self = @setupThis(false, false)
|
||||
equal @displayPointTotals.call(self), false
|
||||
|
||||
QUnit.module 'Gradebook#showNotesColumn',
|
||||
setup: ->
|
||||
@loadNotes = @stub(DataLoader, "getDataForColumn")
|
||||
@loadNotes = @stub(DataLoader, 'getDataForColumn')
|
||||
|
||||
setupShowNotesColumn: (opts) ->
|
||||
defaultOptions =
|
||||
|
@ -276,7 +482,7 @@ define [
|
|||
}
|
||||
|
||||
teardown: ->
|
||||
@fixtureParent.innerHTML = ""
|
||||
@fixtureParent.innerHTML = ''
|
||||
@fixture = undefined
|
||||
|
||||
test 'when not editable, returns false if the active cell node has the "cannot_edit" class', ->
|
||||
|
|
|
@ -12,8 +12,7 @@ define [
|
|||
defaults =
|
||||
current_user_roles: [ "teacher" ]
|
||||
GRADEBOOK_OPTIONS:
|
||||
multiple_grading_periods_enabled: true
|
||||
latest_end_date_of_admin_created_grading_periods_in_the_past: 'Thu Jul 30 2015 00:00:00 GMT-0700 (PDT)'
|
||||
has_grading_periods: true
|
||||
@previousWindowENV = window.ENV
|
||||
|
||||
_.extend(window.ENV, defaults)
|
||||
|
@ -54,8 +53,7 @@ define [
|
|||
defaults =
|
||||
current_user_roles: [ "teacher" ]
|
||||
GRADEBOOK_OPTIONS:
|
||||
multiple_grading_periods_enabled: true
|
||||
latest_end_date_of_admin_created_grading_periods_in_the_past: 'Thu Jul 30 2015 00:00:00 GMT-0700 (PDT)'
|
||||
has_grading_periods: true
|
||||
@previousWindowENV = window.ENV
|
||||
|
||||
_.extend(window.ENV, defaults)
|
||||
|
@ -86,8 +84,7 @@ define [
|
|||
defaults =
|
||||
current_user_roles: [ "teacher" ]
|
||||
GRADEBOOK_OPTIONS:
|
||||
multiple_grading_periods_enabled: true
|
||||
latest_end_date_of_admin_created_grading_periods_in_the_past: '2013-10-01T10:00:00Z'
|
||||
has_grading_periods: true
|
||||
@previousWindowENV = window.ENV
|
||||
|
||||
_.extend(window.ENV, defaults)
|
||||
|
|
|
@ -164,7 +164,7 @@ define [
|
|||
setup: ->
|
||||
fakeENV.setup({
|
||||
GRADEBOOK_OPTIONS: {
|
||||
multiple_grading_periods_enabled: true
|
||||
has_grading_periods: true
|
||||
}
|
||||
})
|
||||
@disableUnavailableMenuActions = GradebookHeaderMenu.prototype.disableUnavailableMenuActions
|
||||
|
@ -224,7 +224,7 @@ define [
|
|||
setup: ->
|
||||
fakeENV.setup({
|
||||
GRADEBOOK_OPTIONS: {
|
||||
multiple_grading_periods_enabled: true
|
||||
has_grading_periods: true
|
||||
},
|
||||
current_user_roles: ['admin']
|
||||
})
|
||||
|
@ -274,7 +274,7 @@ define [
|
|||
setup: ->
|
||||
fakeENV.setup({
|
||||
GRADEBOOK_OPTIONS: {
|
||||
multiple_grading_periods_enabled: true
|
||||
has_grading_periods: true
|
||||
},
|
||||
current_user_roles: ['admin']
|
||||
})
|
||||
|
|
|
@ -1,11 +1,165 @@
|
|||
#
|
||||
# Copyright (C) 2016 - 2017 Instructure, Inc.
|
||||
#
|
||||
# This file is part of Canvas.
|
||||
#
|
||||
# Canvas is free software: you can redistribute it and/or modify it under
|
||||
# the terms of the GNU Affero General Public License as published by the Free
|
||||
# Software Foundation, version 3 of the License.
|
||||
#
|
||||
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
# details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License along
|
||||
# with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
define [
|
||||
'spec/jsx/gradebook/GradeCalculatorSpecHelper'
|
||||
'compiled/gradezilla/Gradebook'
|
||||
'jsx/gradezilla/DataLoader'
|
||||
'underscore'
|
||||
'timezone'
|
||||
'compiled/SubmissionDetailsDialog'
|
||||
'compiled/util/natcompare'
|
||||
], (Gradebook, DataLoader, _, tz, SubmissionDetailsDialog, natcompare) ->
|
||||
'compiled/SubmissionDetailsDialog'
|
||||
'jsx/gradebook/CourseGradeCalculator'
|
||||
], (
|
||||
GradeCalculatorSpecHelper, Gradebook, DataLoader, _, tz, natcompare, SubmissionDetailsDialog, CourseGradeCalculator
|
||||
) ->
|
||||
exampleGradebookOptions =
|
||||
settings:
|
||||
show_concluded_enrollments: 'true'
|
||||
show_inactive_enrollments: 'true'
|
||||
sections: []
|
||||
|
||||
createExampleGrades = GradeCalculatorSpecHelper.createCourseGradesWithGradingPeriods
|
||||
|
||||
QUnit.module 'Gradebook'
|
||||
|
||||
test 'normalizes the grading period set from the env', ->
|
||||
options = _.extend {}, exampleGradebookOptions,
|
||||
grading_period_set:
|
||||
id: '1501'
|
||||
grading_periods: [{ id: '701', weight: 50 }, { id: '702', weight: 50 }]
|
||||
weighted: true
|
||||
gradingPeriodSet = new Gradebook(options).gradingPeriodSet
|
||||
deepEqual(gradingPeriodSet.id, '1501')
|
||||
equal(gradingPeriodSet.gradingPeriods.length, 2)
|
||||
deepEqual(_.map(gradingPeriodSet.gradingPeriods, 'id'), ['701', '702'])
|
||||
|
||||
test 'sets grading period set to null when not defined in the env', ->
|
||||
gradingPeriodSet = new Gradebook(exampleGradebookOptions).gradingPeriodSet
|
||||
deepEqual(gradingPeriodSet, null)
|
||||
|
||||
QUnit.module 'Gradebook#calculateStudentGrade',
|
||||
setupThis:(options = {}) ->
|
||||
assignments = [{ id: 201, points_possible: 10, omit_from_final_grade: false }]
|
||||
submissions = [{ assignment_id: 201, score: 10 }]
|
||||
defaults = {
|
||||
gradingPeriodToShow: '0'
|
||||
isAllGradingPeriods: Gradebook.prototype.isAllGradingPeriods
|
||||
assignmentGroups: [{ id: 301, group_weight: 60, rules: {}, assignments }]
|
||||
options: { group_weighting_scheme: 'points' }
|
||||
gradingPeriods: [{ id: 701, weight: 50 }, { id: 702, weight: 50 }]
|
||||
gradingPeriodSet:
|
||||
id: '1501'
|
||||
gradingPeriods: [{ id: '701', weight: 50 }, { id: '702', weight: 50 }]
|
||||
weighted: true
|
||||
effectiveDueDates: { 201: { 101: { grading_period_id: '701' } } }
|
||||
submissionsForStudent: () ->
|
||||
submissions
|
||||
addDroppedClass: () ->
|
||||
}
|
||||
_.defaults options, defaults
|
||||
|
||||
setup: ->
|
||||
@calculate = Gradebook.prototype.calculateStudentGrade
|
||||
|
||||
test 'calculates grades using properties from the gradebook', ->
|
||||
self = @setupThis()
|
||||
@stub(CourseGradeCalculator, 'calculate').returns(createExampleGrades())
|
||||
@calculate.call(self, id: '101', loaded: true, initialized: true)
|
||||
args = CourseGradeCalculator.calculate.getCall(0).args
|
||||
equal(args[0], self.submissionsForStudent())
|
||||
equal(args[1], self.assignmentGroups)
|
||||
equal(args[2], self.options.group_weighting_scheme)
|
||||
equal(args[3], self.gradingPeriodSet)
|
||||
|
||||
test 'scopes effective due dates to the user', ->
|
||||
self = @setupThis()
|
||||
@stub(CourseGradeCalculator, 'calculate').returns(createExampleGrades())
|
||||
@calculate.call(self, id: '101', loaded: true, initialized: true)
|
||||
dueDates = CourseGradeCalculator.calculate.getCall(0).args[4]
|
||||
deepEqual(dueDates, 201: { grading_period_id: '701' })
|
||||
|
||||
test 'calculates grades without grading period data when grading period set is null', ->
|
||||
self = @setupThis(gradingPeriodSet: null)
|
||||
@stub(CourseGradeCalculator, 'calculate').returns(createExampleGrades())
|
||||
@calculate.call(self, id: '101', loaded: true, initialized: true)
|
||||
args = CourseGradeCalculator.calculate.getCall(0).args
|
||||
equal(args[0], self.submissionsForStudent())
|
||||
equal(args[1], self.assignmentGroups)
|
||||
equal(args[2], self.options.group_weighting_scheme)
|
||||
equal(typeof args[3], 'undefined')
|
||||
equal(typeof args[4], 'undefined')
|
||||
|
||||
test 'calculates grades without grading period data when effective due dates are not defined', ->
|
||||
self = @setupThis(effectiveDueDates: null)
|
||||
@stub(CourseGradeCalculator, 'calculate').returns(createExampleGrades())
|
||||
@calculate.call(self, id: '101', loaded: true, initialized: true)
|
||||
args = CourseGradeCalculator.calculate.getCall(0).args
|
||||
equal(args[0], self.submissionsForStudent())
|
||||
equal(args[1], self.assignmentGroups)
|
||||
equal(args[2], self.options.group_weighting_scheme)
|
||||
equal(typeof args[3], 'undefined')
|
||||
equal(typeof args[4], 'undefined')
|
||||
|
||||
test 'stores the current grade on the student when not including ungraded assignments', ->
|
||||
exampleGrades = createExampleGrades()
|
||||
self = @setupThis(include_ungraded_assignments: false)
|
||||
@stub(CourseGradeCalculator, 'calculate').returns(exampleGrades)
|
||||
student = { id: '101', loaded: true, initialized: true }
|
||||
@calculate.call(self, student)
|
||||
equal(student.total_grade, exampleGrades.current)
|
||||
|
||||
test 'stores the final grade on the student when including ungraded assignments', ->
|
||||
exampleGrades = createExampleGrades()
|
||||
self = @setupThis(include_ungraded_assignments: true)
|
||||
@stub(CourseGradeCalculator, 'calculate').returns(exampleGrades)
|
||||
student = { id: '101', loaded: true, initialized: true }
|
||||
@calculate.call(self, student)
|
||||
equal(student.total_grade, exampleGrades.final)
|
||||
|
||||
test 'stores the current grade from the selected grading period when not including ungraded assignments', ->
|
||||
exampleGrades = createExampleGrades()
|
||||
self = @setupThis(gradingPeriodToShow: 701, include_ungraded_assignments: false)
|
||||
@stub(CourseGradeCalculator, 'calculate').returns(exampleGrades)
|
||||
student = { id: '101', loaded: true, initialized: true }
|
||||
@calculate.call(self, student)
|
||||
equal(student.total_grade, exampleGrades.gradingPeriods[701].current)
|
||||
|
||||
test 'stores the final grade from the selected grading period when including ungraded assignments', ->
|
||||
exampleGrades = createExampleGrades()
|
||||
self = @setupThis(gradingPeriodToShow: 701, include_ungraded_assignments: true)
|
||||
@stub(CourseGradeCalculator, 'calculate').returns(exampleGrades)
|
||||
student = { id: '101', loaded: true, initialized: true }
|
||||
@calculate.call(self, student)
|
||||
equal(student.total_grade, exampleGrades.gradingPeriods[701].final)
|
||||
|
||||
test 'does not calculate when the student is not loaded', ->
|
||||
self = @setupThis()
|
||||
@stub(CourseGradeCalculator, 'calculate').returns(createExampleGrades())
|
||||
@calculate.call(self, id: '101', loaded: false, initialized: true)
|
||||
notOk(CourseGradeCalculator.calculate.called)
|
||||
|
||||
test 'does not calculate when the student is not initialized', ->
|
||||
self = @setupThis()
|
||||
@stub(CourseGradeCalculator, 'calculate').returns(createExampleGrades())
|
||||
@calculate.call(self, id: '101', loaded: true, initialized: false)
|
||||
notOk(CourseGradeCalculator.calculate.called)
|
||||
|
||||
QUnit.module "Gradebook#localeSort"
|
||||
|
||||
test "delegates to natcompare.strings", ->
|
||||
|
@ -18,9 +172,9 @@ define [
|
|||
Gradebook.prototype.localeSort(0, false)
|
||||
ok natCompareSpy.calledWith('', '')
|
||||
|
||||
QUnit.module "Gradebook#gradeSort"
|
||||
QUnit.module 'Gradebook#gradeSort'
|
||||
|
||||
test "gradeSort - total_grade", ->
|
||||
test 'gradeSort - total_grade', ->
|
||||
gradeSort = (showTotalGradeAsPoints, a, b, field, asc) ->
|
||||
asc = true unless asc?
|
||||
|
||||
|
@ -32,27 +186,27 @@ define [
|
|||
, {total_grade: {score: 10, possible: 20}}
|
||||
, {total_grade: {score: 5, possible: 10}}
|
||||
, 'total_grade') == 0
|
||||
, "total_grade sorts by percent (normally)"
|
||||
, 'total_grade sorts by percent (normally)'
|
||||
|
||||
ok gradeSort(true
|
||||
, {total_grade: {score: 10, possible: 20}}
|
||||
, {total_grade: {score: 5, possible: 10}}
|
||||
, 'total_grade') > 0
|
||||
, "total_grade sorts by score when if show_total_grade_as_points"
|
||||
, 'total_grade sorts by score when if show_total_grade_as_points'
|
||||
|
||||
ok gradeSort(true
|
||||
, {assignment_group_1: {score: 10, possible: 20}}
|
||||
, {assignment_group_1: {score: 5, possible: 10}}
|
||||
, 'assignment_group_1') == 0
|
||||
, "assignment groups are always sorted by percent"
|
||||
, 'assignment groups are always sorted by percent'
|
||||
|
||||
ok gradeSort(false
|
||||
, {assignment1: {score: 5, possible: 10}}
|
||||
, {assignment1: {score: 10, possible: 20}}
|
||||
, 'assignment1') < 0
|
||||
, "other fields are sorted by score"
|
||||
, 'other fields are sorted by score'
|
||||
|
||||
QUnit.module "Gradebook#hideAggregateColumns",
|
||||
QUnit.module 'Gradebook#hideAggregateColumns',
|
||||
gradebookStubs: ->
|
||||
indexedOverrides: Gradebook.prototype.indexedOverrides
|
||||
indexedGradingPeriods: _.indexBy(@gradingPeriods, 'id')
|
||||
|
@ -60,7 +214,7 @@ define [
|
|||
setupThis: (options) ->
|
||||
customOptions = options || {}
|
||||
defaults =
|
||||
gradingPeriodsEnabled: true
|
||||
hasGradingPeriods: true
|
||||
getGradingPeriodToShow: -> '1'
|
||||
options:
|
||||
all_grading_periods_totals: false
|
||||
|
@ -71,13 +225,13 @@ define [
|
|||
@hideAggregateColumns = Gradebook.prototype.hideAggregateColumns
|
||||
teardown: ->
|
||||
|
||||
test 'returns false if multiple grading periods is disabled', ->
|
||||
self = @setupThis(gradingPeriodsEnabled: false, isAllGradingPeriods: -> false)
|
||||
test 'returns false if there are no grading periods', ->
|
||||
self = @setupThis(hasGradingPeriods: false, isAllGradingPeriods: -> false)
|
||||
notOk @hideAggregateColumns.call(self)
|
||||
|
||||
test 'returns false if multiple grading periods is disabled, even if isAllGradingPeriods is true', ->
|
||||
test 'returns false if there are no grading periods, even if isAllGradingPeriods is true', ->
|
||||
self = @setupThis
|
||||
gradingPeriodsEnabled: false
|
||||
hasGradingPeriods: false
|
||||
getGradingPeriodToShow: -> '0'
|
||||
isAllGradingPeriods: -> true
|
||||
|
||||
|
@ -114,9 +268,9 @@ define [
|
|||
@getStoredSortOrder = Gradebook.prototype.getStoredSortOrder
|
||||
@defaultSortType = 'assignment_group'
|
||||
@allAssignmentColumns = [
|
||||
{ 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: 1, name: 'first' } },
|
||||
{ object: { assignment_group: { position: 1 }, position: 2, name: 'second' } },
|
||||
{ object: { assignment_group: { position: 1 }, position: 3, name: 'third' } }
|
||||
]
|
||||
@aggregateColumns = []
|
||||
@parentColumns = []
|
||||
|
@ -147,21 +301,21 @@ define [
|
|||
setup: ->
|
||||
@excludedFields = Gradebook.prototype.fieldsToExcludeFromAssignments
|
||||
|
||||
test "includes 'description' in the response", ->
|
||||
test 'includes "description" in the response', ->
|
||||
ok _.contains(@excludedFields, 'description')
|
||||
|
||||
test "includes 'needs_grading_count' in the response", ->
|
||||
test 'includes "needs_grading_count" in the response', ->
|
||||
ok _.contains(@excludedFields, 'needs_grading_count')
|
||||
|
||||
QUnit.module "Gradebook#submissionsForStudent",
|
||||
QUnit.module 'Gradebook#submissionsForStudent',
|
||||
setupThis: (options = {}) ->
|
||||
effectiveDueDates = {
|
||||
1: { 1: { grading_period_id: "1" } },
|
||||
2: { 1: { grading_period_id: "2" } }
|
||||
1: { 1: { grading_period_id: '1' } },
|
||||
2: { 1: { grading_period_id: '2' } }
|
||||
}
|
||||
|
||||
defaults = {
|
||||
gradingPeriodsEnabled: false,
|
||||
hasGradingPeriods: false,
|
||||
gradingPeriodToShow: null,
|
||||
isAllGradingPeriods: -> false,
|
||||
effectiveDueDates
|
||||
|
@ -170,32 +324,32 @@ define [
|
|||
|
||||
setup: ->
|
||||
@student =
|
||||
id: "1"
|
||||
assignment_1: { assignment_id: "1", user_id: "1", name: "yolo" }
|
||||
assignment_2: { assignment_id: "2", user_id: "1", name: "froyo" }
|
||||
id: '1'
|
||||
assignment_1: { assignment_id: '1', user_id: '1', name: 'yolo' }
|
||||
assignment_2: { assignment_id: '2', user_id: '1', name: 'froyo' }
|
||||
@submissionsForStudent = Gradebook.prototype.submissionsForStudent
|
||||
|
||||
test "returns all submissions for the student (multiple grading periods disabled)", ->
|
||||
test 'returns all submissions for the student when there are no grading periods', ->
|
||||
self = @setupThis()
|
||||
submissions = @submissionsForStudent.call(self, @student)
|
||||
propEqual _.pluck(submissions, "assignment_id"), ["1", "2"]
|
||||
propEqual _.pluck(submissions, 'assignment_id'), ['1', '2']
|
||||
|
||||
test "returns all submissions if 'All Grading Periods' is selected", ->
|
||||
test 'returns all submissions if "All Grading Periods" is selected', ->
|
||||
self = @setupThis(
|
||||
gradingPeriodsEnabled: true,
|
||||
gradingPeriodToShow: "0",
|
||||
hasGradingPeriods: true,
|
||||
gradingPeriodToShow: '0',
|
||||
isAllGradingPeriods: -> true
|
||||
)
|
||||
submissions = @submissionsForStudent.call(self, @student)
|
||||
propEqual _.pluck(submissions, "assignment_id"), ["1", "2"]
|
||||
propEqual _.pluck(submissions, 'assignment_id'), ['1', '2']
|
||||
|
||||
test "only returns submissions due for the student in the selected grading period", ->
|
||||
test 'only returns submissions due for the student in the selected grading period', ->
|
||||
self = @setupThis(
|
||||
gradingPeriodsEnabled: true,
|
||||
gradingPeriodToShow: "2"
|
||||
hasGradingPeriods: true,
|
||||
gradingPeriodToShow: '2'
|
||||
)
|
||||
submissions = @submissionsForStudent.call(self, @student)
|
||||
propEqual _.pluck(submissions, "assignment_id"), ["2"]
|
||||
propEqual _.pluck(submissions, 'assignment_id'), ['2']
|
||||
|
||||
QUnit.module 'Gradebook#studentsUrl',
|
||||
setupThis:(options) ->
|
||||
|
@ -224,9 +378,61 @@ define [
|
|||
self = @setupThis(showConcludedEnrollments: true, showInactiveEnrollments: true)
|
||||
equal @studentsUrl.call(self), 'students_with_concluded_and_inactive_enrollments_url'
|
||||
|
||||
QUnit.module 'Gradebook#weightedGroups',
|
||||
setup: ->
|
||||
@weightedGroups = Gradebook.prototype.weightedGroups
|
||||
|
||||
test 'returns true when group_weighting_scheme is "percent"', ->
|
||||
equal @weightedGroups.call(options: { group_weighting_scheme: 'percent' }), true
|
||||
|
||||
test 'returns false when group_weighting_scheme is not "percent"', ->
|
||||
equal @weightedGroups.call(options: { group_weighting_scheme: 'points' }), false
|
||||
equal @weightedGroups.call(options: { group_weighting_scheme: null }), false
|
||||
|
||||
QUnit.module 'Gradebook#weightedGrades',
|
||||
setupThis:(group_weighting_scheme, gradingPeriodSet) ->
|
||||
{ options: { group_weighting_scheme }, gradingPeriodSet }
|
||||
setup: ->
|
||||
@weightedGrades = Gradebook.prototype.weightedGrades
|
||||
|
||||
test 'returns true when group_weighting_scheme is "percent"', ->
|
||||
self = @setupThis('percent', { weighted: false })
|
||||
equal @weightedGrades.call(self), true
|
||||
|
||||
test 'returns true when the gradingPeriodSet is weighted', ->
|
||||
self = @setupThis('points', { weighted: true })
|
||||
equal @weightedGrades.call(self), true
|
||||
|
||||
test 'returns false when group_weighting_scheme is not "percent" and gradingPeriodSet is not weighted', ->
|
||||
self = @setupThis('points', { weighted: false })
|
||||
equal @weightedGrades.call(self), false
|
||||
|
||||
test 'returns false when group_weighting_scheme is not "percent" and gradingPeriodSet is not defined', ->
|
||||
self = @setupThis('points', null)
|
||||
equal @weightedGrades.call(self), false
|
||||
|
||||
QUnit.module 'Gradebook#displayPointTotals',
|
||||
setupThis:(show_total_grade_as_points, weightedGrades) ->
|
||||
options: { show_total_grade_as_points }
|
||||
weightedGrades: () -> weightedGrades
|
||||
setup: ->
|
||||
@displayPointTotals = Gradebook.prototype.displayPointTotals
|
||||
|
||||
test 'returns true when grades are not weighted and show_total_grade_as_points is true', ->
|
||||
self = @setupThis(true, false)
|
||||
equal @displayPointTotals.call(self), true
|
||||
|
||||
test 'returns false when grades are weighted', ->
|
||||
self = @setupThis(true, true)
|
||||
equal @displayPointTotals.call(self), false
|
||||
|
||||
test 'returns false when show_total_grade_as_points is false', ->
|
||||
self = @setupThis(false, false)
|
||||
equal @displayPointTotals.call(self), false
|
||||
|
||||
QUnit.module 'Gradebook#showNotesColumn',
|
||||
setup: ->
|
||||
@loadNotes = @stub(DataLoader, "getDataForColumn")
|
||||
@loadNotes = @stub(DataLoader, 'getDataForColumn')
|
||||
|
||||
setupShowNotesColumn: (opts) ->
|
||||
defaultOptions =
|
||||
|
@ -276,7 +482,7 @@ define [
|
|||
}
|
||||
|
||||
teardown: ->
|
||||
@fixtureParent.innerHTML = ""
|
||||
@fixtureParent.innerHTML = ''
|
||||
@fixture = undefined
|
||||
|
||||
test 'when not editable, returns false if the active cell node has the "cannot_edit" class', ->
|
||||
|
|
|
@ -12,8 +12,7 @@ define [
|
|||
defaults =
|
||||
current_user_roles: [ "teacher" ]
|
||||
GRADEBOOK_OPTIONS:
|
||||
multiple_grading_periods_enabled: true
|
||||
latest_end_date_of_admin_created_grading_periods_in_the_past: 'Thu Jul 30 2015 00:00:00 GMT-0700 (PDT)'
|
||||
has_grading_periods: true
|
||||
@previousWindowENV = window.ENV
|
||||
|
||||
_.extend(window.ENV, defaults)
|
||||
|
@ -54,8 +53,7 @@ define [
|
|||
defaults =
|
||||
current_user_roles: [ "teacher" ]
|
||||
GRADEBOOK_OPTIONS:
|
||||
multiple_grading_periods_enabled: true
|
||||
latest_end_date_of_admin_created_grading_periods_in_the_past: 'Thu Jul 30 2015 00:00:00 GMT-0700 (PDT)'
|
||||
has_grading_periods: true
|
||||
@previousWindowENV = window.ENV
|
||||
|
||||
_.extend(window.ENV, defaults)
|
||||
|
@ -86,8 +84,7 @@ define [
|
|||
defaults =
|
||||
current_user_roles: [ "teacher" ]
|
||||
GRADEBOOK_OPTIONS:
|
||||
multiple_grading_periods_enabled: true
|
||||
latest_end_date_of_admin_created_grading_periods_in_the_past: '2013-10-01T10:00:00Z'
|
||||
has_grading_periods: true
|
||||
@previousWindowENV = window.ENV
|
||||
|
||||
_.extend(window.ENV, defaults)
|
||||
|
|
|
@ -32,7 +32,7 @@ define [
|
|||
groups: {1:{id: "1", name: "Reading Group One"}, 2: {id: "2", name: "Reading Group Two"}}
|
||||
overrideModel: AssignmentOverride
|
||||
syncWithBackbone: ->
|
||||
multipleGradingPeriodsEnabled: false
|
||||
hasGradingPeriods: false
|
||||
gradingPeriods: []
|
||||
isOnlyVisibleToOverrides: false
|
||||
dueAt: null
|
||||
|
@ -135,7 +135,7 @@ define [
|
|||
attributes = _.keys(@dueDates.getAllOverrides()[0].attributes)
|
||||
ok _.contains(attributes, "persisted")
|
||||
|
||||
QUnit.module 'DueDates with Multiple Grading Periods enabled',
|
||||
QUnit.module 'DueDates with grading periods',
|
||||
setup: ->
|
||||
fakeENV.setup()
|
||||
@server = sinon.fakeServer.create()
|
||||
|
@ -247,7 +247,7 @@ define [
|
|||
sections: sections
|
||||
groups: {1:{id: "1", name: "Reading Group One"}, 2: {id: "2", name: "Reading Group Two"}}
|
||||
syncWithBackbone: ->
|
||||
multipleGradingPeriodsEnabled: true
|
||||
hasGradingPeriods: true
|
||||
gradingPeriods: gradingPeriods
|
||||
isOnlyVisibleToOverrides: true
|
||||
dueAt: null
|
||||
|
@ -305,7 +305,7 @@ define [
|
|||
students: {"1":{id: "1", name: "Scipio Africanus"}, "3":{id: 3, name: "Publius Publicoa"}}
|
||||
overrideModel: AssignmentOverride
|
||||
syncWithBackbone: ->
|
||||
multipleGradingPeriodsEnabled: false
|
||||
hasGradingPeriods: false
|
||||
gradingPeriods: []
|
||||
isOnlyVisibleToOverrides: false
|
||||
dueAt: null
|
||||
|
|
|
@ -41,8 +41,7 @@ define [
|
|||
}
|
||||
]
|
||||
"grading_periods_read_only": false,
|
||||
"can_create_grading_periods": true,
|
||||
"can_toggle_grading_periods": true
|
||||
"can_create_grading_periods": true
|
||||
|
||||
@formattedIndexData =
|
||||
"grading_periods":[
|
||||
|
@ -66,8 +65,7 @@ define [
|
|||
}
|
||||
]
|
||||
"grading_periods_read_only": false,
|
||||
"can_create_grading_periods": true,
|
||||
"can_toggle_grading_periods": true
|
||||
"can_create_grading_periods": true
|
||||
|
||||
@createdPeriodData = "grading_periods":[
|
||||
{
|
||||
|
@ -260,51 +258,6 @@ define [
|
|||
test 'renderSaveButton renders a button if the user is not at the course grading periods page', ->
|
||||
ok @gradingPeriodCollection.renderSaveButton()
|
||||
|
||||
QUnit.module 'GradingPeriodCollection with one grading period',
|
||||
setup: ->
|
||||
@server = sinon.fakeServer.create()
|
||||
fakeENV.setup()
|
||||
ENV.current_user_roles = ["admin"]
|
||||
ENV.GRADING_PERIODS_URL = "/api/v1/accounts/1/grading_periods"
|
||||
@indexData =
|
||||
"grading_periods":[
|
||||
{
|
||||
"id":"1", "start_date":"2015-03-01T06:00:00Z", "end_date":"2015-05-31T05:00:00Z",
|
||||
"weight":null, "title":"Spring", "permissions": { "update":true, "delete":true }
|
||||
}
|
||||
]
|
||||
"grading_periods_read_only": false,
|
||||
"can_create_grading_periods": true,
|
||||
"can_toggle_grading_periods": true
|
||||
|
||||
@formattedIndexData =
|
||||
"grading_periods":[
|
||||
{
|
||||
"id":"1", "startDate": new Date("2015-03-01T06:00:00Z"), "endDate": new Date("2015-05-31T05:00:00Z"),
|
||||
"weight":null, "title":"Spring", "permissions": { "update":true, "delete":true }
|
||||
}
|
||||
]
|
||||
"grading_periods_read_only": false,
|
||||
"can_create_grading_periods": true,
|
||||
"can_toggle_grading_periods": true
|
||||
|
||||
@server.respondWith "GET", ENV.GRADING_PERIODS_URL, [200, {"Content-Type":"application/json"}, JSON.stringify @indexData]
|
||||
GradingPeriodCollectionElement = React.createElement(GradingPeriodCollection)
|
||||
@gradingPeriodCollection = TestUtils.renderIntoDocument(GradingPeriodCollectionElement)
|
||||
@server.respond()
|
||||
|
||||
teardown: ->
|
||||
ReactDOM.unmountComponentAtNode(@gradingPeriodCollection.getDOMNode().parentNode)
|
||||
fakeENV.teardown()
|
||||
@server.restore()
|
||||
|
||||
test 'shows a link to the settings page if the user can toggle the multiple grading periods feature', ->
|
||||
ok @gradingPeriodCollection.refs.linkToSettings
|
||||
|
||||
test 'does not show a link to the settings page if the user cannot toggle the multiple grading periods feature', ->
|
||||
@gradingPeriodCollection.setState(canChangeGradingPeriodsSetting: false)
|
||||
notOk @gradingPeriodCollection.refs.linkToSettings
|
||||
|
||||
QUnit.module 'GradingPeriodCollection with read-only grading periods',
|
||||
setup: ->
|
||||
@server = sinon.fakeServer.create()
|
||||
|
@ -319,19 +272,7 @@ define [
|
|||
}
|
||||
]
|
||||
"grading_periods_read_only": true,
|
||||
"can_create_grading_periods": true,
|
||||
"can_toggle_grading_periods": true
|
||||
|
||||
@formattedIndexData =
|
||||
"grading_periods":[
|
||||
{
|
||||
"id":"1", "startDate": new Date("2015-03-01T06:00:00Z"), "endDate": new Date("2015-05-31T05:00:00Z"),
|
||||
"weight":null, "title":"Spring", "permissions": { "update":true, "delete":true }
|
||||
}
|
||||
]
|
||||
"grading_periods_read_only": true,
|
||||
"can_create_grading_periods": true,
|
||||
"can_toggle_grading_periods": true
|
||||
"can_create_grading_periods": true
|
||||
|
||||
@server.respondWith "GET", ENV.GRADING_PERIODS_URL, [200, {"Content-Type":"application/json"}, JSON.stringify @indexData]
|
||||
GradingPeriodCollectionElement = React.createElement(GradingPeriodCollection)
|
||||
|
|
|
@ -304,7 +304,7 @@ define [
|
|||
equal errors["name"][0]["message"], "Name is required!"
|
||||
|
||||
test "requires due_at to be in an open grading period if it is being changed and the user is a teacher", ->
|
||||
ENV.MULTIPLE_GRADING_PERIODS_ENABLED = true
|
||||
ENV.HAS_GRADING_PERIODS = true
|
||||
ENV.active_grading_periods = [{
|
||||
id: "1"
|
||||
start_date: "2103-07-01T06:00:00Z"
|
||||
|
|
|
@ -82,8 +82,6 @@ describe AssignmentGroupsController do
|
|||
|
||||
context 'given an assignment group with and without integration data' do
|
||||
before(:once) do
|
||||
root_account.allow_feature!(:multiple_grading_periods)
|
||||
root_account.enable_feature!(:multiple_grading_periods)
|
||||
account_admin_user(account: root_account)
|
||||
end
|
||||
|
||||
|
@ -127,8 +125,6 @@ describe AssignmentGroupsController do
|
|||
|
||||
context 'given a root account with a grading period and a sub account with a grading period' do
|
||||
before(:once) do
|
||||
root_account.allow_feature!(:multiple_grading_periods)
|
||||
root_account.enable_feature!(:multiple_grading_periods)
|
||||
account_admin_user(account: root_account)
|
||||
end
|
||||
|
||||
|
@ -241,18 +237,6 @@ describe AssignmentGroupsController do
|
|||
expect(response).to be_success
|
||||
end
|
||||
end
|
||||
|
||||
context 'multiple grading periods feature enabled' do
|
||||
before do
|
||||
@course.root_account.enable_feature!(:multiple_grading_periods)
|
||||
end
|
||||
|
||||
it 'does not throw an error when grading_period_id is passed in as empty string' do
|
||||
user_session(@teacher)
|
||||
get 'index', :course_id => @course.id, :include => ['assignments', 'assignment_visibility'], :grading_period_id => '', :format => :json
|
||||
expect(response).to be_success
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'passing include_param submission', type: :request do
|
||||
|
@ -365,9 +349,8 @@ describe AssignmentGroupsController do
|
|||
expect(@group1.assignments.count).to eq(3)
|
||||
end
|
||||
|
||||
context 'with multiple grading periods enabled' do
|
||||
context 'with grading periods' do
|
||||
before :once do
|
||||
@course.root_account.enable_feature!(:multiple_grading_periods)
|
||||
group = Factories::GradingPeriodGroupHelper.new.create_for_account(@course.root_account)
|
||||
term = @course.enrollment_term
|
||||
term.grading_period_group = group
|
||||
|
|
|
@ -424,7 +424,7 @@ describe AssignmentsController do
|
|||
end
|
||||
|
||||
describe "GET 'edit'" do
|
||||
include_context "multiple grading periods within controller" do
|
||||
include_context "grading periods within controller" do
|
||||
let(:course) { @course }
|
||||
let(:teacher) { @teacher }
|
||||
let(:request_params) { [:edit, course_id: course, id: @assignment] }
|
||||
|
|
|
@ -616,14 +616,15 @@ describe DiscussionTopicsController do
|
|||
course_topic
|
||||
end
|
||||
|
||||
include_context "multiple grading periods within controller" do
|
||||
include_context "grading periods within controller" do
|
||||
let(:course) { @course }
|
||||
let(:teacher) { @teacher }
|
||||
let(:request_params) { [:edit, course_id: course, id: @topic] }
|
||||
end
|
||||
|
||||
it "should not explode with mgp and group context" do
|
||||
@course.root_account.enable_feature!(:multiple_grading_periods)
|
||||
group1 = Factories::GradingPeriodGroupHelper.new.create_for_account(@course.root_account)
|
||||
group1.enrollment_terms << @course.enrollment_term
|
||||
user_session(@teacher)
|
||||
group = group_model(:context => @course)
|
||||
group_topic = group.discussion_topics.create!(:title => "title")
|
||||
|
|
|
@ -315,9 +315,16 @@ describe GradebooksController do
|
|||
end
|
||||
end
|
||||
|
||||
context "Multiple Grading Periods" do
|
||||
context "with grading periods" do
|
||||
let(:group_helper) { Factories::GradingPeriodGroupHelper.new }
|
||||
let(:period_helper) { Factories::GradingPeriodHelper.new }
|
||||
|
||||
before :once do
|
||||
@course.root_account.enable_feature!(:multiple_grading_periods)
|
||||
@grading_period_group = group_helper.create_for_account(@course.root_account)
|
||||
term = @course.enrollment_term
|
||||
term.grading_period_group = @grading_period_group
|
||||
term.save!
|
||||
@grading_periods = period_helper.create_presets_for_group(@grading_period_group, :past, :current, :future)
|
||||
end
|
||||
|
||||
it "does not display totals if 'All Grading Periods' is selected" do
|
||||
|
@ -332,6 +339,37 @@ describe GradebooksController do
|
|||
get 'grade_summary', :course_id => @course.id, :id => @student.id, grading_period_id: 1
|
||||
expect(assigns[:exclude_total]).to eq false
|
||||
end
|
||||
|
||||
it "includes the grading period group (as 'set') in the ENV" do
|
||||
user_session(@teacher)
|
||||
get :grade_summary, { course_id: @course.id, id: @student.id }
|
||||
grading_period_set = assigns[:js_env][:grading_period_set]
|
||||
expect(grading_period_set[:id]).to eq @grading_period_group.id
|
||||
end
|
||||
|
||||
it "includes grading periods within the group" do
|
||||
user_session(@teacher)
|
||||
get :grade_summary, { course_id: @course.id, id: @student.id }
|
||||
grading_period_set = assigns[:js_env][:grading_period_set]
|
||||
expect(grading_period_set[:grading_periods].count).to eq 3
|
||||
period = grading_period_set[:grading_periods][0]
|
||||
expect(period).to have_key(:is_closed)
|
||||
expect(period).to have_key(:is_last)
|
||||
end
|
||||
|
||||
it "includes necessary keys with each grading period" do
|
||||
user_session(@teacher)
|
||||
get :grade_summary, { course_id: @course.id, id: @student.id }
|
||||
periods = assigns[:js_env][:grading_period_set][:grading_periods]
|
||||
periods.each do |period|
|
||||
expect(period).to have_key(:id)
|
||||
expect(period).to have_key(:start_date)
|
||||
expect(period).to have_key(:end_date)
|
||||
expect(period).to have_key(:close_date)
|
||||
expect(period).to have_key(:is_closed)
|
||||
expect(period).to have_key(:is_last)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "with assignment due date overrides" do
|
||||
|
@ -558,6 +596,49 @@ describe GradebooksController do
|
|||
expect(assigns[:js_env][:STUDENT_CONTEXT_CARDS_ENABLED]).to eq true
|
||||
end
|
||||
end
|
||||
|
||||
context "with grading periods" do
|
||||
let(:group_helper) { Factories::GradingPeriodGroupHelper.new }
|
||||
let(:period_helper) { Factories::GradingPeriodHelper.new }
|
||||
|
||||
before :once do
|
||||
@grading_period_group = group_helper.create_for_account(@course.root_account)
|
||||
term = @course.enrollment_term
|
||||
term.grading_period_group = @grading_period_group
|
||||
term.save!
|
||||
@grading_periods = period_helper.create_presets_for_group(@grading_period_group, :past, :current, :future)
|
||||
end
|
||||
|
||||
before { user_session(@teacher) }
|
||||
|
||||
it "includes the grading period group (as 'set') in the ENV" do
|
||||
get :show, { course_id: @course.id }
|
||||
grading_period_set = assigns[:js_env][:GRADEBOOK_OPTIONS][:grading_period_set]
|
||||
expect(grading_period_set[:id]).to eq @grading_period_group.id
|
||||
end
|
||||
|
||||
it "includes grading periods within the group" do
|
||||
get :show, { course_id: @course.id }
|
||||
grading_period_set = assigns[:js_env][:GRADEBOOK_OPTIONS][:grading_period_set]
|
||||
expect(grading_period_set[:grading_periods].count).to eq 3
|
||||
period = grading_period_set[:grading_periods][0]
|
||||
expect(period).to have_key(:is_closed)
|
||||
expect(period).to have_key(:is_last)
|
||||
end
|
||||
|
||||
it "includes necessary keys with each grading period" do
|
||||
get :show, { course_id: @course.id }
|
||||
periods = assigns[:js_env][:GRADEBOOK_OPTIONS][:grading_period_set][:grading_periods]
|
||||
periods.each do |period|
|
||||
expect(period).to have_key(:id)
|
||||
expect(period).to have_key(:start_date)
|
||||
expect(period).to have_key(:end_date)
|
||||
expect(period).to have_key(:close_date)
|
||||
expect(period).to have_key(:is_closed)
|
||||
expect(period).to have_key(:is_last)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "GET 'change_gradebook_version'" do
|
||||
|
|
|
@ -9,8 +9,6 @@ RSpec.describe GradingPeriodSetsController, type: :controller do
|
|||
let(:valid_session) { {} }
|
||||
|
||||
before do
|
||||
root_account.allow_feature!(:multiple_grading_periods)
|
||||
root_account.enable_feature!(:multiple_grading_periods)
|
||||
request.accept = 'application/json'
|
||||
@root_user = root_account.users.create! do |user|
|
||||
user.accept_terms
|
||||
|
@ -44,7 +42,7 @@ RSpec.describe GradingPeriodSetsController, type: :controller do
|
|||
post :create, {
|
||||
account_id: root_account.to_param,
|
||||
enrollment_term_ids: [enrollment_term.to_param],
|
||||
grading_period_set: group_helper.valid_attributes
|
||||
grading_period_set: group_helper.valid_attributes(weighted: true)
|
||||
}, valid_session
|
||||
end
|
||||
|
||||
|
@ -58,6 +56,7 @@ RSpec.describe GradingPeriodSetsController, type: :controller do
|
|||
set_json = json_parse.fetch('grading_period_set')
|
||||
expect(response.status).to eql Rack::Utils.status_code(:created)
|
||||
expect(set_json["title"]).to eql group_helper.valid_attributes[:title]
|
||||
expect(set_json["weighted"]).to be true
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -87,7 +86,7 @@ RSpec.describe GradingPeriodSetsController, type: :controller do
|
|||
end
|
||||
|
||||
describe "PATCH #update" do
|
||||
let(:new_attributes) { { title: 'An updated title!' } }
|
||||
let(:new_attributes) { { title: 'An updated title!', weighted: false } }
|
||||
let(:grading_period_set) { group_helper.create_for_account(root_account) }
|
||||
|
||||
context "with valid params" do
|
||||
|
@ -104,12 +103,26 @@ RSpec.describe GradingPeriodSetsController, type: :controller do
|
|||
patch_update
|
||||
grading_period_set.reload
|
||||
expect(grading_period_set.title).to eql new_attributes.fetch(:title)
|
||||
expect(grading_period_set.weighted).to eql new_attributes.fetch(:weighted)
|
||||
end
|
||||
|
||||
it "returns no content" do
|
||||
patch_update
|
||||
expect(response.status).to eql Rack::Utils.status_code(:no_content)
|
||||
end
|
||||
|
||||
it 'recomputes grades when an enrollment term is removed from the set' do
|
||||
term = root_account.enrollment_terms.create!
|
||||
root_account.courses.create!(enrollment_term: term)
|
||||
grading_period_set.enrollment_terms << term
|
||||
Enrollment.expects(:recompute_final_score).once
|
||||
patch :update, {
|
||||
account_id: root_account.to_param,
|
||||
id: grading_period_set.to_param,
|
||||
enrollment_term_ids: [],
|
||||
grading_period_set: new_attributes
|
||||
}, valid_session
|
||||
end
|
||||
end
|
||||
|
||||
it "defaults enrollment_term_ids to empty array" do
|
||||
|
|
|
@ -61,7 +61,6 @@ describe GradingPeriodsController do
|
|||
before do
|
||||
account_admin_user(account: root_account)
|
||||
user_session(@admin)
|
||||
root_account.enable_feature!(:multiple_grading_periods)
|
||||
request.accept = 'application/json'
|
||||
end
|
||||
|
||||
|
@ -76,65 +75,16 @@ describe GradingPeriodsController do
|
|||
|
||||
describe 'with root account admins' do
|
||||
it 'disallows creating grading periods' do
|
||||
root_account.enable_feature!(:multiple_grading_periods)
|
||||
get :index, { course_id: course.id }
|
||||
expect(json_parse['can_create_grading_periods']).to eql(false)
|
||||
end
|
||||
|
||||
it 'allows toggling grading periods when multiple grading periods are enabled' do
|
||||
root_account.enable_feature!(:multiple_grading_periods)
|
||||
get :index, { course_id: course.id }
|
||||
expect(json_parse['can_toggle_grading_periods']).to eql(true)
|
||||
end
|
||||
|
||||
it "returns 'not found' when multiple grading periods are allowed" do
|
||||
root_account.allow_feature!(:multiple_grading_periods)
|
||||
get :index, { course_id: course.id }
|
||||
expect(response).to be_not_found
|
||||
end
|
||||
|
||||
it "returns 'not found' when multiple grading periods are disabled" do
|
||||
root_account.disable_feature!(:multiple_grading_periods)
|
||||
get :index, { course_id: course.id }
|
||||
expect(response).to be_not_found
|
||||
expect(json_parse['can_create_grading_periods']).to be false
|
||||
end
|
||||
end
|
||||
|
||||
describe 'with sub account admins' do
|
||||
it 'disallows creating grading periods' do
|
||||
root_account.enable_feature!(:multiple_grading_periods)
|
||||
login_sub_account
|
||||
get :index, { course_id: course.id }
|
||||
expect(json_parse['can_create_grading_periods']).to eql(false)
|
||||
end
|
||||
|
||||
it 'disallows toggling grading periods when multiple grading periods are root account enabled' do
|
||||
root_account.enable_feature!(:multiple_grading_periods)
|
||||
login_sub_account
|
||||
get :index, { course_id: course.id }
|
||||
expect(json_parse['can_toggle_grading_periods']).to eql(false)
|
||||
end
|
||||
|
||||
it "returns 'not found' when multiple grading periods are root account disabled" do
|
||||
root_account.disable_feature!(:multiple_grading_periods)
|
||||
login_sub_account
|
||||
get :index, { course_id: course.id }
|
||||
expect(response).to be_not_found
|
||||
end
|
||||
|
||||
it "returns 'not found' when multiple grading periods are allowed" do
|
||||
root_account.allow_feature!(:multiple_grading_periods)
|
||||
login_sub_account
|
||||
get :index, { course_id: course.id }
|
||||
expect(response).to be_not_found
|
||||
end
|
||||
|
||||
it "returns 'not found' when multiple grading periods are sub account disabled" do
|
||||
root_account.allow_feature!(:multiple_grading_periods)
|
||||
sub_account.disable_feature!(:multiple_grading_periods)
|
||||
login_sub_account
|
||||
get :index, { course_id: course.id }
|
||||
expect(response).to be_not_found
|
||||
expect(json_parse['can_create_grading_periods']).to be false
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -186,7 +186,7 @@ describe Quizzes::QuizzesController do
|
|||
describe "GET 'edit'" do
|
||||
before(:once) { course_quiz }
|
||||
|
||||
include_context "multiple grading periods within controller" do
|
||||
include_context "grading periods within controller" do
|
||||
let(:course) { @course }
|
||||
let(:teacher) { @teacher }
|
||||
let(:request_params) { [:edit, course_id: course, id: @quiz] }
|
||||
|
@ -1066,7 +1066,7 @@ describe Quizzes::QuizzesController do
|
|||
expect(@student.recent_stream_items.map {|item| item.data['notification_id']}).not_to include notification.id
|
||||
end
|
||||
|
||||
context "with multiple grading periods enabled" do
|
||||
context "with grading periods" do
|
||||
def call_create(params)
|
||||
post('create', course_id: @course.id, quiz: {
|
||||
title: "Example Quiz", quiz_type: "assignment"
|
||||
|
@ -1077,7 +1077,6 @@ describe Quizzes::QuizzesController do
|
|||
|
||||
before :once do
|
||||
teacher_in_course(active_all: true)
|
||||
@course.root_account.enable_feature!(:multiple_grading_periods)
|
||||
grading_period_group = Factories::GradingPeriodGroupHelper.new.create_for_account(@course.root_account)
|
||||
term = @course.enrollment_term
|
||||
term.grading_period_group = grading_period_group
|
||||
|
@ -1420,7 +1419,7 @@ describe Quizzes::QuizzesController do
|
|||
end
|
||||
end
|
||||
|
||||
context "with multiple grading periods enabled" do
|
||||
context "with grading periods" do
|
||||
def create_quiz(attr)
|
||||
@course.quizzes.create!({ title: "Example Quiz", quiz_type: "assignment" }.merge(attr))
|
||||
end
|
||||
|
@ -1443,7 +1442,6 @@ describe Quizzes::QuizzesController do
|
|||
|
||||
before :once do
|
||||
teacher_in_course(active_all: true)
|
||||
@course.root_account.enable_feature!(:multiple_grading_periods)
|
||||
grading_period_group = Factories::GradingPeriodGroupHelper.new.create_for_account(@course.root_account)
|
||||
term = @course.enrollment_term
|
||||
term.grading_period_group = grading_period_group
|
||||
|
|
|
@ -607,7 +607,6 @@ describe UsersController do
|
|||
describe "GET 'grades_for_student'" do
|
||||
let(:test_course) do
|
||||
test_course = course_factory(active_all: true)
|
||||
test_course.root_account.enable_feature!(:multiple_grading_periods)
|
||||
test_course
|
||||
end
|
||||
let(:student) { user_factory(active_all: true) }
|
||||
|
@ -632,13 +631,13 @@ describe UsersController do
|
|||
end
|
||||
|
||||
context "as a student" do
|
||||
it "returns the grade and the total for the student, filtered by the grading period" do
|
||||
it "returns the grade for the student, filtered by the grading period" do
|
||||
user_session(student)
|
||||
get('grades_for_student', grading_period_id: grading_period.id,
|
||||
enrollment_id: student_enrollment.id)
|
||||
|
||||
expect(response).to be_ok
|
||||
expected_response = {'grade' => 40, 'total' => 4, 'possible' => 10, 'hide_final_grades' => false}
|
||||
expected_response = {'grade' => 40.0, 'hide_final_grades' => false}
|
||||
expect(json_parse(response.body)).to eq expected_response
|
||||
|
||||
grading_period.end_date = 4.months.from_now
|
||||
|
@ -648,7 +647,7 @@ describe UsersController do
|
|||
enrollment_id: student_enrollment.id)
|
||||
|
||||
expect(response).to be_ok
|
||||
expected_response = {'grade' => 94.55, 'total' => 104, 'possible' => 110, 'hide_final_grades' => false}
|
||||
expected_response = {'grade' => 94.55, 'hide_final_grades' => false}
|
||||
expect(json_parse(response.body)).to eq expected_response
|
||||
end
|
||||
|
||||
|
@ -660,7 +659,7 @@ describe UsersController do
|
|||
enrollment_id: student_enrollment.id)
|
||||
|
||||
expect(response).to be_ok
|
||||
expected_response = {'grade' => 94.55, 'total' => 104, 'possible' => 110, 'hide_final_grades' => false}
|
||||
expected_response = {'grade' => 94.55, 'hide_final_grades' => false}
|
||||
expect(json_parse(response.body)).to eq expected_response
|
||||
end
|
||||
|
||||
|
@ -686,7 +685,7 @@ describe UsersController do
|
|||
grading_period_id: grading_period.id)
|
||||
|
||||
expect(response).to be_ok
|
||||
expected_response = {'grade' => 40, 'total' => 4, 'possible' => 10, 'hide_final_grades' => false}
|
||||
expected_response = {'grade' => 40.0, 'hide_final_grades' => false}
|
||||
expect(json_parse(response.body)).to eq expected_response
|
||||
|
||||
grading_period.end_date = 4.months.from_now
|
||||
|
@ -696,7 +695,7 @@ describe UsersController do
|
|||
enrollment_id: student_enrollment.id)
|
||||
|
||||
expect(response).to be_ok
|
||||
expected_response = {'grade' => 94.55, 'total' => 104, 'possible' => 110, 'hide_final_grades' => false}
|
||||
expected_response = {'grade' => 94.55, 'hide_final_grades' => false}
|
||||
expect(json_parse(response.body)).to eq expected_response
|
||||
end
|
||||
|
||||
|
@ -709,7 +708,7 @@ describe UsersController do
|
|||
enrollment_id: student_enrollment.id)
|
||||
|
||||
expect(response).to be_ok
|
||||
expected_response = {'grade' => 94.55, 'total' => 104, 'possible' => 110, 'hide_final_grades' => false}
|
||||
expected_response = {'grade' => 94.55, 'hide_final_grades' => false}
|
||||
expect(json_parse(response.body)).to eq expected_response
|
||||
end
|
||||
|
||||
|
@ -746,29 +745,7 @@ describe UsersController do
|
|||
observer
|
||||
end
|
||||
|
||||
context "with Multiple Grading periods disabled" do
|
||||
it "returns grades of observees" do
|
||||
user_session(observer)
|
||||
get 'grades'
|
||||
|
||||
grades = assigns[:grades][:observed_enrollments][test_course.id]
|
||||
expect(grades.length).to eq 2
|
||||
expect(grades.key?(student1.id)).to eq true
|
||||
expect(grades.key?(student2.id)).to eq true
|
||||
end
|
||||
|
||||
it "returns an empty hash for grading periods" do
|
||||
user_session(observer)
|
||||
get 'grades'
|
||||
|
||||
grading_periods = assigns[:grading_periods]
|
||||
expect(grading_periods).to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
context "with Multiple Grading Periods enabled" do
|
||||
before(:once) { course_factory.root_account.enable_feature!(:multiple_grading_periods) }
|
||||
|
||||
context "with grading periods" do
|
||||
it "returns the grading periods" do
|
||||
user_session(observer)
|
||||
get 'grades'
|
||||
|
@ -817,30 +794,8 @@ describe UsersController do
|
|||
course_with_user('StudentEnrollment', course: another_test_course, user: student, active_all: true)
|
||||
student
|
||||
end
|
||||
context "with Multiple Grading periods disabled" do
|
||||
it "returns grades" do
|
||||
user_session(test_student)
|
||||
get 'grades'
|
||||
|
||||
grades = assigns[:grades][:student_enrollments]
|
||||
|
||||
expect(grades.length).to eq 2
|
||||
expect(grades.key?(test_course.id)).to eq true
|
||||
expect(grades.key?(another_test_course.id)).to eq true
|
||||
end
|
||||
|
||||
it "returns an empty hash for grading periods" do
|
||||
user_session(test_student)
|
||||
get 'grades'
|
||||
|
||||
grading_periods = assigns[:grading_periods]
|
||||
expect(grading_periods).to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
context "with Multiple Grading Periods enabled" do
|
||||
before(:once) { course_factory.root_account.enable_feature!(:multiple_grading_periods) }
|
||||
|
||||
context "with grading periods" do
|
||||
it "returns the grading periods" do
|
||||
user_session(test_student)
|
||||
get 'grades'
|
||||
|
@ -859,6 +814,18 @@ describe UsersController do
|
|||
expect(selected_period_id).to eq grading_period.global_id
|
||||
end
|
||||
|
||||
it "returns the grade for the current grading period, if one exists " \
|
||||
"and no grading period is passed in" do
|
||||
assignment = test_course.assignments.create!(
|
||||
due_at: 3.days.from_now(grading_period.end_date),
|
||||
points_possible: 10
|
||||
)
|
||||
assignment.grade_student(test_student, grader: test_course.teachers.first, grade: 10)
|
||||
user_session(test_student)
|
||||
get :grades
|
||||
expect(assigns[:grades][:student_enrollments][test_course.id]).to be_nil
|
||||
end
|
||||
|
||||
it "returns 0 (signifying 'All Grading Periods') if no current " \
|
||||
"grading period exists and no grading period parameter is passed in" do
|
||||
grading_period.start_date = 1.month.from_now
|
||||
|
@ -870,6 +837,19 @@ describe UsersController do
|
|||
expect(selected_period_id).to eq 0
|
||||
end
|
||||
|
||||
it "returns the grade for 'All Grading Periods' if no current " \
|
||||
"grading period exists and no grading period is passed in" do
|
||||
grading_period.update!(start_date: 1.month.from_now)
|
||||
assignment = test_course.assignments.create!(
|
||||
due_at: 3.days.from_now(grading_period.end_date),
|
||||
points_possible: 10
|
||||
)
|
||||
assignment.grade_student(test_student, grader: test_course.teachers.first, grade: 10)
|
||||
user_session(test_student)
|
||||
get :grades
|
||||
expect(assigns[:grades][:student_enrollments][test_course.id]).to eq(100.0)
|
||||
end
|
||||
|
||||
it "returns the grading_period_id passed in, if one is provided along with a course_id" do
|
||||
user_session(test_student)
|
||||
get 'grades', course_id: test_course.id, grading_period_id: 2939
|
||||
|
@ -885,7 +865,6 @@ describe UsersController do
|
|||
course_with_user('StudentEnrollment', course: test_course, user: student1, active_all: true)
|
||||
@shard1.activate do
|
||||
account = Account.create!
|
||||
account.enable_feature!(:multiple_grading_periods)
|
||||
@course2 = course_factory(active_all: true, account: account)
|
||||
course_with_user('StudentEnrollment', course: @course2, user: student1, active_all: true)
|
||||
grading_period_group2 = group_helper.legacy_create_for_course(@course2)
|
||||
|
|
|
@ -16,12 +16,11 @@
|
|||
# with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
# this factory creates an Account with the multiple_grading_periods feature flag enabled.
|
||||
# this factory creates an Account with n grading periods.
|
||||
# it also creates two grading periods for the account
|
||||
# the grading_periods both have a weight of 1
|
||||
module Factories
|
||||
def grading_periods(options = {})
|
||||
Account.default.set_feature_flag! :multiple_grading_periods, 'on'
|
||||
course = options[:context] || @course || course_factory()
|
||||
count = options[:count] || 2
|
||||
|
||||
|
@ -38,10 +37,7 @@ module Factories
|
|||
end
|
||||
|
||||
def create_grading_periods_for(course, opts={})
|
||||
opts = { mgp_flag_enabled: true }.merge(opts)
|
||||
course.root_account = Account.default if !course.root_account
|
||||
course.root_account.enable_feature!(:multiple_grading_periods) if opts[:mgp_flag_enabled]
|
||||
|
||||
course.root_account = Account.default unless course.root_account
|
||||
gp_group = Factories::GradingPeriodGroupHelper.new.legacy_create_for_course(course)
|
||||
class_name = course.class.name.demodulize
|
||||
timeframes = opts[:grading_periods] || [:current]
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue