mobile: add grading period scores to endpoints

adds support for a 'current_grading_period_scores'
argument that can be passed to api/v1/courses,
api/v1/courses/:id, api/v1/users/:user_id/courses,
and api/v1/users/self/favorites/courses. if this
argument is passed to these endpoints, scores for
the current grading period will be returned.

closes CNVS-26203

test plan:
 - run
     $ bundle exec rake doc:api
   to generate API documentation.
 - view the documentation at
   public/doc/api/index.html and navigate to the
   documentation for the Courses controller.
 - verify there is documentation explaining how
   to pass the 'current_grading_period_scores'
   argument, and explaining what will be returned.
 - hit the api/v1/courses, api/v1/courses/:id,
   api/v1/users/:user_id/courses, and
   api/v1/users/self/favorites/courses endpoints
   and pass in 'current_grading_period_scores'
   as an include[] argument. verify you get the
   expected responses back, as outlined in the
   API documentation for 'current_grading_period
   _scores'.
 - ping me with any questions :)

Change-Id: Ic8985c6a85af120b9d07ef2cd4e8e049e0c327fc
Reviewed-on: https://gerrit.instructure.com/69836
Tested-by: Jenkins
Reviewed-by: Derek Bender <djbender@instructure.com>
QA-Review: Amber Taniuchi <amber@instructure.com>
Product-Review: Keith T. Garner <kgarner@instructure.com>
This commit is contained in:
Spencer Olson 2016-01-06 10:41:51 -06:00 committed by Robert Lamb
parent a506ec9245
commit 2658d2c05c
11 changed files with 440 additions and 125 deletions

View File

@ -318,7 +318,7 @@ class CoursesController < ApplicationController
# 'StudentEnrollment', 'TeacherEnrollment', 'TaEnrollment', 'ObserverEnrollment',
# or 'DesignerEnrollment'.
#
# @argument include[] [String, "needs_grading_count"|"syllabus_body"|"total_scores"|"term"|"course_progress"|"sections"|"storage_quota_used_mb"|"total_students"|"favorites"|"teachers"|"observed_users"]
# @argument include[] [String, "needs_grading_count"|"syllabus_body"|"total_scores"|"term"|"course_progress"|"sections"|"storage_quota_used_mb"|"total_students"|"favorites"|"teachers"|"observed_users"|"current_grading_period_scores"]
# - "needs_grading_count": Optional information to include with each Course.
# When needs_grading_count is given, and the current user has grading
# rights, the total number of submissions needing grading for all
@ -327,16 +327,23 @@ class CoursesController < ApplicationController
# When syllabus_body is given the user-generated html for the course
# syllabus is returned.
# - "total_scores": Optional information to include with each Course.
# When total_scores is given, any enrollments with type 'student' will also
# include the fields 'calculated_current_score', 'calculated_final_score',
# 'calculated_current_grade', and 'calculated_final_grade'.
# calculated_current_score is the student's score in the course, ignoring
# ungraded assignments. calculated_final_score is the student's score in
# the course including ungraded assignments with a score of 0.
# calculated_current_grade is the letter grade equivalent of
# calculated_current_score (if available). calculated_final_grade is the
# letter grade equivalent of calculated_final_score (if available). This
# argument is ignored if the course is configured to hide final grades.
# When total_scores is given, any student enrollments will also
# include the fields 'computed_current_score', 'computed_final_score',
# 'computed_current_grade', and 'computed_final_grade' (see Enrollment
# documentation for more information on these fields). This argument
# is ignored if the course is configured to hide final grades.
# - "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',
# 'totals_for_all_grading_periods_option', 'current_grading_period_title',
# '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). 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
# term is given, the information for the enrollment term for each course
# is returned.
@ -417,7 +424,7 @@ class CoursesController < ApplicationController
# @API List courses for a user
# Returns a list of active courses for this user. To view the course list for a user other than yourself, you must be either an observer of that user or an administrator.
#
# @argument include[] [String, "needs_grading_count"|"syllabus_body"|"total_scores"|"term"|"course_progress"|"sections"|"storage_quota_used_mb"|"total_students"|"favorites"]
# @argument include[] [String, "needs_grading_count"|"syllabus_body"|"total_scores"|"term"|"course_progress"|"sections"|"storage_quota_used_mb"|"total_students"|"favorites"|"current_grading_period_scores"]
# - "needs_grading_count": Optional information to include with each Course.
# When needs_grading_count is given, and the current user has grading
# rights, the total number of submissions needing grading for all
@ -426,16 +433,23 @@ class CoursesController < ApplicationController
# When syllabus_body is given the user-generated html for the course
# syllabus is returned.
# - "total_scores": Optional information to include with each Course.
# When total_scores is given, any enrollments with type 'student' will also
# include the fields 'calculated_current_score', 'calculated_final_score',
# 'calculated_current_grade', and 'calculated_final_grade'.
# calculated_current_score is the student's score in the course, ignoring
# ungraded assignments. calculated_final_score is the student's score in
# the course including ungraded assignments with a score of 0.
# calculated_current_grade is the letter grade equivalent of
# calculated_current_score (if available). calculated_final_grade is the
# letter grade equivalent of calculated_final_score (if available). This
# argument is ignored if the course is configured to hide final grades.
# When total_scores is given, any student enrollments will also
# include the fields 'computed_current_score', 'computed_final_score',
# 'computed_current_grade', and 'computed_final_grade' (see Enrollment
# documentation for more information on these fields). This argument
# is ignored if the course is configured to hide final grades.
# - "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',
# 'totals_for_all_grading_periods_option', 'current_grading_period_title',
# '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). 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
# term is given, the information for the enrollment term for each course
# is returned.
@ -2418,10 +2432,12 @@ class CoursesController < ApplicationController
Canvas::Builders::EnrollmentDateBuilder.preload(enrollments)
enrollments_by_course = enrollments.group_by(&:course_id).values
enrollments_by_course = Api.paginate(enrollments_by_course, self, api_v1_courses_url) if api_request?
if includes.include?("teachers")
courses = enrollments_by_course.map(&:first).map(&:course)
ActiveRecord::Associations::Preloader.new.preload(courses, :teachers)
end
preloads = [:account]
preloads << :teachers if includes.include?('teachers')
preloads << :grading_standard if includes.include?('total_scores')
ActiveRecord::Associations::Preloader.new.preload(courses, preloads)
enrollments_by_course.each do |course_enrollments|
course = course_enrollments.first.course
hash << course_json(course, user, session, includes, course_enrollments)

