LMGB outcome details includes all results
closes OUT-2550 test plan: - create more than 20 student users in a course. you can bulk create users by modifying the sample file of 10 users under "users.csv" on https://community.canvaslms.com/docs/DOC-12585-4214164118 and then importing them through an account's SIS Import page (you may need to enable "SIS imports" on the account features section on its settings page). afterwards, add the students to a course - create a course outcome - create an assignment, aligned to the outcome using a rubric - as all the students, submit to the assignment - as a teacher, in SpeedGrader, create rubric assessments for all submissions, selecting different criterion ratings - load the LMGB page - hover over the outcome header, and confirm the outcome details summarize the correct percentages based on the outcome scores - navigate to the 2nd page of results - hover over the outcome header, and confirm the outcomes details, like described two steps above - enable the New Gradebook - repeat the steps above, starting at loading the LMGB page Change-Id: I52815eb2e05a04a0ad70c9c53e13726dbc626b64 Reviewed-on: https://gerrit.instructure.com/173291 Tested-by: Jenkins Reviewed-by: Neil Gupta <ngupta@instructure.com> Reviewed-by: Frank Murphy III <fmurphy@instructure.com> QA-Review: Brian Watson <bwatson@instructure.com> Product-Review: Neil Gupta <ngupta@instructure.com>
This commit is contained in:
parent
b8c4c8f799
commit
4d2bedefb3
|
@ -351,8 +351,3 @@ define [
|
|||
results = Grid.View.getColumnResults(grid.getData(), column)
|
||||
ratings = column.outcome.ratings || []
|
||||
ratings.result_count = results.length
|
||||
points = _.pluck ratings, 'points'
|
||||
counts = _.countBy results, (result) ->
|
||||
_.find points, (x) -> result && x <= result.score
|
||||
_.each ratings, (rating) ->
|
||||
rating.percent = Math.round((counts[rating.points] || 0) / results.length * 100)
|
||||
|
|
|
@ -351,8 +351,3 @@ define [
|
|||
results = Grid.View.getColumnResults(grid.getData(), column)
|
||||
ratings = column.outcome.ratings || []
|
||||
ratings.result_count = results.length
|
||||
points = _.pluck ratings, 'points'
|
||||
counts = _.countBy results, (result) ->
|
||||
_.find points, (x) -> result && x <= result.score
|
||||
_.each ratings, (rating) ->
|
||||
rating.percent = Math.round((counts[rating.points] || 0) / results.length * 100)
|
||||
|
|
|
@ -281,7 +281,7 @@ define [
|
|||
sortParams = "#{sortParams}&sort_outcome_id=#{sortOutcomeId}" if sortOutcomeId
|
||||
sortParams = "#{sortParams}&sort_order=desc" if !@sortOrderAsc
|
||||
sectionParam = if Grid.section then "§ion_id=#{Grid.section}" else ""
|
||||
"/api/v1/courses/#{course}/outcome_rollups?per_page=20&include[]=outcomes&include[]=users&include[]=outcome_paths#{excluding}&page=#{page}#{sortParams}#{sectionParam}"
|
||||
"/api/v1/courses/#{course}/outcome_rollups?rating_percents=true&per_page=20&include[]=outcomes&include[]=users&include[]=outcome_paths#{excluding}&page=#{page}#{sortParams}#{sectionParam}"
|
||||
|
||||
_loadOutcomes: (page = 1) =>
|
||||
exclude = if @$('#no_results_students').prop('checked') then 'missing_user_rollups' else ''
|
||||
|
|
|
@ -304,7 +304,7 @@ define [
|
|||
sortParams = "#{sortParams}&sort_outcome_id=#{sortOutcomeId}" if sortOutcomeId
|
||||
sortParams = "#{sortParams}&sort_order=desc" if !@sortOrderAsc
|
||||
sectionParam = if Grid.section and Grid.section != "0" then "§ion_id=#{Grid.section}" else ""
|
||||
"/api/v1/courses/#{course}/outcome_rollups?per_page=20&include[]=outcomes&include[]=users&include[]=outcome_paths#{excluding}&page=#{page}#{sortParams}#{sectionParam}"
|
||||
"/api/v1/courses/#{course}/outcome_rollups?rating_percents=true&per_page=20&include[]=outcomes&include[]=users&include[]=outcome_paths#{excluding}&page=#{page}#{sortParams}#{sectionParam}"
|
||||
|
||||
_loadOutcomes: (page = 1) =>
|
||||
exclude = if @$('#no_results_students').prop('checked') then 'missing_user_rollups' else ''
|
||||
|
|
|
@ -336,12 +336,18 @@ class OutcomeResultsController < ApplicationController
|
|||
private
|
||||
|
||||
def find_results(opts = {})
|
||||
find_outcome_results(@current_user, users: @users, context: @context, outcomes: @outcomes, **opts)
|
||||
find_outcome_results(
|
||||
@current_user,
|
||||
users: opts[:all_users] ? @all_users : @users,
|
||||
context: @context,
|
||||
outcomes: @outcomes,
|
||||
**opts
|
||||
)
|
||||
end
|
||||
|
||||
def user_rollups(_opts = {})
|
||||
def user_rollups(opts = {})
|
||||
excludes = Api.value_to_array(params[:exclude]).uniq
|
||||
@results = find_results.preload(:user)
|
||||
@results = find_results(opts).preload(:user)
|
||||
outcome_results_rollups(@results, @users, excludes)
|
||||
end
|
||||
|
||||
|
@ -412,7 +418,9 @@ class OutcomeResultsController < ApplicationController
|
|||
end
|
||||
|
||||
def include_outcomes
|
||||
outcome_results_include_outcomes_json(@outcomes)
|
||||
percents = {}
|
||||
percents = rating_percents(user_rollups(all_users: true)) if params[:rating_percents] == 'true'
|
||||
outcome_results_include_outcomes_json(@outcomes, percents)
|
||||
end
|
||||
|
||||
def include_outcome_groups
|
||||
|
@ -564,6 +572,9 @@ class OutcomeResultsController < ApplicationController
|
|||
end
|
||||
@users ||= users_for_outcome_context.to_a
|
||||
@users.sort! {|a,b| a.id <=> b.id} unless params[:sort_by]
|
||||
# cache all users, since pagination in #user_rollups_json may remove some
|
||||
# when we need all users when calculating rating percents
|
||||
@all_users = @users
|
||||
end
|
||||
|
||||
def users_for_outcome_context
|
||||
|
|
|
@ -57,7 +57,10 @@ module Api::V1::Outcome
|
|||
|
||||
hash['points_possible'] = outcome.rubric_criterion[:points_possible]
|
||||
hash['mastery_points'] = outcome.rubric_criterion[:mastery_points]
|
||||
hash['ratings'] = outcome.rubric_criterion[:ratings]
|
||||
hash['ratings'] = outcome.rubric_criterion[:ratings]&.clone
|
||||
hash['ratings']&.each_with_index do |rating, i|
|
||||
rating[:percent] = opts[:rating_percents][i] if i < opts[:rating_percents].length
|
||||
end if opts[:rating_percents]
|
||||
if opts[:assessed_outcomes] && outcome.context_type != "Account"
|
||||
hash['assessed'] = opts[:assessed_outcomes].include?(outcome.id)
|
||||
else
|
||||
|
|
|
@ -60,7 +60,7 @@ module Api::V1::OutcomeResults
|
|||
# Public: Serializes outcomes in a hash that can be added to the linked hash.
|
||||
#
|
||||
# Returns a Hash containing serialized outcomes.
|
||||
def outcome_results_include_outcomes_json(outcomes)
|
||||
def outcome_results_include_outcomes_json(outcomes, percents = {})
|
||||
alignment_asset_string_map = {}
|
||||
outcomes.each_slice(50).each do |outcomes_slice|
|
||||
ActiveRecord::Associations::Preloader.new.preload(outcomes_slice, [:context])
|
||||
|
@ -74,7 +74,10 @@ module Api::V1::OutcomeResults
|
|||
assessed_outcomes += LearningOutcomeResult.distinct.where(learning_outcome_id: outcome_ids).pluck(:learning_outcome_id)
|
||||
end
|
||||
outcomes.map do |o|
|
||||
hash = outcome_json(o, @current_user, session, assessed_outcomes: assessed_outcomes)
|
||||
hash = outcome_json(o,
|
||||
@current_user, session,
|
||||
assessed_outcomes: assessed_outcomes,
|
||||
rating_percents: percents[o.id])
|
||||
hash.merge!(alignments: alignment_asset_string_map[o.id])
|
||||
hash
|
||||
end
|
||||
|
|
|
@ -129,6 +129,33 @@ module Outcomes
|
|||
rollups + missing_users.map { |u| Rollup.new(u, []) }
|
||||
end
|
||||
|
||||
# Public: Gets rating percents for outcomes based on rollup
|
||||
#
|
||||
# Returns a hash of outcome id to array of rating percents
|
||||
def rating_percents(rollups)
|
||||
counts = {}
|
||||
rollups.each do |rollup|
|
||||
rollup.scores.each do |score|
|
||||
next unless score.score
|
||||
outcome = score.outcome
|
||||
next unless outcome
|
||||
ratings = outcome.rubric_criterion[:ratings]
|
||||
next unless ratings
|
||||
counts[outcome.id] = Array.new(ratings.length, 0) unless counts[outcome.id]
|
||||
idx = ratings.find_index { |rating| rating[:points] <= score.score }
|
||||
counts[outcome.id][idx] = counts[outcome.id][idx] + 1 if idx
|
||||
end
|
||||
end
|
||||
counts.each {|k, v| counts[k] = to_percents(v)}
|
||||
counts
|
||||
end
|
||||
|
||||
def to_percents(count_arr)
|
||||
total = count_arr.sum
|
||||
return count_arr if total.zero?
|
||||
count_arr.map {|v| (100.0 * v / total).round}
|
||||
end
|
||||
|
||||
class << self
|
||||
include ResultAnalytics
|
||||
end
|
||||
|
|
|
@ -258,6 +258,11 @@ describe OutcomeResultsController do
|
|||
format: "json"
|
||||
end
|
||||
|
||||
it 'includes rating percents' do
|
||||
json = parse_response(get_rollups(rating_percents: true, include: ['outcomes']))
|
||||
expect(json['linked']['outcomes'][0]['ratings'].map { |r| r['percent'] }).to eq [50, 50]
|
||||
end
|
||||
|
||||
context 'sorting' do
|
||||
it 'should validate sort_by parameter' do
|
||||
get_rollups(sort_by: 'garbage')
|
||||
|
|
|
@ -66,6 +66,10 @@ RSpec.describe "Api::V1::Outcome" do
|
|||
end
|
||||
|
||||
context "outcome json" do
|
||||
let(:opts) do
|
||||
{ rating_percents: [30, 40, 30] }
|
||||
end
|
||||
|
||||
let(:check_outcome_json) do
|
||||
->(outcome) do
|
||||
expect(outcome['title']).to eq(outcome_params[:title])
|
||||
|
@ -79,17 +83,18 @@ RSpec.describe "Api::V1::Outcome" do
|
|||
LearningOutcome.find(outcome['id']).updateable_rubrics?
|
||||
)
|
||||
expect(outcome['ratings'].length).to eq 3
|
||||
expect(outcome['ratings'].map { |r| r['percent'] }).to eq [30, 40, 30]
|
||||
end
|
||||
end
|
||||
|
||||
it "returns the json for an outcome" do
|
||||
check_outcome_json.call(lib.outcome_json(new_outcome(outcome_params), nil, nil))
|
||||
check_outcome_json.call(lib.outcome_json(new_outcome(outcome_params), nil, nil, opts))
|
||||
end
|
||||
|
||||
it "returns the json for multiple outcomes" do
|
||||
outcomes = []
|
||||
10.times{ outcomes.push(new_outcome) }
|
||||
lib.outcomes_json(outcomes, nil, nil).each { |o| check_outcome_json.call(o) }
|
||||
lib.outcomes_json(outcomes, nil, nil, opts).each { |o| check_outcome_json.call(o) }
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -58,7 +58,7 @@ describe Outcomes::ResultAnalytics do
|
|||
# outcomes that predate the newer calculation methods
|
||||
id = args[:id] || 80
|
||||
method = args[:method] || "highest"
|
||||
criterion = args[:criterion] || {mastery_points: 3.0}
|
||||
criterion = args[:criterion] || LearningOutcome.default_rubric_criterion
|
||||
MockOutcome[id, method, args[:calc_int], criterion]
|
||||
end
|
||||
|
||||
|
@ -375,6 +375,24 @@ describe Outcomes::ResultAnalytics do
|
|||
end
|
||||
end
|
||||
|
||||
describe '#rating_percents' do
|
||||
before do
|
||||
allow_any_instance_of(ActiveRecord::Associations::Preloader).to receive(:preload)
|
||||
end
|
||||
|
||||
it 'computes percents' do
|
||||
results = [
|
||||
outcome_from_score(4.0, {}),
|
||||
outcome_from_score(5.0, {user: MockUser[20, 'b']}),
|
||||
outcome_from_score(3.0, {user: MockUser[20, 'b']})
|
||||
]
|
||||
users = [MockUser[10, 'a'], MockUser[30, 'c']]
|
||||
rollups = ra.outcome_results_rollups(results, users)
|
||||
percents = ra.rating_percents(rollups)
|
||||
expect(percents).to eq({ 80 => [50, 50, 0] })
|
||||
end
|
||||
end
|
||||
|
||||
describe "handling quiz outcome results objects" do
|
||||
it "scales quiz scores to rubric score" do
|
||||
o1 = MockOutcome[80, 'decaying_average', 65, {points_possible: 5}]
|
||||
|
|
Loading…
Reference in New Issue