2020-10-27 00:46:40 +08:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2011-02-01 09:57:29 +08:00
|
|
|
#
|
2017-04-28 03:42:22 +08:00
|
|
|
# Copyright (C) 2011 - present Instructure, Inc.
|
2011-02-01 09:57:29 +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/>.
|
|
|
|
#
|
|
|
|
|
|
|
|
# asset_code is used to specify the 'asset' or idea being accessed
|
|
|
|
# asset_group_code is for the group
|
|
|
|
# so, for example, the asset could be an assignment, the group would be the assignment_group
|
|
|
|
class AssetUserAccess < ActiveRecord::Base
|
2020-06-16 03:06:49 +08:00
|
|
|
extend RootAccountResolver
|
|
|
|
|
2016-04-03 07:34:36 +08:00
|
|
|
belongs_to :context, polymorphic: %i[account course group user], polymorphic_prefix: true
|
2011-02-01 09:57:29 +08:00
|
|
|
belongs_to :user
|
|
|
|
has_many :page_views
|
2020-09-01 07:45:05 +08:00
|
|
|
|
2020-08-20 21:44:07 +08:00
|
|
|
# if you add any more callbacks, be sure to update #log
|
2011-02-01 09:57:29 +08:00
|
|
|
before_save :infer_defaults
|
2021-03-12 14:25:12 +08:00
|
|
|
before_save :infer_root_account_id
|
2020-06-16 03:06:49 +08:00
|
|
|
resolves_root_account through: ->(instance) { instance.infer_root_account_id }
|
|
|
|
|
2013-03-21 03:38:19 +08:00
|
|
|
scope :for_context, ->(context) { where(context_id: context, context_type: context.class.to_s) }
|
|
|
|
scope :for_user, ->(user) { where(user_id: user) }
|
2014-07-02 03:38:26 +08:00
|
|
|
scope :participations, -> { where(action_level: "participate") }
|
|
|
|
scope :most_recent, -> { order("updated_at DESC") }
|
2012-11-16 07:34:57 +08:00
|
|
|
|
2020-09-01 07:45:05 +08:00
|
|
|
def infer_root_account_id(asset_for_root_account_id = nil)
|
2021-03-12 14:25:12 +08:00
|
|
|
self.root_account_id ||= if context_type != "User"
|
|
|
|
context&.resolved_root_account_id || 0
|
|
|
|
elsif asset_for_root_account_id.is_a?(User)
|
|
|
|
# Unfillable. Point to the dummy root account with id=0.
|
|
|
|
0
|
|
|
|
else
|
|
|
|
asset_for_root_account_id.try(:resolved_root_account_id) ||
|
|
|
|
asset_for_root_account_id.try(:root_account_id) || 0
|
|
|
|
# We could default `asset_for_root_account_id ||= asset`, but AUAs shouldn't
|
|
|
|
# ever be created outside of .log(), and calling `asset` would add a DB hit
|
2020-09-01 07:45:05 +08:00
|
|
|
end
|
2020-06-16 03:06:49 +08:00
|
|
|
end
|
|
|
|
|
2011-02-01 09:57:29 +08:00
|
|
|
def category
|
|
|
|
asset_category
|
|
|
|
end
|
2012-11-16 07:34:57 +08:00
|
|
|
|
2011-02-01 09:57:29 +08:00
|
|
|
def infer_defaults
|
2014-10-10 02:36:57 +08:00
|
|
|
self.display_name = asset_display_name
|
2011-02-01 09:57:29 +08:00
|
|
|
end
|
2012-11-16 07:34:57 +08:00
|
|
|
|
2011-02-01 09:57:29 +08:00
|
|
|
def category=(val)
|
|
|
|
self.asset_category = val
|
|
|
|
end
|
2012-11-16 07:34:57 +08:00
|
|
|
|
2011-05-26 06:16:01 +08:00
|
|
|
def display_name
|
|
|
|
# repair existing AssetUserAccesses that have bad display_names
|
|
|
|
if read_attribute(:display_name) == asset_code
|
|
|
|
better_display_name = asset_display_name
|
|
|
|
if better_display_name != asset_code
|
|
|
|
update_attribute(:display_name, better_display_name)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
read_attribute(:display_name)
|
|
|
|
end
|
2012-11-16 07:34:57 +08:00
|
|
|
|
2011-02-01 09:57:29 +08:00
|
|
|
def asset_display_name
|
2014-10-10 23:35:38 +08:00
|
|
|
return nil unless asset
|
2021-09-23 00:20:17 +08:00
|
|
|
|
2011-02-01 09:57:29 +08:00
|
|
|
if asset.respond_to?(:title) && !asset.title.nil?
|
|
|
|
asset.title
|
|
|
|
elsif asset.is_a? Enrollment
|
|
|
|
asset.user.name
|
2014-10-01 21:23:18 +08:00
|
|
|
elsif asset.respond_to?(:name) && !asset.name.nil?
|
|
|
|
asset.name
|
2011-02-01 09:57:29 +08:00
|
|
|
else
|
|
|
|
asset_code
|
|
|
|
end
|
|
|
|
end
|
2012-11-16 07:34:57 +08:00
|
|
|
|
2011-02-01 09:57:29 +08:00
|
|
|
def context_code
|
|
|
|
"#{context_type.underscore}_#{context_id}" rescue nil
|
|
|
|
end
|
2012-11-16 07:34:57 +08:00
|
|
|
|
2020-06-24 04:35:54 +08:00
|
|
|
def readable_name(include_group_name: true)
|
2021-11-11 03:36:19 +08:00
|
|
|
if asset_code&.include?(":")
|
2021-11-16 04:49:40 +08:00
|
|
|
split = asset_code.split(":")
|
2015-11-19 22:43:43 +08:00
|
|
|
|
2021-11-12 01:20:36 +08:00
|
|
|
if split[1].match?(/course_\d+/)
|
2015-11-19 22:43:43 +08:00
|
|
|
case split[0]
|
|
|
|
when "announcements"
|
|
|
|
t("Course Announcements")
|
|
|
|
when "assignments"
|
|
|
|
t("Course Assignments")
|
|
|
|
when "calendar_feed"
|
|
|
|
t("Course Calendar")
|
|
|
|
when "collaborations"
|
|
|
|
t("Course Collaborations")
|
|
|
|
when "conferences"
|
|
|
|
t("Course Conferences")
|
|
|
|
when "files"
|
|
|
|
t("Course Files")
|
|
|
|
when "grades"
|
|
|
|
t("Course Grades")
|
|
|
|
when "home"
|
|
|
|
t("Course Home")
|
|
|
|
when "modules"
|
|
|
|
t("Course Modules")
|
|
|
|
when "outcomes"
|
|
|
|
t("Course Outcomes")
|
|
|
|
when "pages"
|
|
|
|
t("Course Pages")
|
|
|
|
when "quizzes"
|
|
|
|
t("Course Quizzes")
|
|
|
|
when "roster"
|
|
|
|
t("Course People")
|
|
|
|
when "speed_grader"
|
|
|
|
t("SpeedGrader")
|
|
|
|
when "syllabus"
|
|
|
|
t("Course Syllabus")
|
|
|
|
when "topics"
|
|
|
|
t("Course Discussions")
|
|
|
|
else
|
|
|
|
"Course #{split[0].titleize}"
|
|
|
|
end
|
|
|
|
elsif (match = split[1].match(/group_(\d+)/)) && (group = Group.where(id: match[1]).first)
|
|
|
|
case split[0]
|
|
|
|
when "announcements"
|
2020-06-24 04:35:54 +08:00
|
|
|
include_group_name ? t("%{group_name} - Group Announcements", group_name: group.name) : t("Group Announcements")
|
2015-11-19 22:43:43 +08:00
|
|
|
when "calendar_feed"
|
2020-06-24 04:35:54 +08:00
|
|
|
include_group_name ? t("%{group_name} - Group Calendar", group_name: group.name) : t("Group Calendar")
|
2015-11-19 22:43:43 +08:00
|
|
|
when "collaborations"
|
2020-06-24 04:35:54 +08:00
|
|
|
include_group_name ? t("%{group_name} - Group Collaborations", group_name: group.name) : t("Group Collaborations")
|
2015-11-19 22:43:43 +08:00
|
|
|
when "conferences"
|
2020-06-24 04:35:54 +08:00
|
|
|
include_group_name ? t("%{group_name} - Group Conferences", group_name: group.name) : t("Group Conferences")
|
2015-11-19 22:43:43 +08:00
|
|
|
when "files"
|
2020-06-24 04:35:54 +08:00
|
|
|
include_group_name ? t("%{group_name} - Group Files", group_name: group.name) : t("Group Files")
|
2015-11-19 22:43:43 +08:00
|
|
|
when "home"
|
2020-06-24 04:35:54 +08:00
|
|
|
include_group_name ? t("%{group_name} - Group Home", group_name: group.name) : t("Group Home")
|
2015-11-19 22:43:43 +08:00
|
|
|
when "pages"
|
2020-06-24 04:35:54 +08:00
|
|
|
include_group_name ? t("%{group_name} - Group Pages", group_name: group.name) : t("Group Pages")
|
2015-11-19 22:43:43 +08:00
|
|
|
when "roster"
|
2020-06-24 04:35:54 +08:00
|
|
|
include_group_name ? t("%{group_name} - Group People", group_name: group.name) : t("Group People")
|
2015-11-19 22:43:43 +08:00
|
|
|
when "topics"
|
2020-06-24 04:35:54 +08:00
|
|
|
include_group_name ? t("%{group_name} - Group Discussions", group_name: group.name) : t("Group Discussions")
|
2015-11-19 22:43:43 +08:00
|
|
|
else
|
2020-06-24 04:35:54 +08:00
|
|
|
"#{include_group_name ? "#{group.name} - " : ""}Group #{split[0].titleize}"
|
|
|
|
end
|
2021-11-12 01:20:36 +08:00
|
|
|
elsif split[1].match?(/user_\d+/)
|
2020-06-24 04:35:54 +08:00
|
|
|
case split[0]
|
|
|
|
when "files"
|
|
|
|
t("User Files")
|
|
|
|
else
|
|
|
|
display_name
|
2015-11-19 22:43:43 +08:00
|
|
|
end
|
2011-02-01 09:57:29 +08:00
|
|
|
else
|
|
|
|
display_name
|
|
|
|
end
|
|
|
|
else
|
|
|
|
re = Regexp.new("#{asset_code} - ")
|
|
|
|
display_name.nil? ? "" : display_name.gsub(re, "")
|
|
|
|
end
|
|
|
|
end
|
2012-11-16 07:34:57 +08:00
|
|
|
|
2011-02-01 09:57:29 +08:00
|
|
|
def asset
|
2016-06-16 02:59:20 +08:00
|
|
|
unless @asset
|
|
|
|
return nil unless asset_code
|
2021-09-23 00:20:17 +08:00
|
|
|
|
2021-10-20 05:23:50 +08:00
|
|
|
asset_code, = self.asset_code.split(":").reverse
|
2016-06-16 02:59:20 +08:00
|
|
|
@asset = Context.find_asset_by_asset_string(asset_code, context)
|
|
|
|
@asset ||= (match = asset_code.match(/enrollment_(\d+)/)) && Enrollment.where(id: match[1]).first
|
|
|
|
end
|
|
|
|
@asset
|
2011-02-01 09:57:29 +08:00
|
|
|
end
|
2012-11-16 07:34:57 +08:00
|
|
|
|
2011-05-26 06:16:01 +08:00
|
|
|
def asset_class_name
|
2014-10-01 21:23:18 +08:00
|
|
|
name = asset.class.name.underscore if asset
|
|
|
|
name = "Quiz" if name == "Quizzes::Quiz"
|
|
|
|
name
|
2011-05-26 06:16:01 +08:00
|
|
|
end
|
2012-11-16 07:34:57 +08:00
|
|
|
|
2018-08-23 09:39:43 +08:00
|
|
|
def self.get_correct_context(context, accessed_asset)
|
|
|
|
if accessed_asset[:category] == "files" && accessed_asset[:code]&.starts_with?("attachment")
|
|
|
|
attachment_id = accessed_asset[:code].match(/\A\w+_(\d+)\z/)[1]
|
2021-08-18 01:43:37 +08:00
|
|
|
asset = accessed_asset[:asset_for_root_account_id]
|
|
|
|
return asset.context if asset.is_a?(Attachment) && asset.id == attachment_id
|
|
|
|
|
2018-08-23 09:39:43 +08:00
|
|
|
Attachment.find_by(id: attachment_id)&.context
|
|
|
|
elsif context.is_a?(UserProfile)
|
2016-06-06 06:11:25 +08:00
|
|
|
context.user
|
|
|
|
elsif context.is_a?(AssessmentQuestion)
|
|
|
|
context.context
|
|
|
|
else
|
|
|
|
context
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2018-08-23 09:39:43 +08:00
|
|
|
def self.log(user, context, accessed_asset)
|
|
|
|
return unless user && accessed_asset[:code]
|
2021-08-18 01:43:37 +08:00
|
|
|
|
2018-08-23 09:39:43 +08:00
|
|
|
correct_context = get_correct_context(context, accessed_asset)
|
|
|
|
return unless correct_context && Context::CONTEXT_TYPES.include?(correct_context.class_name.to_sym)
|
2021-08-18 01:43:37 +08:00
|
|
|
|
2020-10-06 06:42:27 +08:00
|
|
|
GuardRail.activate(:secondary) do
|
2023-06-02 06:06:09 +08:00
|
|
|
@access = AssetUserAccess.where(user:,
|
2021-08-18 07:10:55 +08:00
|
|
|
asset_code: accessed_asset[:code],
|
|
|
|
context: correct_context).first_or_initialize
|
2019-03-22 03:56:18 +08:00
|
|
|
end
|
2018-08-23 09:39:43 +08:00
|
|
|
accessed_asset[:level] ||= "view"
|
2019-03-22 03:56:18 +08:00
|
|
|
@access.log correct_context, accessed_asset
|
2018-08-23 09:39:43 +08:00
|
|
|
end
|
|
|
|
|
|
|
|
def log(kontext, accessed)
|
2012-11-16 07:34:57 +08:00
|
|
|
self.asset_category ||= accessed[:category]
|
|
|
|
self.asset_group_code ||= accessed[:group_code]
|
|
|
|
self.membership_type ||= accessed[:membership_type]
|
2018-08-23 09:39:43 +08:00
|
|
|
self.context = kontext
|
2020-08-20 21:44:07 +08:00
|
|
|
self.updated_at = self.last_access = Time.now.utc
|
2012-11-16 07:34:57 +08:00
|
|
|
log_action(accessed[:level])
|
2020-08-20 21:44:07 +08:00
|
|
|
|
|
|
|
# manually call callbacks to avoid transactions. this saves a BEGIN/COMMIT per request
|
|
|
|
infer_defaults
|
2021-03-12 14:25:12 +08:00
|
|
|
infer_root_account_id(accessed[:asset_for_root_account_id])
|
2020-09-01 07:45:05 +08:00
|
|
|
|
2020-09-05 11:31:05 +08:00
|
|
|
if self.class.use_log_compaction_for_views? && eligible_for_log_path?
|
|
|
|
# Since this is JUST a view bump, we'll write it to the
|
|
|
|
# view log and let periodic jobs compact them later
|
|
|
|
# (this is intentionally trading off more latency for less I/O pressure)
|
|
|
|
AssetUserAccessLog.put_view(self)
|
|
|
|
else
|
|
|
|
save_without_transaction
|
|
|
|
end
|
2018-08-23 09:39:43 +08:00
|
|
|
self
|
2012-11-16 07:34:57 +08:00
|
|
|
end
|
|
|
|
|
2020-09-05 11:31:05 +08:00
|
|
|
def eligible_for_log_path?
|
|
|
|
# in general we want writes to go to the table right now.
|
|
|
|
# view count updates happen a LOT though, so if the setting is
|
|
|
|
# configured such that we're allowed to use the log path, check
|
|
|
|
# if this set of changes is "just" a view update.
|
|
|
|
change_hash = changes_to_save
|
|
|
|
updated_key_set = changes_to_save.keys.to_set
|
|
|
|
return false unless updated_key_set.include?("view_score")
|
|
|
|
return false unless (updated_key_set - Set.new(%w[updated_at last_access view_score])).empty?
|
2021-09-23 00:20:17 +08:00
|
|
|
|
2020-09-05 11:31:05 +08:00
|
|
|
# ASSUMPTION: All view_score updates are a single increment.
|
|
|
|
# If this is violated, rather than failing to capture, we should accept the
|
|
|
|
# write through the row update for now (by returning false from here).
|
|
|
|
view_delta = change_hash["view_score"].compact
|
|
|
|
# ^array with old and new value, which CAN be null, hence compact
|
|
|
|
return false if view_delta.empty?
|
2021-11-06 06:02:13 +08:00
|
|
|
return (view_delta[0] - 1.0).abs < Float::EPSILON if view_delta.size == 1
|
2021-09-23 00:20:17 +08:00
|
|
|
|
2020-09-05 11:31:05 +08:00
|
|
|
(view_delta[1] - view_delta[0]).abs == 1 # this is an increment, if true
|
|
|
|
end
|
|
|
|
|
2012-11-16 07:34:57 +08:00
|
|
|
def log_action(level)
|
|
|
|
increment(:view_score) if %w[view participate].include?(level)
|
|
|
|
increment(:participate_score) if %w[participate submit].include?(level)
|
|
|
|
|
|
|
|
if action_level != "participate"
|
|
|
|
self.action_level = (level == "submit") ? "participate" : level
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2020-09-05 11:31:05 +08:00
|
|
|
def self.use_log_compaction_for_views?
|
|
|
|
view_counting_method.to_s == "log"
|
|
|
|
end
|
|
|
|
|
|
|
|
def self.view_counting_method
|
|
|
|
Canvas::Plugin.find(:asset_user_access_logs).settings[:write_path]
|
|
|
|
end
|
|
|
|
|
2011-02-01 09:57:29 +08:00
|
|
|
def self.infer_asset(code)
|
2021-10-20 05:23:50 +08:00
|
|
|
asset_code, = code.split(":").reverse
|
|
|
|
Context.find_asset_by_asset_string(asset_code)
|
2011-02-01 09:57:29 +08:00
|
|
|
end
|
2012-11-16 07:34:57 +08:00
|
|
|
|
More accurate Access Report scores for Quizzes
This patch makes it that when viewing the Access Report for a course
student, their "Times Viewed" column will reflect the number of times
the student has browsed the quiz or any of its related resources
(like History, or attempt views), but not taken it.
While the "Times Participated" column will reflect the number of times
the student really took the quiz (1:1 mapping with the number of
submissions.)
TEST PLAN
---- ----
In both test cases, you'll need:
- a course with a student enrolled
- one browser session with a teacher logged in viewing the Access
Report of the student
- one browser session with the student logged
CASE: Normal quizzes
- Create a quiz with a few questions and unlimited attempts.
- Refresh the teacher tab, keep an eye on Times "Viewed" and
"Participated" columns
- As the student:
- Go to the quizzes page
- Go to the quiz page
- Refresh the teacher tab, and:
- ONLY the "Times Viewed" score should be incremented by 1
- As the student:
- Push the Take the Quiz
- Refresh the teacher tab, and:
- ONLY the "Times Participated" score should be incremented by 1
- As the student:
- Refresh the quiz page (while taking it)
- Refresh the teacher tab, and:
- NEITHER score should be incremented
CASE: OQAAT quizzes
The expected behaviour for OQAAT quizzes is that the entire attempt
counts as 1 participation, just like the normal quizzes.
Follow the same steps as above, but:
- While taking the quiz, and for every question page:
- Refresh the teacher tab and make sure that neither score is
incremented
OBLIGATORY REFERENCES
---------- ----------
- Acceptance criteria @ http://docs.kodoware.com/canvas/cnvs-5294
refs CNVS-5294
Change-Id: I55883b8edbf417edb42b9fd103e08369e0e9e63c
Reviewed-on: https://gerrit.instructure.com/26543
Tested-by: Jenkins <jenkins@instructure.com>
Reviewed-by: Derek DeVries <ddevries@instructure.com>
QA-Review: Jeremy Putnam <jeremyp@instructure.com>
Product-Review: Derek DeVries <ddevries@instructure.com>
2013-11-22 01:33:04 +08:00
|
|
|
# For Quizzes, we want the view score not to include the participation score
|
|
|
|
# so it reflects the number of times a student really just browsed the quiz.
|
|
|
|
def corrected_view_score
|
|
|
|
deductible_points = 0
|
|
|
|
|
2014-02-05 12:32:22 +08:00
|
|
|
if self.asset_group_code == "quizzes"
|
More accurate Access Report scores for Quizzes
This patch makes it that when viewing the Access Report for a course
student, their "Times Viewed" column will reflect the number of times
the student has browsed the quiz or any of its related resources
(like History, or attempt views), but not taken it.
While the "Times Participated" column will reflect the number of times
the student really took the quiz (1:1 mapping with the number of
submissions.)
TEST PLAN
---- ----
In both test cases, you'll need:
- a course with a student enrolled
- one browser session with a teacher logged in viewing the Access
Report of the student
- one browser session with the student logged
CASE: Normal quizzes
- Create a quiz with a few questions and unlimited attempts.
- Refresh the teacher tab, keep an eye on Times "Viewed" and
"Participated" columns
- As the student:
- Go to the quizzes page
- Go to the quiz page
- Refresh the teacher tab, and:
- ONLY the "Times Viewed" score should be incremented by 1
- As the student:
- Push the Take the Quiz
- Refresh the teacher tab, and:
- ONLY the "Times Participated" score should be incremented by 1
- As the student:
- Refresh the quiz page (while taking it)
- Refresh the teacher tab, and:
- NEITHER score should be incremented
CASE: OQAAT quizzes
The expected behaviour for OQAAT quizzes is that the entire attempt
counts as 1 participation, just like the normal quizzes.
Follow the same steps as above, but:
- While taking the quiz, and for every question page:
- Refresh the teacher tab and make sure that neither score is
incremented
OBLIGATORY REFERENCES
---------- ----------
- Acceptance criteria @ http://docs.kodoware.com/canvas/cnvs-5294
refs CNVS-5294
Change-Id: I55883b8edbf417edb42b9fd103e08369e0e9e63c
Reviewed-on: https://gerrit.instructure.com/26543
Tested-by: Jenkins <jenkins@instructure.com>
Reviewed-by: Derek DeVries <ddevries@instructure.com>
QA-Review: Jeremy Putnam <jeremyp@instructure.com>
Product-Review: Derek DeVries <ddevries@instructure.com>
2013-11-22 01:33:04 +08:00
|
|
|
deductible_points = participate_score || 0
|
|
|
|
end
|
|
|
|
|
2014-02-05 12:32:22 +08:00
|
|
|
self.view_score ||= 0
|
More accurate Access Report scores for Quizzes
This patch makes it that when viewing the Access Report for a course
student, their "Times Viewed" column will reflect the number of times
the student has browsed the quiz or any of its related resources
(like History, or attempt views), but not taken it.
While the "Times Participated" column will reflect the number of times
the student really took the quiz (1:1 mapping with the number of
submissions.)
TEST PLAN
---- ----
In both test cases, you'll need:
- a course with a student enrolled
- one browser session with a teacher logged in viewing the Access
Report of the student
- one browser session with the student logged
CASE: Normal quizzes
- Create a quiz with a few questions and unlimited attempts.
- Refresh the teacher tab, keep an eye on Times "Viewed" and
"Participated" columns
- As the student:
- Go to the quizzes page
- Go to the quiz page
- Refresh the teacher tab, and:
- ONLY the "Times Viewed" score should be incremented by 1
- As the student:
- Push the Take the Quiz
- Refresh the teacher tab, and:
- ONLY the "Times Participated" score should be incremented by 1
- As the student:
- Refresh the quiz page (while taking it)
- Refresh the teacher tab, and:
- NEITHER score should be incremented
CASE: OQAAT quizzes
The expected behaviour for OQAAT quizzes is that the entire attempt
counts as 1 participation, just like the normal quizzes.
Follow the same steps as above, but:
- While taking the quiz, and for every question page:
- Refresh the teacher tab and make sure that neither score is
incremented
OBLIGATORY REFERENCES
---------- ----------
- Acceptance criteria @ http://docs.kodoware.com/canvas/cnvs-5294
refs CNVS-5294
Change-Id: I55883b8edbf417edb42b9fd103e08369e0e9e63c
Reviewed-on: https://gerrit.instructure.com/26543
Tested-by: Jenkins <jenkins@instructure.com>
Reviewed-by: Derek DeVries <ddevries@instructure.com>
QA-Review: Jeremy Putnam <jeremyp@instructure.com>
Product-Review: Derek DeVries <ddevries@instructure.com>
2013-11-22 01:33:04 +08:00
|
|
|
self.view_score -= deductible_points
|
|
|
|
end
|
|
|
|
|
2020-08-25 05:14:17 +08:00
|
|
|
# Includes both the icon name and the associated screenreader label for the icon
|
2016-12-03 13:27:46 +08:00
|
|
|
ICON_MAP = {
|
2020-08-25 05:14:17 +08:00
|
|
|
announcements: ["icon-announcement", t("Announcement")].freeze,
|
|
|
|
assignments: ["icon-assignment", t("Assignment")].freeze,
|
|
|
|
calendar: ["icon-calendar-month", t("Calendar")].freeze,
|
2020-09-23 06:33:56 +08:00
|
|
|
collaborations: ["icon-document", t("Collaboration")].freeze,
|
|
|
|
conferences: ["icon-group", t("Conference")].freeze,
|
|
|
|
external_tools: ["icon-link", t("App")].freeze,
|
2020-08-25 05:14:17 +08:00
|
|
|
files: ["icon-download", t("File")].freeze,
|
|
|
|
grades: ["icon-gradebook", t("Grades")].freeze,
|
|
|
|
home: ["icon-home", t("Home")].freeze,
|
|
|
|
inbox: ["icon-message", t("Inbox")].freeze,
|
|
|
|
modules: ["icon-module", t("Module")].freeze,
|
|
|
|
outcomes: ["icon-outcomes", t("Outcome")].freeze,
|
|
|
|
pages: ["icon-document", t("Page")].freeze,
|
|
|
|
quizzes: ["icon-quiz", t("Quiz")].freeze,
|
|
|
|
roster: ["icon-user", t("People")].freeze,
|
|
|
|
syllabus: ["icon-syllabus", t("Syllabus")].freeze,
|
|
|
|
topics: ["icon-discussion", t("Discussion")].freeze,
|
|
|
|
wiki: ["icon-document", t("Page")].freeze
|
2016-12-03 13:27:46 +08:00
|
|
|
}.freeze
|
|
|
|
|
|
|
|
def icon
|
2020-09-23 06:33:56 +08:00
|
|
|
ICON_MAP[asset_category.to_sym]&.[](0) || "icon-question"
|
2020-08-25 05:14:17 +08:00
|
|
|
end
|
|
|
|
|
|
|
|
def readable_category
|
2020-09-23 06:33:56 +08:00
|
|
|
ICON_MAP[asset_category.to_sym]&.[](1) || ""
|
2016-12-03 13:27:46 +08:00
|
|
|
end
|
|
|
|
|
2023-04-26 01:02:27 +08:00
|
|
|
def self.expiration_date
|
2024-01-11 05:17:53 +08:00
|
|
|
2.years.ago
|
2023-04-26 01:02:27 +08:00
|
|
|
end
|
|
|
|
|
2024-01-11 05:17:53 +08:00
|
|
|
DELETE_BATCH_SIZE = 10_000
|
|
|
|
DELETE_BATCH_SLEEP = 5
|
|
|
|
|
2023-04-26 01:02:27 +08:00
|
|
|
def self.delete_old_records
|
2023-06-09 01:34:58 +08:00
|
|
|
loop do
|
2024-01-11 05:17:53 +08:00
|
|
|
count = AssetUserAccess.connection.with_max_update_limit(DELETE_BATCH_SIZE) do
|
|
|
|
where(last_access: ..expiration_date).limit(DELETE_BATCH_SIZE).delete_all
|
2023-04-26 01:02:27 +08:00
|
|
|
end
|
2023-06-09 01:34:58 +08:00
|
|
|
break if count.zero?
|
|
|
|
|
2024-01-11 05:17:53 +08:00
|
|
|
sleep(DELETE_BATCH_SLEEP) # rubocop:disable Lint/NoSleep
|
2023-04-26 01:02:27 +08:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2012-11-16 07:34:57 +08:00
|
|
|
private
|
|
|
|
|
|
|
|
def increment(attribute)
|
|
|
|
incremented_value = (send(attribute) || 0) + 1
|
2023-12-19 00:42:37 +08:00
|
|
|
send(:"#{attribute}=", incremented_value)
|
2012-11-16 07:34:57 +08:00
|
|
|
end
|
2011-02-01 09:57:29 +08:00
|
|
|
end
|