View File

@ -164,12 +164,63 @@
# "type": "string"
# },
# "grades": {
# "description": "The URL to the Canvas web UI page the grades associated with this enrollment.",
# "description": "The URL to the Canvas web UI page containing the grades associated with this enrollment.",
# "$ref": "Grade"
# },
# "user": {
# "description": "A description of the user.",
# "type": "User"
# "computed_current_score": {
# "description": "optional: The student's score in the course, ignoring ungraded assignments. (applies only to student enrollments, and only available in course endpoints)",
# "example": 90.25,
# "type": "float"
# },
# "computed_final_score": {
# "description": "optional: The student's score in the course including ungraded assignments with a score of 0. (applies only to student enrollments, and only available in course endpoints)",
# "example": 80.67,
# "type": "float"
# },
# "computed_current_grade": {
# "description": "optional: The letter grade equivalent of computed_current_score, if available. (applies only to student enrollments, and only available in course endpoints)",
# "example": "A-",
# "type": "string"
# },
# "computed_final_grade": {
# "description": "optional: The letter grade equivalent of computed_final_score, if available. (applies only to student enrollments, and only available in course endpoints)",
# "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)",
# "example": true,
# "type": "boolean"
# },
# "totals_for_all_grading_periods_option": {
# "description": "optional: Indicates whether the course the enrollment belongs to has the Display Totals for 'All Grading Periods' feature enabled. (applies only to student enrollments, and only available in course endpoints)",
# "example": true,
# "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)",
# "example": "Fall Grading Period",
# "type": "string"
# },
# "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)",
# "example": 95.80,
# "type": "float"
# },
# "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)",
# "example": 85.25,
# "type": "float"
# },
# "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)",
# "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)",
# "example": "B",
# "type": "string"
# }
# }
# }

View File

@ -240,7 +240,7 @@ class GradebooksController < ApplicationController
@current_grading_period_id = params[:grading_period_id].to_i
else
return if view_all_grading_periods?
current = GradingPeriod.for(@context).find(&:current?)
current = GradingPeriod.current_period_for(@context)
@current_grading_period_id = current ? current.id : 0
end
end

