2020-10-27 00:50:13 +08:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2013-12-18 08:44:23 +08:00
|
|
|
#
|
2017-04-28 04:05:04 +08:00
|
|
|
# Copyright (C) 2013 - present Instructure, Inc.
|
2013-12-18 08:44:23 +08:00
|
|
|
#
|
|
|
|
# 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 Outcomes
|
|
|
|
module ResultAnalytics
|
|
|
|
|
2014-01-03 06:27:40 +08:00
|
|
|
Rollup = Struct.new(:context, :scores)
|
2018-06-13 04:18:09 +08:00
|
|
|
Result = Struct.new(:learning_outcome, :score, :count, :hide_points)
|
2013-12-18 08:44:23 +08:00
|
|
|
|
|
|
|
# Public: Queries learning_outcome_results for rollup.
|
|
|
|
#
|
Exclude outcome results from muted asgmts/quizzes
closes OUT-2304
performance:
Indices are used throughout the scoped query.
Shard.current.id => 1773
base query:
LearningOutcomeResult.active.where(context_code:'course_1079845',user_id:3306819,learning_outcome_id:1397026)
without scope:
----------------------------------------------------------------------------------------------------------------------------------
Nested Loop (cost=4.86..8.20 rows=1 width=268)
-> Bitmap Heap Scan on learning_outcome_results (cost=4.42..5.54 rows=1 width=268)
Recheck Cond: ((user_id = 3306819) AND (learning_outcome_id = 1397026))
Filter: ((context_code)::text = 'course_1079845'::text)
-> BitmapAnd (cost=4.42..4.42 rows=1 width=0)
-> Bitmap Index Scan on index_learning_outcome_results_association (cost=0.00..1.73 rows=27 width=0)
Index Cond: (user_id = 3306819)
-> Bitmap Index Scan on index_learning_outcome_results_on_learning_outcome_id (cost=0.00..2.44 rows=123 width=0)
Index Cond: (learning_outcome_id = 1397026)
-> Index Scan using content_tags_pkey on content_tags (cost=0.43..2.66 rows=1 width=8)
Index Cond: (id = learning_outcome_results.content_tag_id)
Filter: ((workflow_state)::text <> 'deleted'::text)
with scope (`exclude_muted_associations`):
----------------------------------------------------------------------------------------------------------------------------------------------------------
Nested Loop Left Join (cost=6.99..20.54 rows=1 width=268)
Join Filter: ((learning_outcome_results.association_type)::text = 'Assignment'::text)
Filter: (((ra.muted IS NULL) AND (qa.muted IS NULL) AND (sa.muted IS NULL)) OR (ra.muted IS FALSE) OR (qa.muted IS FALSE) OR (sa.muted IS FALSE))
-> Nested Loop Left Join (cost=6.56..17.88 rows=1 width=270)
-> Nested Loop Left Join (cost=6.13..15.72 rows=1 width=277)
Join Filter: ((learning_outcome_results.association_type)::text = 'Quizzes::Quiz'::text)
-> Nested Loop Left Join (cost=5.71..13.06 rows=1 width=269)
Join Filter: (((rassoc.association_type)::text = 'Assignment'::text) AND ((rassoc.purpose)::text = 'grading'::text))
-> Nested Loop Left Join (cost=5.28..10.85 rows=1 width=293)
Join Filter: ((learning_outcome_results.association_type)::text = 'RubricAssociation'::text)
-> Nested Loop (cost=4.86..8.20 rows=1 width=268)
-> Bitmap Heap Scan on learning_outcome_results (cost=4.42..5.54 rows=1 width=268)
Recheck Cond: ((user_id = 3306819) AND (learning_outcome_id = 1397026))
Filter: ((context_code)::text = 'course_1079845'::text)
-> BitmapAnd (cost=4.42..4.42 rows=1 width=0)
-> Bitmap Index Scan on index_learning_outcome_results_association (cost=0.00..1.73 rows=27 width=0)
Index Cond: (user_id = 3306819)
-> Bitmap Index Scan on index_learning_outcome_results_on_learning_outcome_id (cost=0.00..2.44 rows=123 width=0)
Index Cond: (learning_outcome_id = 1397026)
-> Index Scan using content_tags_pkey on content_tags (cost=0.43..2.66 rows=1 width=8)
Index Cond: (id = learning_outcome_results.content_tag_id)
Filter: ((workflow_state)::text <> 'deleted'::text)
-> Index Scan using rubric_associations_pkey on rubric_associations rassoc (cost=0.42..2.64 rows=1 width=33)
Index Cond: (id = learning_outcome_results.association_id)
-> Index Scan using assignments_pkey on assignments ra (cost=0.43..2.19 rows=1 width=9)
Index Cond: (id = rassoc.association_id)
-> Index Scan using quizzes_pkey on quizzes (cost=0.43..2.65 rows=1 width=16)
Index Cond: (id = learning_outcome_results.association_id)
-> Index Scan using assignments_pkey on assignments qa (cost=0.43..2.15 rows=1 width=9)
Index Cond: (id = quizzes.assignment_id)
-> Index Scan using assignments_pkey on assignments sa (cost=0.43..2.65 rows=1 width=9)
Index Cond: (id = learning_outcome_results.association_id)
test plan:
- create two course-level outcomes
- create an assignment with a single question, and align the 1st outcome via a rubric
- create a quiz bank with a single auto-gradeable question (e.g. true/false), and
align the 2nd outcome to it
- create a quiz that pulls the single question from the quiz bank above
- as a student, submit to the assignment and the quiz
- as a teacher, assess the assignment, providing a score to the rubric
- as a teacher, confirm:
* both outcomes have results in the LMGB
* both outcomes have results in the sLMGB
- as a student, confirm:
* both outcomes have results in the sLMGB
- as a teacher, mute the assignment in the gradebook:
https://community.canvaslms.com/docs/DOC-12961-4152724339
- as a teacher, confirm:
* both outcomes have results in the LMGB
* both outcomes have results in the sLMGB
- as a student, confirm:
* only the outcome associated with the quiz bank has results in the sLMGB
- as a teacher, mute the quiz in the gradebook
- as a teacher, confirm:
* both outcomes have results in the LMGB
* both outcomes have results in the sLMGB
- as a student, confirm:
* no outcomes should have results in the sLMGB
- as a teacher, unmute the assignment in the gradebook
- as a teacher, confirm:
* both outcomes have results in the LMGB
* both outcomes have results in the sLMGB
- as a student, confirm:
* only the outcome associated with the assignment has results in the sLMGB
Change-Id: I0ea05eedd29383501cc9306bcedcfa67aee4cd67
Reviewed-on: https://gerrit.instructure.com/155210
Tested-by: Jenkins
Reviewed-by: Neil Gupta <ngupta@instructure.com>
Product-Review: Neil Gupta <ngupta@instructure.com>
QA-Review: Neil Gupta <ngupta@instructure.com>
2018-06-22 06:52:49 +08:00
|
|
|
# user - User requesting results.
|
2013-12-18 08:44:23 +08:00
|
|
|
# opts - The options for the query. In a later version of ruby, these would
|
|
|
|
# be named parameters.
|
|
|
|
# :users - The users to lookup results for (required)
|
|
|
|
# :context - The context to lookup results for (required)
|
|
|
|
# :outcomes - The outcomes to lookup results for (required)
|
|
|
|
#
|
|
|
|
# Returns a relation of the results, suitably ordered.
|
Exclude outcome results from muted asgmts/quizzes
closes OUT-2304
performance:
Indices are used throughout the scoped query.
Shard.current.id => 1773
base query:
LearningOutcomeResult.active.where(context_code:'course_1079845',user_id:3306819,learning_outcome_id:1397026)
without scope:
----------------------------------------------------------------------------------------------------------------------------------
Nested Loop (cost=4.86..8.20 rows=1 width=268)
-> Bitmap Heap Scan on learning_outcome_results (cost=4.42..5.54 rows=1 width=268)
Recheck Cond: ((user_id = 3306819) AND (learning_outcome_id = 1397026))
Filter: ((context_code)::text = 'course_1079845'::text)
-> BitmapAnd (cost=4.42..4.42 rows=1 width=0)
-> Bitmap Index Scan on index_learning_outcome_results_association (cost=0.00..1.73 rows=27 width=0)
Index Cond: (user_id = 3306819)
-> Bitmap Index Scan on index_learning_outcome_results_on_learning_outcome_id (cost=0.00..2.44 rows=123 width=0)
Index Cond: (learning_outcome_id = 1397026)
-> Index Scan using content_tags_pkey on content_tags (cost=0.43..2.66 rows=1 width=8)
Index Cond: (id = learning_outcome_results.content_tag_id)
Filter: ((workflow_state)::text <> 'deleted'::text)
with scope (`exclude_muted_associations`):
----------------------------------------------------------------------------------------------------------------------------------------------------------
Nested Loop Left Join (cost=6.99..20.54 rows=1 width=268)
Join Filter: ((learning_outcome_results.association_type)::text = 'Assignment'::text)
Filter: (((ra.muted IS NULL) AND (qa.muted IS NULL) AND (sa.muted IS NULL)) OR (ra.muted IS FALSE) OR (qa.muted IS FALSE) OR (sa.muted IS FALSE))
-> Nested Loop Left Join (cost=6.56..17.88 rows=1 width=270)
-> Nested Loop Left Join (cost=6.13..15.72 rows=1 width=277)
Join Filter: ((learning_outcome_results.association_type)::text = 'Quizzes::Quiz'::text)
-> Nested Loop Left Join (cost=5.71..13.06 rows=1 width=269)
Join Filter: (((rassoc.association_type)::text = 'Assignment'::text) AND ((rassoc.purpose)::text = 'grading'::text))
-> Nested Loop Left Join (cost=5.28..10.85 rows=1 width=293)
Join Filter: ((learning_outcome_results.association_type)::text = 'RubricAssociation'::text)
-> Nested Loop (cost=4.86..8.20 rows=1 width=268)
-> Bitmap Heap Scan on learning_outcome_results (cost=4.42..5.54 rows=1 width=268)
Recheck Cond: ((user_id = 3306819) AND (learning_outcome_id = 1397026))
Filter: ((context_code)::text = 'course_1079845'::text)
-> BitmapAnd (cost=4.42..4.42 rows=1 width=0)
-> Bitmap Index Scan on index_learning_outcome_results_association (cost=0.00..1.73 rows=27 width=0)
Index Cond: (user_id = 3306819)
-> Bitmap Index Scan on index_learning_outcome_results_on_learning_outcome_id (cost=0.00..2.44 rows=123 width=0)
Index Cond: (learning_outcome_id = 1397026)
-> Index Scan using content_tags_pkey on content_tags (cost=0.43..2.66 rows=1 width=8)
Index Cond: (id = learning_outcome_results.content_tag_id)
Filter: ((workflow_state)::text <> 'deleted'::text)
-> Index Scan using rubric_associations_pkey on rubric_associations rassoc (cost=0.42..2.64 rows=1 width=33)
Index Cond: (id = learning_outcome_results.association_id)
-> Index Scan using assignments_pkey on assignments ra (cost=0.43..2.19 rows=1 width=9)
Index Cond: (id = rassoc.association_id)
-> Index Scan using quizzes_pkey on quizzes (cost=0.43..2.65 rows=1 width=16)
Index Cond: (id = learning_outcome_results.association_id)
-> Index Scan using assignments_pkey on assignments qa (cost=0.43..2.15 rows=1 width=9)
Index Cond: (id = quizzes.assignment_id)
-> Index Scan using assignments_pkey on assignments sa (cost=0.43..2.65 rows=1 width=9)
Index Cond: (id = learning_outcome_results.association_id)
test plan:
- create two course-level outcomes
- create an assignment with a single question, and align the 1st outcome via a rubric
- create a quiz bank with a single auto-gradeable question (e.g. true/false), and
align the 2nd outcome to it
- create a quiz that pulls the single question from the quiz bank above
- as a student, submit to the assignment and the quiz
- as a teacher, assess the assignment, providing a score to the rubric
- as a teacher, confirm:
* both outcomes have results in the LMGB
* both outcomes have results in the sLMGB
- as a student, confirm:
* both outcomes have results in the sLMGB
- as a teacher, mute the assignment in the gradebook:
https://community.canvaslms.com/docs/DOC-12961-4152724339
- as a teacher, confirm:
* both outcomes have results in the LMGB
* both outcomes have results in the sLMGB
- as a student, confirm:
* only the outcome associated with the quiz bank has results in the sLMGB
- as a teacher, mute the quiz in the gradebook
- as a teacher, confirm:
* both outcomes have results in the LMGB
* both outcomes have results in the sLMGB
- as a student, confirm:
* no outcomes should have results in the sLMGB
- as a teacher, unmute the assignment in the gradebook
- as a teacher, confirm:
* both outcomes have results in the LMGB
* both outcomes have results in the sLMGB
- as a student, confirm:
* only the outcome associated with the assignment has results in the sLMGB
Change-Id: I0ea05eedd29383501cc9306bcedcfa67aee4cd67
Reviewed-on: https://gerrit.instructure.com/155210
Tested-by: Jenkins
Reviewed-by: Neil Gupta <ngupta@instructure.com>
Product-Review: Neil Gupta <ngupta@instructure.com>
QA-Review: Neil Gupta <ngupta@instructure.com>
2018-06-22 06:52:49 +08:00
|
|
|
def find_outcome_results(user, opts)
|
2013-12-18 08:44:23 +08:00
|
|
|
required_opts = [:users, :context, :outcomes]
|
|
|
|
required_opts.each { |p| raise "#{p} option is required" unless opts[p] }
|
|
|
|
users, context, outcomes = opts.values_at(*required_opts)
|
2018-06-20 06:29:55 +08:00
|
|
|
results = LearningOutcomeResult.active.where(
|
2013-12-18 08:44:23 +08:00
|
|
|
context_code: context.asset_string,
|
|
|
|
user_id: users.map(&:id),
|
2018-06-20 06:29:55 +08:00
|
|
|
learning_outcome_id: outcomes.map(&:id),
|
2013-12-18 08:44:23 +08:00
|
|
|
)
|
Exclude outcome results from muted asgmts/quizzes
closes OUT-2304
performance:
Indices are used throughout the scoped query.
Shard.current.id => 1773
base query:
LearningOutcomeResult.active.where(context_code:'course_1079845',user_id:3306819,learning_outcome_id:1397026)
without scope:
----------------------------------------------------------------------------------------------------------------------------------
Nested Loop (cost=4.86..8.20 rows=1 width=268)
-> Bitmap Heap Scan on learning_outcome_results (cost=4.42..5.54 rows=1 width=268)
Recheck Cond: ((user_id = 3306819) AND (learning_outcome_id = 1397026))
Filter: ((context_code)::text = 'course_1079845'::text)
-> BitmapAnd (cost=4.42..4.42 rows=1 width=0)
-> Bitmap Index Scan on index_learning_outcome_results_association (cost=0.00..1.73 rows=27 width=0)
Index Cond: (user_id = 3306819)
-> Bitmap Index Scan on index_learning_outcome_results_on_learning_outcome_id (cost=0.00..2.44 rows=123 width=0)
Index Cond: (learning_outcome_id = 1397026)
-> Index Scan using content_tags_pkey on content_tags (cost=0.43..2.66 rows=1 width=8)
Index Cond: (id = learning_outcome_results.content_tag_id)
Filter: ((workflow_state)::text <> 'deleted'::text)
with scope (`exclude_muted_associations`):
----------------------------------------------------------------------------------------------------------------------------------------------------------
Nested Loop Left Join (cost=6.99..20.54 rows=1 width=268)
Join Filter: ((learning_outcome_results.association_type)::text = 'Assignment'::text)
Filter: (((ra.muted IS NULL) AND (qa.muted IS NULL) AND (sa.muted IS NULL)) OR (ra.muted IS FALSE) OR (qa.muted IS FALSE) OR (sa.muted IS FALSE))
-> Nested Loop Left Join (cost=6.56..17.88 rows=1 width=270)
-> Nested Loop Left Join (cost=6.13..15.72 rows=1 width=277)
Join Filter: ((learning_outcome_results.association_type)::text = 'Quizzes::Quiz'::text)
-> Nested Loop Left Join (cost=5.71..13.06 rows=1 width=269)
Join Filter: (((rassoc.association_type)::text = 'Assignment'::text) AND ((rassoc.purpose)::text = 'grading'::text))
-> Nested Loop Left Join (cost=5.28..10.85 rows=1 width=293)
Join Filter: ((learning_outcome_results.association_type)::text = 'RubricAssociation'::text)
-> Nested Loop (cost=4.86..8.20 rows=1 width=268)
-> Bitmap Heap Scan on learning_outcome_results (cost=4.42..5.54 rows=1 width=268)
Recheck Cond: ((user_id = 3306819) AND (learning_outcome_id = 1397026))
Filter: ((context_code)::text = 'course_1079845'::text)
-> BitmapAnd (cost=4.42..4.42 rows=1 width=0)
-> Bitmap Index Scan on index_learning_outcome_results_association (cost=0.00..1.73 rows=27 width=0)
Index Cond: (user_id = 3306819)
-> Bitmap Index Scan on index_learning_outcome_results_on_learning_outcome_id (cost=0.00..2.44 rows=123 width=0)
Index Cond: (learning_outcome_id = 1397026)
-> Index Scan using content_tags_pkey on content_tags (cost=0.43..2.66 rows=1 width=8)
Index Cond: (id = learning_outcome_results.content_tag_id)
Filter: ((workflow_state)::text <> 'deleted'::text)
-> Index Scan using rubric_associations_pkey on rubric_associations rassoc (cost=0.42..2.64 rows=1 width=33)
Index Cond: (id = learning_outcome_results.association_id)
-> Index Scan using assignments_pkey on assignments ra (cost=0.43..2.19 rows=1 width=9)
Index Cond: (id = rassoc.association_id)
-> Index Scan using quizzes_pkey on quizzes (cost=0.43..2.65 rows=1 width=16)
Index Cond: (id = learning_outcome_results.association_id)
-> Index Scan using assignments_pkey on assignments qa (cost=0.43..2.15 rows=1 width=9)
Index Cond: (id = quizzes.assignment_id)
-> Index Scan using assignments_pkey on assignments sa (cost=0.43..2.65 rows=1 width=9)
Index Cond: (id = learning_outcome_results.association_id)
test plan:
- create two course-level outcomes
- create an assignment with a single question, and align the 1st outcome via a rubric
- create a quiz bank with a single auto-gradeable question (e.g. true/false), and
align the 2nd outcome to it
- create a quiz that pulls the single question from the quiz bank above
- as a student, submit to the assignment and the quiz
- as a teacher, assess the assignment, providing a score to the rubric
- as a teacher, confirm:
* both outcomes have results in the LMGB
* both outcomes have results in the sLMGB
- as a student, confirm:
* both outcomes have results in the sLMGB
- as a teacher, mute the assignment in the gradebook:
https://community.canvaslms.com/docs/DOC-12961-4152724339
- as a teacher, confirm:
* both outcomes have results in the LMGB
* both outcomes have results in the sLMGB
- as a student, confirm:
* only the outcome associated with the quiz bank has results in the sLMGB
- as a teacher, mute the quiz in the gradebook
- as a teacher, confirm:
* both outcomes have results in the LMGB
* both outcomes have results in the sLMGB
- as a student, confirm:
* no outcomes should have results in the sLMGB
- as a teacher, unmute the assignment in the gradebook
- as a teacher, confirm:
* both outcomes have results in the LMGB
* both outcomes have results in the sLMGB
- as a student, confirm:
* only the outcome associated with the assignment has results in the sLMGB
Change-Id: I0ea05eedd29383501cc9306bcedcfa67aee4cd67
Reviewed-on: https://gerrit.instructure.com/155210
Tested-by: Jenkins
Reviewed-by: Neil Gupta <ngupta@instructure.com>
Product-Review: Neil Gupta <ngupta@instructure.com>
QA-Review: Neil Gupta <ngupta@instructure.com>
2018-06-22 06:52:49 +08:00
|
|
|
unless context.grants_any_right?(user, :manage_grades, :view_all_grades)
|
|
|
|
results = results.exclude_muted_associations
|
|
|
|
end
|
2018-06-20 06:29:55 +08:00
|
|
|
unless opts[:include_hidden]
|
|
|
|
results = results.where(hidden: false)
|
|
|
|
end
|
|
|
|
order_results_for_rollup results
|
2013-12-18 08:44:23 +08:00
|
|
|
end
|
|
|
|
|
|
|
|
# Internal: Add an order clause to a relation so results are returned in an
|
|
|
|
# order suitable for rollup calculations.
|
|
|
|
#
|
|
|
|
# relation - The relation to add an order clause to.
|
|
|
|
#
|
|
|
|
# Returns the resulting relation
|
|
|
|
def order_results_for_rollup(relation)
|
2020-09-25 23:30:05 +08:00
|
|
|
relation.joins(:user).order(User.sortable_name_order_by_clause).order('learning_outcome_results.learning_outcome_id ASC, learning_outcome_results.id ASC')
|
2013-12-18 08:44:23 +08:00
|
|
|
end
|
|
|
|
|
|
|
|
# Public: Generates a rollup of each outcome result for each user.
|
|
|
|
#
|
|
|
|
# results - An Enumeration of properly sorted LearningOutcomeResult objects.
|
|
|
|
# The results should be sorted by user id and then by outcome id.
|
|
|
|
#
|
2014-01-01 09:01:13 +08:00
|
|
|
# users - (Optional) Ensure rollups are included for users in this list.
|
|
|
|
# A listed user with no results will have an empty score array.
|
|
|
|
#
|
2018-07-03 07:38:33 +08:00
|
|
|
# excludes - (Optional) Specify additional values to exclude. "missing_user_rollups" excludes
|
|
|
|
# rollups for users without results.
|
|
|
|
#
|
2020-07-29 05:03:24 +08:00
|
|
|
# context - (Optional) The current context making the function call which will be used in
|
|
|
|
# determining the current_method chosen for calculating rollups.
|
|
|
|
#
|
2014-01-03 06:27:40 +08:00
|
|
|
# Returns an Array of Rollup objects.
|
2020-07-29 05:03:24 +08:00
|
|
|
def outcome_results_rollups(results: , users: [], excludes: [], context: nil)
|
2015-12-19 05:47:46 +08:00
|
|
|
ActiveRecord::Associations::Preloader.new.preload(results, :learning_outcome)
|
2014-01-01 09:01:13 +08:00
|
|
|
rollups = results.chunk(&:user_id).map do |_, user_results|
|
2020-07-29 05:03:24 +08:00
|
|
|
Rollup.new(user_results.first.user, rollup_user_results(user_results, context))
|
2013-12-18 08:44:23 +08:00
|
|
|
end
|
2018-07-03 07:38:33 +08:00
|
|
|
if excludes.include? 'missing_user_rollups'
|
|
|
|
rollups
|
|
|
|
else
|
|
|
|
add_missing_user_rollups(rollups, users)
|
|
|
|
end
|
2014-01-03 06:27:40 +08:00
|
|
|
end
|
|
|
|
|
|
|
|
# Public: Calculates an average rollup for the specified results
|
|
|
|
#
|
|
|
|
# results - An Enumeration of properly sorted LearningOutcomeResult objects.
|
|
|
|
# context - The context to use for the resulting rollup.
|
|
|
|
#
|
|
|
|
# Returns a Rollup.
|
2018-07-07 03:31:53 +08:00
|
|
|
def aggregate_outcome_results_rollup(results, context, stat = 'mean')
|
2020-07-29 05:03:24 +08:00
|
|
|
rollups = outcome_results_rollups(results: results, context: context)
|
2014-01-03 06:27:40 +08:00
|
|
|
rollup_scores = rollups.map(&:scores).flatten
|
2015-01-29 07:06:12 +08:00
|
|
|
outcome_results = rollup_scores.group_by(&:outcome).values
|
|
|
|
aggregate_results = outcome_results.map do |scores|
|
2018-06-13 04:18:09 +08:00
|
|
|
scores.map{|score| Result.new(score.outcome, score.score, score.count, score.hide_points)}
|
2014-01-03 06:27:40 +08:00
|
|
|
end
|
2015-05-01 01:27:50 +08:00
|
|
|
aggregate_rollups = aggregate_results.map do |result|
|
2020-07-29 05:03:24 +08:00
|
|
|
RollupScore.new(outcome_results: result, opts: {aggregate_score: true, aggregate_stat: stat}, context: context)
|
2015-05-01 01:27:50 +08:00
|
|
|
end
|
2015-01-29 07:06:12 +08:00
|
|
|
Rollup.new(context, aggregate_rollups)
|
2013-12-18 08:44:23 +08:00
|
|
|
end
|
|
|
|
|
|
|
|
# Internal: Generates a rollup of the outcome results, Assuming all the
|
|
|
|
# results are for the same user.
|
|
|
|
#
|
|
|
|
# user_results - An Enumeration of LearningOutcomeResult objects for a user
|
|
|
|
# sorted by outcome id.
|
|
|
|
#
|
2014-01-03 06:27:40 +08:00
|
|
|
# Returns an Array of RollupScore objects
|
2020-07-29 05:03:24 +08:00
|
|
|
def rollup_user_results(user_results, context = nil)
|
fix outcome calcs for mix of assignments & quizzes
fixes OUT-460
test plan:
- create 2 outcomes, one with decaying average and one w/ n_mastery
- attach each outcome to an assignment and a quiz
- it's not reccomended to use exactly 5 questions for quiz testing
since this has the potential to obfuscate possible calc errors
- log in as a student and submit to the assignment/take the quiz
- as the teacher/admin, asses the outcome on the assignment in
speedgrader. It's reccomended to get a high score on at least
one quiz that's being tested, in order to ensure the mastery
score on the result does not exceed the max possible score
for the outcome
- view the students outcome scores in the lmgb and student lmgb
to confirm the score's accuracy
try various scores, but here's an initial example assuming
Outcome A is decaying avg, and Outcome B is n_mastery
Outcome A
- Attach Outcome A to two assignments and two quizzes
- Submit as the student, first to the two assignments,
scoring a 3.0 and a 2.0, then on a quiz in which you
get 90% on the aligned bank
- on the final/most recent bank, score a 40%
- the score for the Outcome should be 2.41
Outcome B
- Attach Outcome B to two assignments and three quizzes
- Submit as the student. Order does not matter, but ensure
scores of 3.0 and 3.5 on the assignments, and 20%, 50%,
and 80% on the quizzes.
- the score for the Outcome should be 3.5
Change-Id: If99d8ab6a3791137e407ab43fd8af2c0d69058d5
Reviewed-on: https://gerrit.instructure.com/93333
Reviewed-by: Augusto Callejas <acallejas@instructure.com>
Reviewed-by: Michael Brewer-Davis <mbd@instructure.com>
Tested-by: Jenkins
QA-Review: Cemal Aktas <caktas@instructure.com>
Product-Review: McCall Smith <mcsmith@instructure.com>
2016-10-21 04:39:59 +08:00
|
|
|
filtered_results = user_results.select{|r| !r.score.nil?}
|
|
|
|
filtered_results.group_by(&:learning_outcome_id).map do |_, outcome_results|
|
2020-07-29 05:03:24 +08:00
|
|
|
RollupScore.new(outcome_results:outcome_results, opts:{}, context: context)
|
2013-12-18 08:44:23 +08:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2014-01-03 06:27:40 +08:00
|
|
|
# Internal: Adds rollups rows for users that did not have any results
|
|
|
|
#
|
|
|
|
# rollups - The list of rollup objects based on existing results.
|
|
|
|
# users - The list of User objects that should have results.
|
|
|
|
#
|
|
|
|
# Returns the modified rollups list. Users without rollups will have a
|
|
|
|
# rollup row with an empty scores array.
|
|
|
|
def add_missing_user_rollups(rollups, users)
|
|
|
|
missing_users = users - rollups.map(&:context)
|
|
|
|
rollups + missing_users.map { |u| Rollup.new(u, []) }
|
|
|
|
end
|
|
|
|
|
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>
2018-11-22 07:50:36 +08:00
|
|
|
# Public: Gets rating percents for outcomes based on rollup
|
|
|
|
#
|
|
|
|
# Returns a hash of outcome id to array of rating percents
|
2020-11-10 04:28:20 +08:00
|
|
|
def rating_percents(rollups, context: nil)
|
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>
2018-11-22 07:50:36 +08:00
|
|
|
counts = {}
|
2020-11-10 04:28:20 +08:00
|
|
|
outcome_proficiency_ratings = if context&.root_account&.feature_enabled?(:account_level_mastery_scales)
|
|
|
|
context.resolved_outcome_proficiency.ratings_hash
|
|
|
|
end
|
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>
2018-11-22 07:50:36 +08:00
|
|
|
rollups.each do |rollup|
|
|
|
|
rollup.scores.each do |score|
|
|
|
|
next unless score.score
|
2020-11-10 04:28:20 +08:00
|
|
|
|
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>
2018-11-22 07:50:36 +08:00
|
|
|
outcome = score.outcome
|
|
|
|
next unless outcome
|
2020-11-10 04:28:20 +08:00
|
|
|
|
|
|
|
ratings = outcome_proficiency_ratings || outcome.rubric_criterion[:ratings]
|
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>
2018-11-22 07:50:36 +08:00
|
|
|
next unless ratings
|
2020-11-10 04:28:20 +08:00
|
|
|
|
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>
2018-11-22 07:50:36 +08:00
|
|
|
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
|
|
|
|
|
2013-12-18 08:44:23 +08:00
|
|
|
class << self
|
|
|
|
include ResultAnalytics
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|