jsonapi-ify outcome results api

fixes CNVS-10198, CNVS-10201

test plan
- test outcome result api as in c/27631
- ensure that jsonapi paging metadata is returned

- fetch outcome results for a course that includes students
 without outcome results (i.e. haven't submitted anything)
- ensure that the student is listed in the returned results
 with an empty scores array

Change-Id: I00d8e9de241a243fb6ac1aa9f55150b8955a2452
Reviewed-on: https://gerrit.instructure.com/28015
Tested-by: Jenkins <jenkins@instructure.com>
Reviewed-by: Joel Hough <joel@instructure.com>
QA-Review: Steven Shepherd <sshepherd@instructure.com>
Product-Review: Zach Pendleton <zachp@instructure.com>
Product-Review: Joel Hough <joel@instructure.com>
This commit is contained in:
Joel Hough 2013-12-31 18:01:13 -07:00
parent 9af963c357
commit 0d1c04cbf1
5 changed files with 49 additions and 21 deletions

View File

@ -23,14 +23,15 @@
# #
# @object OutcomeRollupScore # @object OutcomeRollupScore
# { # {
#
# // The id of the related outcome
# "outcome_id": 42,
#
# // The rollup score for the outcome, based on the student assessment # // The rollup score for the outcome, based on the student assessment
# // scores related to the outcome. This could be null if the student has # // scores related to the outcome. This could be null if the student has
# // no related scores. # // no related scores.
# "score": 3 # "score": 3,
#
# "links": {
# // The id of the related outcome
# "outcome": 42
# }
# } # }
# #
# @object OutcomeRollup # @object OutcomeRollup
@ -42,7 +43,12 @@
# "id": 42, # "id": 42,
# #
# // The name of the resource for this rollup. For example, the user name. # // The name of the resource for this rollup. For example, the user name.
# "name": "John Doe" # "name": "John Doe",
#
# "links": {
# // (Optional) The id of the section this resource is in
# "section": 57
# }
# } # }
# #
@ -69,12 +75,12 @@ class OutcomeResultsController < ApplicationController
# } # }
def rollups def rollups
@outcomes = @context.linked_learning_outcomes @outcomes = @context.linked_learning_outcomes
@users = users_for_outcome_context
# TODO: will this work if users are spread across shards? # TODO: will this work if users are spread across shards?
@users = Api.paginate(@users, self, api_v1_course_outcome_rollups_url(@context)) @users = Api.paginate(users_for_outcome_context, self, api_v1_course_outcome_rollups_url(@context))
@results = find_outcome_results(users: @users, context: @context, outcomes: @outcomes) @results = find_outcome_results(users: @users, context: @context, outcomes: @outcomes)
rollups = rollup_results(@results) rollups = rollup_results(@results, @users)
json = outcome_results_rollup_json(rollups, @outcomes) json = outcome_results_rollup_json(rollups, @outcomes)
json[:meta] = Api.jsonapi_meta(@users, self, api_v1_course_outcome_rollups_url(@context))
render :json => json render :json => json
end end

View File

@ -35,20 +35,32 @@ module Api::V1::OutcomeResults
# Internal: Returns a suitable output hash for the rollups # Internal: Returns a suitable output hash for the rollups
def sanitize_rollups(rollups) def sanitize_rollups(rollups)
rollups.map do |rollup| # this is uglier than it should be to inject section ids. they really should
{ # be in a 'links' section or something.
# ideally, we would have some seperate mapping from users to course sections
# it will also need to change is context is ever not a course
# we're mostly assuming that there is one section enrollment per user. if a user
# is in multiple sections, they will have multiple rollup results. pagination is
# still by user, so the counts won't match up. again, this is a very rare thing
results = []
rollups.each do |rollup|
row = {
id: rollup[:user].id, id: rollup[:user].id,
name: rollup[:user].name, name: rollup[:user].name,
scores: rollup[:scores].map { |score| sanitize_rollup_score(score) }, scores: rollup[:scores].map { |score| sanitize_rollup_score(score) },
} }
rollup[:user].sections_for_course(@context).each do |section|
results << row.merge(links: {section: section.id})
end end
end end
results
end
# Internal: Returns a suitable output hash for the rollup score # Internal: Returns a suitable output hash for the rollup score
def sanitize_rollup_score(score) def sanitize_rollup_score(score)
{ {
outcome_id: score[:outcome].id,
score: score[:score], score: score[:score],
links: {outcome: score[:outcome].id},
} }
end end

View File

@ -57,21 +57,27 @@ module Outcomes
# results - An Enumeration of properly sorted LearningOutcomeResult objects. # results - An Enumeration of properly sorted LearningOutcomeResult objects.
# The results should be sorted by user id and then by outcome id. # The results should be sorted by user id and then by outcome id.
# #
# Returns a hash of the results: # users - (Optional) Ensure rollups are included for users in this list.
# { # A listed user with no results will have an empty score array.
#
# Returns a list of users and their score rollups:
# [{
# user: the associated user object, # user: the associated user object,
# scores: [{ # scores: [{
# outcome: the outcome object # outcome: the outcome object
# score: the rollup score for all the user's results for the outcome. # score: the rollup score for all the user's results for the outcome.
# }, ..., repeated for each outcome, ...] # }, ..., repeated for each outcome, ...]
# } # }, ...]
def rollup_results(results) def rollup_results(results, users=[])
results.chunk(&:user_id).map do |_, user_results| rollups = results.chunk(&:user_id).map do |_, user_results|
{ {
user: user_results.first.user, user: user_results.first.user,
scores: rollup_user_results(user_results), scores: rollup_user_results(user_results),
} }
end end
missing_users = users - rollups.map {|r| r[:user]}
rollups + missing_users.map {|u| {user: u, scores: []}}
end end
# Internal: Generates a rollup of the outcome results, Assuming all the # Internal: Generates a rollup of the outcome results, Assuming all the

View File

@ -123,13 +123,15 @@ describe "Outcome Results API", :type => :integration do
api_call(:get, outcome_rollups_url(outcome_course), api_call(:get, outcome_rollups_url(outcome_course),
controller: 'outcome_results', action: 'rollups', format: 'json', course_id: outcome_course.id.to_s) controller: 'outcome_results', action: 'rollups', format: 'json', course_id: outcome_course.id.to_s)
json = JSON.parse(response.body) json = JSON.parse(response.body)
json.keys.sort.should == %w(linked rollups) json.keys.sort.should == %w(linked meta rollups)
json['rollups'].size.should == 1 json['rollups'].size.should == 1
json['rollups'].each do |rollup| json['rollups'].each do |rollup|
rollup.keys.sort.should == %w(id name scores) rollup.keys.sort.should == %w(id links name scores)
rollup['links'].keys.should == %w(section)
rollup['scores'].size.should == 1 rollup['scores'].size.should == 1
rollup['scores'].each do |score| rollup['scores'].each do |score|
score.keys.sort.should == %w(outcome_id score) score.keys.sort.should == %w(links score)
score['links'].keys.should == %w(outcome)
end end
end end
json['linked'].keys.should == %w(outcomes) json['linked'].keys.should == %w(outcomes)

View File

@ -68,9 +68,11 @@ describe Outcomes::ResultAnalytics do
MockOutcomeResult[MockUser[10, 'a'], MockOutcome[80], 40], MockOutcomeResult[MockUser[10, 'a'], MockOutcome[80], 40],
MockOutcomeResult[MockUser[20, 'b'], MockOutcome[80], 50], MockOutcomeResult[MockUser[20, 'b'], MockOutcome[80], 50],
] ]
ra.rollup_results(results).should == [ users = [MockUser[10, 'a'], MockUser[30, 'c']]
ra.rollup_results(results, users).should == [
{ user: MockUser[10, 'a'], scores: [{outcome: MockOutcome[80], score: 40}] }, { user: MockUser[10, 'a'], scores: [{outcome: MockOutcome[80], score: 40}] },
{ user: MockUser[20, 'b'], scores: [{outcome: MockOutcome[80], score: 50}] }, { user: MockUser[20, 'b'], scores: [{outcome: MockOutcome[80], score: 50}] },
{ user: MockUser[30, 'c'], scores: [] },
] ]
end end
end end