View File

@ -18,6 +18,7 @@
module Assignment::FilterWithOverridesByDueAt
def filter_assignments
ActiveRecord::Associations::Preloader.new.preload(assignments, :assignment_overrides)
assignments.select { |assignment| in_grading_period?(assignment) }
end
@ -51,7 +52,11 @@ module Assignment::FilterWithOverridesByDueAt
end
end
def any_active_overrides?(assignment)
active_overrides(assignment).any?
end
def no_active_overrides?(assignment)
active_overrides(assignment).none?
!any_active_overrides?(assignment)
end
end

View File

@ -34,7 +34,7 @@ class Assignment::FilterWithOverridesByDueAtForClass
end
def find_due_at(assignment)
due_at = no_active_overrides?(assignment) ? assignment.due_at : filter_date_from_overrides(assignment)
due_at = any_active_overrides?(assignment) ? filter_date_from_overrides(assignment) : assignment.due_at
milliseconds_to_zero(due_at)
end

View File

@ -50,7 +50,8 @@ class Assignment::FilterWithOverridesByDueAtForStudent
def most_lenient_due_at(assignment)
date_to_use = assignment.due_at if assigned_to_everyone_else?(assignment)
if assignment.has_active_overrides?
if any_active_overrides?(assignment)
override_dates = override_dates_for_student(assignment)
return nil if override_dates.any?(&:nil?)

View File

@ -53,6 +53,10 @@ class GradingPeriod < ActiveRecord::Base
.grading_periods
end
def self.current_period_for(context)
self.for(context).find(&:current?)
end
# Takes a context and a grading_period_id and returns a grading period
# if it is in the for collection. Uses Enumberable#find to query
# collection.

View File

@ -83,26 +83,9 @@ module Api::V1
end
def extract_enrollments(enrollments)
if enrollments
enrollments.map do |e|
h = {
:type => e.sis_type,
:role => e.role.name,
:role_id => e.role.id,
:user_id => e.user_id,
:enrollment_state => e.workflow_state
}
h[:associated_user_id] = e.associated_user_id if e.assigned_observer?
if include_total_scores && e.student?
h.merge!(
:computed_current_score => e.computed_current_score,
:computed_final_score => e.computed_final_score,
:computed_current_grade => e.computed_current_grade,
:computed_final_grade => e.computed_final_grade)
end
h
end
end
return unless enrollments
current_period_scores = grading_period_scores_hash(enrollments)
enrollments.map { |e| enrollment_hash(e, current_period_scores) }
end
INCLUDE_CHECKERS.each do |key, val|
@ -111,8 +94,106 @@ module Api::V1
end
end
def include_total_scores
def include_total_scores?
@includes.include?(:total_scores) && !@course.hide_final_grades?
end
private
def enrollment_hash(enrollment, grading_period_scores)
enrollment_hash = default_enrollment_attributes(enrollment)
enrollment_hash[:associated_user_id] = enrollment.associated_user_id if enrollment.assigned_observer?
if include_total_scores? && enrollment.student?
enrollment_hash.merge!(total_scores(enrollment))
enrollment_hash.merge!(grading_period_scores[enrollment.id]) if include_current_grading_period_scores?
end
enrollment_hash
end
def default_enrollment_attributes(enrollment)
{
:type => enrollment.sis_type,
:role => enrollment.role.name,
:role_id => enrollment.role.id,
:user_id => enrollment.user_id,
:enrollment_state => enrollment.workflow_state
}
end
def total_scores(student_enrollment)
{
:computed_current_score => student_enrollment.computed_current_score,
:computed_final_score => student_enrollment.computed_final_score,
:computed_current_grade => student_enrollment.computed_current_grade,
:computed_final_grade => student_enrollment.computed_final_grade
}
end
def grading_period_scores(student_enrollments)
mgp_enabled = @course.feature_enabled?(:multiple_grading_periods)
totals_for_all_grading_periods_option = mgp_enabled &&
@course.feature_enabled?(:all_grading_periods_totals)
current_period = mgp_enabled && GradingPeriod.current_period_for(@course)
if mgp_enabled && 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)
end
end
def grading_period_scores_hash(enrollments)
include_current_grading_period_scores? ? grading_period_scores(enrollments.select(&:student?)) : {}
end
def calculated_grading_period_scores(student_enrollments, current_period, totals_for_all_grading_periods_option)
calculator = GradeCalculator.new(
student_enrollments.map(&:user_id), @course, grading_period: current_period
)
current_period_scores = mgp_scores_from_calculator(calculator)
scores = {}
student_enrollments.each_with_index do |enrollment, index|
scores[enrollment.id] = current_period_scores[index].merge({
multiple_grading_periods_enabled: true,
totals_for_all_grading_periods_option: totals_for_all_grading_periods_option,
current_grading_period_title: current_period.title
})
end
scores
end
def nil_grading_period_scores(student_enrollments, mgp_enabled, totals_for_all_grading_periods_option)
scores = {}
student_enrollments.each do |enrollment|
scores[enrollment.id] = {
multiple_grading_periods_enabled: mgp_enabled,
totals_for_all_grading_periods_option: totals_for_all_grading_periods_option,
current_grading_period_title: nil,
current_period_computed_current_score: nil,
current_period_computed_final_score: nil,
current_period_computed_current_grade: nil,
current_period_computed_final_grade: nil
}
end
scores
end
def mgp_scores_from_calculator(grade_calculator)
grade_calculator.compute_scores.map do |scores|
current_score = scores.first.first[:grade]
final_score = scores.second.first[:grade]
{
current_period_computed_current_score: current_score,
current_period_computed_final_score: final_score,
current_period_computed_current_grade: @course.score_to_grade(current_score),
current_period_computed_final_grade: @course.score_to_grade(final_score)
}
end
end
def include_current_grading_period_scores?
include_total_scores? && @includes.include?(:current_grading_period_scores)
end
end
end

View File

@ -28,8 +28,8 @@ module Api
end
describe '#include_total_scores' do
let(:predicate) { course_json.include_total_scores }
describe '#include_total_scores?' do
let(:predicate) { course_json.include_total_scores? }
let(:course_settings) { Hash.new }
let(:course) { stub( course_settings ) }

View File

@ -30,7 +30,6 @@ class TestCourseApi
end
describe Api::V1::Course do
describe '#course_json' do
before :once do
@test_api = TestCourseApi.new
@ -49,33 +48,33 @@ describe Api::V1::Course do
expect(@test_api.course_json(@course1, @me, {}, ['html_url'], [])).to encompass({
"html_url" => "course_url(Course.find(#{@course1.id}), :host => #{HostUrl.context_host(@course1)})"
})
expect(@test_api.course_json(@course1, @me, {}, [], []).has_key?("html_url")).to be_falsey
expect(@test_api.course_json(@course1, @me, {}, [], [])).to_not include 'html_url'
end
it 'should only include needs_grading_count if requested' do
expect(@test_api.course_json(@course1, @me, {}, [], [teacher_enrollment]).has_key?("needs_grading_count")).to be_falsey
expect(@test_api.course_json(@course1, @me, {}, [], [teacher_enrollment])).to_not include 'needs_grading_count'
end
it 'should only include is_favorite if requested' do
expect(@test_api.course_json(@course1, @me, {}, ['favorites'], [teacher_enrollment]).key?('is_favorite')).to be_truthy
expect(@test_api.course_json(@course1, @me, {}, ['favorites'], [teacher_enrollment])).to include 'is_favorite'
end
it 'should honor needs_grading_count for teachers' do
expect(@test_api.course_json(@course1, @me, {}, ['needs_grading_count'], [teacher_enrollment]).has_key?("needs_grading_count")).to be_truthy
expect(@test_api.course_json(@course1, @me, {}, ['needs_grading_count'], [teacher_enrollment])).to include "needs_grading_count"
end
it 'should return storage_quota_used_mb if requested' do
expect(@test_api.course_json(@course1, @me, {}, ['storage_quota_used_mb'], [teacher_enrollment]).has_key?("storage_quota_used_mb")).to be_truthy
expect(@test_api.course_json(@course1, @me, {}, ['storage_quota_used_mb'], [teacher_enrollment])).to include "storage_quota_used_mb"
end
it 'should not honor needs_grading_count for designers' do
@designer_enrollment = @course1.enroll_designer(@me)
@designer_enrollment.accept!
expect(@test_api.course_json(@course1, @me, {}, ['needs_grading_count'], [@designer_enrollment]).has_key?("needs_grading_count")).to be_falsey
expect(@test_api.course_json(@course1, @me, {}, ['needs_grading_count'], [@designer_enrollment])).to_not include "needs_grading_count"
end
it 'should include apply_assignment_group_weights' do
expect(@test_api.course_json(@course1, @me, {}, [], []).has_key?("apply_assignment_group_weights")).to be_truthy
expect(@test_api.course_json(@course1, @me, {}, [], [])).to include "apply_assignment_group_weights"
end
it "should not show details if user is restricted from access by course dates" do
@ -401,7 +400,6 @@ describe CoursesController, type: :request do
api_call_as_user(parent,:get,"/api/v1/users/#{@me.id}/courses",
{ :user_id => @me.id, :controller => 'courses', :action => 'user_index',
:format => 'json' }, {}, {}, {:expected_status => 401})
end
it "should return courses from observed user's shard if different than observer" do
@ -437,10 +435,8 @@ describe CoursesController, type: :request do
course_ids = json.select{ |c| c["id"]}
expect(course_ids.length).to eq 2
end
end
it 'should paginate the course list' do
json = api_call(:get, "/api/v1/courses.json?per_page=1",
{ :controller => 'courses', :action => 'index', :format => 'json', :per_page => '1' })
@ -457,7 +453,7 @@ describe CoursesController, type: :request do
expect(json.length).to eq 2
courses = json.select { |c| c.has_key?("permissions") }
courses = json.select { |c| c.key?("permissions") }
expect(courses.length).to eq 0
end
@ -1388,57 +1384,197 @@ describe CoursesController, type: :request do
json.each { |course| expect(course['public_syllabus']).to be_truthy }
end
it "should include scores in course list if requested" do
describe "scores" do
before(:once) do
@course2.grading_standard_enabled = true
@course2.save
expected_current_score = 80
expected_final_score = 70
expected_final_grade = @course2.score_to_grade(expected_final_score)
@course2.all_student_enrollments.update_all(
:computed_current_score => expected_current_score,
:computed_final_score => expected_final_score)
end
json = api_call(:get, "/api/v1/courses.json",
{ :controller => 'courses', :action => 'index', :format => 'json' },
{ :include => ['total_scores'] })
# course2 (only care about student)
courses = json.select { |c| c['id'] == @course2.id }
expect(courses.length).to eq 1
expect(courses[0]).to include('enrollments')
expect(courses[0]['enrollments'].length).to eq 1
expect(courses[0]['enrollments'][0]).to include(
'type' => 'student',
'computed_current_score' => expected_current_score,
'computed_final_score' => expected_final_score,
'computed_final_grade' => expected_final_grade,
def courses_api_index_call(includes: ['total_scores'])
api_call(
:get, "/api/v1/courses.json",
{ controller: 'courses', action: 'index', format: 'json' },
{ include: includes }
)
end
it "should not include scores in course list, even if requested, if final grades are hidden" do
@course2.grading_standard_enabled = true
def enrollment(json_response)
course2 = json_response.find { |course| course['id'] == @course2.id }
course2['enrollments'].first
end
context "include total scores" do
before(:once) do
@course2.all_student_enrollments.update_all(
computed_current_score: 80,
computed_final_score: 70
)
end
it "includes scores in course list if requested" do
json_response = courses_api_index_call
expect(enrollment(json_response)).to include(
'type' => 'student',
'computed_current_score' => 80,
'computed_final_score' => 70,
'computed_final_grade' => @course2.score_to_grade(70)
)
end
it "does not include scores in course list, even if requested, if final grades are hidden" do
@course2.hide_final_grades = true
@course2.save
@course2.all_student_enrollments.update_all(:computed_current_score => 80, :computed_final_score => 70)
json = api_call(:get, "/api/v1/courses.json",
{ :controller => 'courses', :action => 'index', :format => 'json' },
{ :include => ['total_scores'] })
# course2 (only care about student)
courses = json.select { |c| c['id'] == @course2.id }
expect(courses.length).to eq 1
expect(courses[0]).to include('enrollments')
expect(courses[0]['enrollments'].length).to eq 1
expect(courses[0]['enrollments'][0]).to include(
'type' => 'student',
)
expect(courses[0]['enrollments'][0]).not_to include(
json_response = courses_api_index_call
enrollment_json = enrollment(json_response)
expect(enrollment_json).to include 'type' => 'student'
expect(enrollment_json).not_to include(
'computed_current_score',
'computed_final_score',
'computed_final_grade',
'computed_final_grade'
)
end
end
context "include current grading period scores" do
let(:grading_period_keys) do
[ 'multiple_grading_periods_enabled',
'totals_for_all_grading_periods_option',
'current_period_computed_current_score',
'current_period_computed_final_score',
'current_period_computed_current_grade',
'current_period_computed_final_grade',
'current_grading_period_title' ]
end
before(:once) do
create_grading_periods_for(
@course2, grading_periods: [:old, :current, :future]
)
end
it "includes current grading period scores if 'total_scores' " \
"and 'current_grading_period_scores' are requested" do
json_response = courses_api_index_call(includes: ['total_scores', 'current_grading_period_scores'])
enrollment_json = enrollment(json_response)
expect(enrollment_json).to include *grading_period_keys
current_grading_period_title = 'Course Period 2: current period'
expect(enrollment_json['current_grading_period_title']).to eq(current_grading_period_title)
end
it "does not include current grading period scores if 'total_scores' are " \
"not requested, even if 'current_grading_period_scores' are requested" do
json_response = courses_api_index_call(includes: ['current_grading_period_scores'])
enrollment_json = enrollment(json_response)
expect(enrollment_json).to_not include *grading_period_keys
end
it "does not include current grading period scores if final grades are hidden, " \
" even if 'total_scores' and 'current_grading_period_scores' are requested" do
@course2.hide_final_grades = true
@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
end
it "returns true for 'multiple_grading_periods_enabled' if the course has Multiple Grading Periods enabled" 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)
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
context "computed scores" do
before(:once) do
assignment_in_current_period = @course2.assignments.create!(
title: "In current grading period - graded",
due_at: 2.days.from_now,
points_possible: 10
)
assignment_in_current_period.grade_student(@student, grader: @teacher, score: 9)
@course2.assignments.create!(
title: "In current grading period - not graded",
due_at: 2.days.from_now,
points_possible: 10
)
end
context "all assignments for the course fall within the current grading period" do
it "current grading period scores match computed scores" do
json_response = courses_api_index_call(includes: ['total_scores', 'current_grading_period_scores'])
enrollment_json = enrollment(json_response)
current_period_current_score = enrollment_json['current_period_computed_current_score']
current_score = enrollment_json['computed_current_score']
expect(current_period_current_score).to eq(current_score)
current_period_final_score = enrollment_json['current_period_computed_final_score']
final_score = enrollment_json['computed_final_score']
expect(current_period_final_score).to eq(final_score)
end
it "current grading period grades match computed grades" do
json_response = courses_api_index_call(includes: ['total_scores', 'current_grading_period_scores'])
enrollment_json = enrollment(json_response)
current_period_current_grade = enrollment_json['current_period_computed_current_grade']
current_grade = enrollment_json['computed_current_grade']
expect(current_period_current_grade).to eq(current_grade)
current_period_final_grade = enrollment_json['current_period_computed_final_grade']
final_grade = enrollment_json['computed_final_grade']
expect(current_period_final_grade).to eq(final_grade)
end
end
context "assignments span across many grading periods" do
before(:once) do
assignment_in_future_grading_period = @course2.assignments.create!(
title: "In future grading period",
due_at: 3.months.from_now,
points_possible: 10
)
assignment_in_future_grading_period.grade_student(@student, grader: @teacher, score: 10)
end
it "current grading period scores and grades do not match computed scores and grades" do
json_response = courses_api_index_call(includes: ['total_scores', 'current_grading_period_scores'])
enrollment_json = enrollment(json_response)
expect(enrollment_json['current_period_computed_current_score'])
.to_not eq(enrollment_json['computed_current_score'])
expect(enrollment_json['current_period_computed_final_score'])
.to_not eq(enrollment_json['computed_final_score'])
expect(enrollment_json['current_period_computed_current_grade'])
.to_not eq(enrollment_json['computed_current_grade'])
expect(enrollment_json['current_period_computed_final_grade'])
.to_not eq(enrollment_json['computed_final_grade'])
end
it "current grading period scores are correct" do
json_response = courses_api_index_call(includes: ['total_scores', 'current_grading_period_scores'])
enrollment_json = enrollment(json_response)
expect(enrollment_json['current_period_computed_current_score']).to eq(90)
expect(enrollment_json['current_period_computed_final_score']).to eq(45)
end
it "current grading period grades are correct" do
json_response = courses_api_index_call(includes: ['total_scores', 'current_grading_period_scores'])
enrollment_json = enrollment(json_response)
expect(enrollment_json['current_period_computed_current_grade']).to eq('A-')
expect(enrollment_json['current_period_computed_final_grade']).to eq('F')
end
end
end
end
end
it "should only return teacher enrolled courses on ?enrollment_type=teacher" do
json = api_call(:get, "/api/v1/courses.json?enrollment_type=teacher",
@ -2424,23 +2560,23 @@ describe CoursesController, type: :request do
it 'should include permissions' do
# Make sure it only returns permissions when asked
json = api_call(:get, "/api/v1/courses/#{@course1.id}.json", { :controller => 'courses', :action => 'show', :id => @course1.to_param, :format => 'json' })
expect(json.has_key?("permissions")).to be_falsey
expect(json).to_not include "permissions"
# When its asked to return permissions make sure they are there
json = api_call(:get, "/api/v1/courses/#{@course1.id}.json?include[]=permissions", { :controller => 'courses', :action => 'show', :id => @course1.to_param, :format => 'json', :include => [ "permissions" ] })
expect(json.has_key?("permissions")).to be_truthy
expect(json).to include "permissions"
end
it 'should include permission create_discussion_topic' do
json = api_call(:get, "/api/v1/courses/#{@course1.id}.json?include[]=permissions", { :controller => 'courses', :action => 'show', :id => @course1.to_param, :format => 'json', :include => [ "permissions" ] })
expect(json.has_key?("permissions")).to be_truthy
expect(json["permissions"].has_key?("create_discussion_topic")).to be_truthy
expect(json).to include "permissions"
expect(json["permissions"]).to include "create_discussion_topic"
end
it 'should include permission create_announcement' do
json = api_call(:get, "/api/v1/courses/#{@course1.id}.json?include[]=permissions", { :controller => 'courses', :action => 'show', :id => @course1.to_param, :format => 'json', :include => [ "permissions" ] })
expect(json.has_key?("permissions")).to be_truthy
expect(json["permissions"].has_key?("create_announcement")).to be_truthy
expect(json).to include "permissions"
expect(json["permissions"]).to include "create_announcement"
expect(json["permissions"]["create_announcement"]).to be_truthy # The setup makes this user a teacher of the course too
end
@ -2529,7 +2665,6 @@ describe CoursesController, type: :request do
end
end
context "course files" do
include_examples "file uploads api with folders"
include_examples "file uploads api with quotas"
@ -2940,5 +3075,4 @@ describe ContentImportsController, type: :request do
expect(json['state']).to eq('completed')
expect(json['issues']).to eq(['mock_issue'])
end
end

View File

@ -74,7 +74,6 @@ describe GradingPeriod do
end
describe ".for" do
context "when context is an account" do
let(:account) { Account.new }
let(:finder) { mock }
@ -98,6 +97,30 @@ describe GradingPeriod do
end
end
describe ".current_period_for" do
let(:account) { Account.new }
let(:not_current_grading_period) { mock }
let(:current_grading_period) { mock }
it "returns the current grading period given a context" do
GradingPeriod.expects(:for).with(account).returns([not_current_grading_period, current_grading_period])
not_current_grading_period.expects(:current?).returns(false)
current_grading_period.expects(:current?).returns(true)
expect(GradingPeriod.current_period_for(account)).to eq(current_grading_period)
end
it "returns nil if grading periods exist for the given context, but none are current" do
GradingPeriod.expects(:for).with(account).returns([not_current_grading_period])
not_current_grading_period.expects(:current?).returns(false)
expect(GradingPeriod.current_period_for(account)).to be_nil
end
it "returns nil if no grading periods exist for the given context" do
GradingPeriod.expects(:for).with(account).returns([])
expect(GradingPeriod.current_period_for(account)).to be_nil
end
end
describe ".context_find" do
let(:account) { mock }
let(:finder) { mock }