2020-10-27 00:50:13 +08:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2017-04-28 04:02:40 +08:00
|
|
|
#
|
|
|
|
# Copyright (C) 2012 - present Instructure, Inc.
|
|
|
|
#
|
|
|
|
# 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/>.
|
|
|
|
|
2012-12-13 03:14:17 +08:00
|
|
|
module DatesOverridable
|
2013-07-24 09:46:05 +08:00
|
|
|
attr_accessor :applied_overrides,
|
|
|
|
:overridden_for_user,
|
|
|
|
:overridden,
|
2016-10-18 23:43:09 +08:00
|
|
|
:has_no_overrides,
|
|
|
|
:has_too_many_overrides,
|
2024-07-10 01:16:40 +08:00
|
|
|
:preloaded_override_students,
|
2024-07-13 00:09:19 +08:00
|
|
|
:preloaded_overrides,
|
2024-07-12 01:31:37 +08:00
|
|
|
:preloaded_module_ids,
|
|
|
|
:preloaded_module_overrides
|
2013-02-05 08:51:39 +08:00
|
|
|
attr_writer :without_overrides
|
2021-09-23 00:25:11 +08:00
|
|
|
|
2014-09-24 04:23:30 +08:00
|
|
|
include DifferentiableAssignment
|
2012-12-20 04:38:12 +08:00
|
|
|
|
2016-09-08 23:28:13 +08:00
|
|
|
class NotOverriddenError < RuntimeError; end
|
|
|
|
|
2012-12-13 03:14:17 +08:00
|
|
|
def self.included(base)
|
2023-12-04 10:03:33 +08:00
|
|
|
base.has_many :assignment_overrides, dependent: :destroy, inverse_of: base.table_name.singularize, foreign_key: "#{base.table_name.singularize}_id"
|
|
|
|
base.has_many :active_assignment_overrides, -> { where(workflow_state: "active") }, class_name: "AssignmentOverride", inverse_of: base.table_name.singularize, foreign_key: "#{base.table_name.singularize}_id"
|
|
|
|
base.has_many :assignment_override_students, -> { where(workflow_state: "active") }, dependent: :destroy, foreign_key: "#{base.table_name.singularize}_id"
|
|
|
|
base.has_many :all_assignment_override_students, class_name: "AssignmentOverrideStudent", dependent: :destroy, foreign_key: "#{base.table_name.singularize}_id"
|
2014-02-12 06:11:25 +08:00
|
|
|
|
2016-01-07 04:18:06 +08:00
|
|
|
base.validates_associated :active_assignment_overrides
|
2012-12-29 05:02:21 +08:00
|
|
|
|
|
|
|
base.extend(ClassMethods)
|
2012-12-13 03:14:17 +08:00
|
|
|
end
|
|
|
|
|
2014-02-12 06:11:25 +08:00
|
|
|
def without_overrides
|
|
|
|
@without_overrides || self
|
|
|
|
end
|
|
|
|
|
2016-10-06 07:09:26 +08:00
|
|
|
def overridden_for(user, skip_clone: false)
|
2024-04-06 03:46:22 +08:00
|
|
|
# TODO: support Attachment in AssignmentOverrideApplicator (LF-1458)
|
|
|
|
return self if is_a?(Attachment)
|
2024-02-29 07:18:44 +08:00
|
|
|
|
2023-06-02 06:06:09 +08:00
|
|
|
AssignmentOverrideApplicator.assignment_overridden_for(self, user, skip_clone:)
|
2012-12-13 03:14:17 +08:00
|
|
|
end
|
|
|
|
|
2014-02-12 06:11:25 +08:00
|
|
|
# All overrides, not just dates
|
2016-02-02 05:21:58 +08:00
|
|
|
def overrides_for(user, opts = {})
|
|
|
|
overrides = AssignmentOverrideApplicator.overrides_for_assignment_and_user(self, user)
|
2016-02-18 16:24:07 +08:00
|
|
|
if opts[:ensure_set_not_empty]
|
|
|
|
overrides.select(&:set_not_empty?)
|
2016-02-02 05:21:58 +08:00
|
|
|
else
|
|
|
|
overrides
|
|
|
|
end
|
2014-02-12 06:11:25 +08:00
|
|
|
end
|
|
|
|
|
|
|
|
def overridden_for?(user)
|
|
|
|
overridden && (overridden_for_user == user)
|
2012-12-13 03:14:17 +08:00
|
|
|
end
|
|
|
|
|
|
|
|
def has_overrides?
|
2017-10-19 03:32:28 +08:00
|
|
|
if current_version?
|
2024-07-13 00:27:00 +08:00
|
|
|
preloaded_all_overrides ? preloaded_all_overrides.any?(&:active?) : all_assignment_overrides.active.exists?
|
2017-10-19 03:32:28 +08:00
|
|
|
else
|
|
|
|
# the old version's overrides might have be deleted too but it's probably more trouble than it's worth to check here
|
2024-07-13 00:27:00 +08:00
|
|
|
preloaded_all_overrides ? preloaded_all_overrides.any? : all_assignment_overrides.exists?
|
2017-10-19 03:32:28 +08:00
|
|
|
end
|
2012-12-13 03:14:17 +08:00
|
|
|
end
|
|
|
|
|
make fancy midnight work for assignment overrides
also fixes an issue where some dates display as "Friday at 11:59pm" instead
of just "Friday"
Also does a little bit of refactoring and spec backfilling for the
override list presenter. The override list presenter now returns a much
more friendly list of "due date" hashes to the outside world to make it
easier to consume in views. Views don't have to format the dates by
passing in a hash anymore.
test plan:
- specs should pass
- as a teacher, create an assignment with overrides using the web
form. In one of the overrides, enter a day like March 1 at 12am.
- save the overrides
- Make sure fancy midnight works for lock dates and due dates, but not
unlock dates (12:00 am unlock date should show up as 12:00 am, not
11:59 pm)
- on the assignment's show page, you should just see "Friday", meaning
that the assignment is due at 11:59 pm on March 1.
- The "fancy midnight" scheme should work correctly for
assignments,quizzes,and discussion topics, including the default due
dates.
- Be sure to check that the dates show up correctly on the
assignment,quiz, and discussion show pages.
- Be sure to make an override that has a blank due_at, lock_at, and
unlock_at, but has a default due date, lock date, and unlock date.
The overrides should not inherit from the default due date (fixes
CNVS-4216)
fixes CNVS-4216, CNVS-4004, CNVS-3890
Change-Id: I8b5e10c074eb2a237a1298cb7def0cb32d3dcb7f
Reviewed-on: https://gerrit.instructure.com/18142
QA-Review: Amber Taniuchi <amber@instructure.com>
Tested-by: Jenkins <jenkins@instructure.com>
Reviewed-by: Simon Williams <simon@instructure.com>
2013-03-06 00:04:59 +08:00
|
|
|
def has_active_overrides?
|
2016-09-17 01:54:19 +08:00
|
|
|
active_assignment_overrides.any?
|
make fancy midnight work for assignment overrides
also fixes an issue where some dates display as "Friday at 11:59pm" instead
of just "Friday"
Also does a little bit of refactoring and spec backfilling for the
override list presenter. The override list presenter now returns a much
more friendly list of "due date" hashes to the outside world to make it
easier to consume in views. Views don't have to format the dates by
passing in a hash anymore.
test plan:
- specs should pass
- as a teacher, create an assignment with overrides using the web
form. In one of the overrides, enter a day like March 1 at 12am.
- save the overrides
- Make sure fancy midnight works for lock dates and due dates, but not
unlock dates (12:00 am unlock date should show up as 12:00 am, not
11:59 pm)
- on the assignment's show page, you should just see "Friday", meaning
that the assignment is due at 11:59 pm on March 1.
- The "fancy midnight" scheme should work correctly for
assignments,quizzes,and discussion topics, including the default due
dates.
- Be sure to check that the dates show up correctly on the
assignment,quiz, and discussion show pages.
- Be sure to make an override that has a blank due_at, lock_at, and
unlock_at, but has a default due date, lock date, and unlock date.
The overrides should not inherit from the default due date (fixes
CNVS-4216)
fixes CNVS-4216, CNVS-4004, CNVS-3890
Change-Id: I8b5e10c074eb2a237a1298cb7def0cb32d3dcb7f
Reviewed-on: https://gerrit.instructure.com/18142
QA-Review: Amber Taniuchi <amber@instructure.com>
Tested-by: Jenkins <jenkins@instructure.com>
Reviewed-by: Simon Williams <simon@instructure.com>
2013-03-06 00:04:59 +08:00
|
|
|
end
|
|
|
|
|
2023-09-25 20:56:30 +08:00
|
|
|
def all_assignment_overrides
|
2024-06-05 03:14:55 +08:00
|
|
|
if Account.site_admin.feature_enabled? :selective_release_backend
|
2024-07-12 01:31:37 +08:00
|
|
|
assignment_overrides.or(AssignmentOverride.active.where(context_module_id: module_ids))
|
2023-09-25 20:56:30 +08:00
|
|
|
else
|
2023-09-29 21:03:43 +08:00
|
|
|
assignment_overrides.where.not(set_type: "Course")
|
2023-09-25 20:56:30 +08:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2024-07-13 00:27:00 +08:00
|
|
|
def preloaded_all_overrides
|
|
|
|
return nil if @preloaded_overrides.nil? || @preloaded_module_overrides.nil?
|
|
|
|
return @preloaded_overrides.reject { |ao| ao.set_type == "Course" } unless Account.site_admin.feature_enabled? :selective_release_backend
|
|
|
|
|
|
|
|
@preloaded_overrides + @preloaded_module_overrides
|
|
|
|
end
|
|
|
|
|
2024-03-06 05:42:15 +08:00
|
|
|
def visible_to_everyone
|
2024-06-05 03:14:55 +08:00
|
|
|
if Account.site_admin.feature_enabled? :selective_release_backend
|
2024-04-16 03:59:51 +08:00
|
|
|
if is_a?(DiscussionTopic)
|
|
|
|
# need to check if is_section_specific for ungraded discussions
|
|
|
|
# this column will eventually be deprecated and then this can be removed
|
2024-07-12 01:31:37 +08:00
|
|
|
course_overrides? || ((!only_visible_to_overrides && !is_section_specific) && (module_ids.empty? || (module_ids.any? && modules_without_overrides?)))
|
2024-04-16 03:59:51 +08:00
|
|
|
else
|
2024-07-12 01:31:37 +08:00
|
|
|
course_overrides? || (!only_visible_to_overrides && (module_ids.empty? || (module_ids.any? && modules_without_overrides?)))
|
2024-04-16 03:59:51 +08:00
|
|
|
end
|
2024-03-06 05:42:15 +08:00
|
|
|
else
|
|
|
|
!only_visible_to_overrides
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2023-09-25 20:56:30 +08:00
|
|
|
def assignment_context_modules
|
add sub_assignment data to assignment index
closes VICE-4293
flag=discussion_checkpoints
Test Plan
1. Create a checkpointed discussion
2. Have due dates or due date overrides on the checkpoints
3. Open assignment index
4. Verify that the API gets the dates in All_dates
5. Should display multiple due dates on the index
5a. The front end for this should be addressed
in VICE-4290
To add checkpoints you can use this graphQL mutation in
graphiql
But set the DiscussionTopicId, and setId to the appropriate
values
mutation MyMutation {
__typename
updateDiscussionTopic(
input: {discussionTopicId: "108", assignment: {forCheckpoints: true}, checkpoints: [{checkpointLabel: reply_to_topic, dates: {type: override, dueAt: "2024-06-14T17:19:38Z", setType: CourseSection, setId: 6}, pointsPossible: 10, repliesRequired: 1}, {checkpointLabel: reply_to_entry, dates: {type: override, dueAt: "2024-06-24T17:19:38Z", setType: CourseSection, setId: 6}, pointsPossible: 10, repliesRequired: 1}]}
) {
discussionTopic {
_id
assignment {
checkpoints {
name
}
}
}
}
}
Change-Id: Ia5a21aea9809f746bde6ae48e47bffe1c69d17b2
Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/350494
Tested-by: Service Cloud Jenkins <svc.cloudjenkins@instructure.com>
QA-Review: Caleb Guanzon <cguanzon@instructure.com>
Reviewed-by: Omar Soto-Fortuño <omar.soto@instructure.com>
Product-Review: Caleb Guanzon <cguanzon@instructure.com>
2024-06-19 03:56:54 +08:00
|
|
|
if is_a?(AbstractAssignment) && quiz.present?
|
2024-04-06 03:46:22 +08:00
|
|
|
# if it's another learning object's assignment, the context module content tags are attached to the learning object
|
2024-02-01 07:06:48 +08:00
|
|
|
ContextModule.not_deleted.where(id: quiz.context_module_tags.select(:context_module_id))
|
add sub_assignment data to assignment index
closes VICE-4293
flag=discussion_checkpoints
Test Plan
1. Create a checkpointed discussion
2. Have due dates or due date overrides on the checkpoints
3. Open assignment index
4. Verify that the API gets the dates in All_dates
5. Should display multiple due dates on the index
5a. The front end for this should be addressed
in VICE-4290
To add checkpoints you can use this graphQL mutation in
graphiql
But set the DiscussionTopicId, and setId to the appropriate
values
mutation MyMutation {
__typename
updateDiscussionTopic(
input: {discussionTopicId: "108", assignment: {forCheckpoints: true}, checkpoints: [{checkpointLabel: reply_to_topic, dates: {type: override, dueAt: "2024-06-14T17:19:38Z", setType: CourseSection, setId: 6}, pointsPossible: 10, repliesRequired: 1}, {checkpointLabel: reply_to_entry, dates: {type: override, dueAt: "2024-06-24T17:19:38Z", setType: CourseSection, setId: 6}, pointsPossible: 10, repliesRequired: 1}]}
) {
discussionTopic {
_id
assignment {
checkpoints {
name
}
}
}
}
}
Change-Id: Ia5a21aea9809f746bde6ae48e47bffe1c69d17b2
Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/350494
Tested-by: Service Cloud Jenkins <svc.cloudjenkins@instructure.com>
QA-Review: Caleb Guanzon <cguanzon@instructure.com>
Reviewed-by: Omar Soto-Fortuño <omar.soto@instructure.com>
Product-Review: Caleb Guanzon <cguanzon@instructure.com>
2024-06-19 03:56:54 +08:00
|
|
|
elsif is_a?(AbstractAssignment) && discussion_topic.present?
|
2024-04-06 03:46:22 +08:00
|
|
|
ContextModule.not_deleted.where(id: discussion_topic.context_module_tags.select(:context_module_id))
|
add sub_assignment data to assignment index
closes VICE-4293
flag=discussion_checkpoints
Test Plan
1. Create a checkpointed discussion
2. Have due dates or due date overrides on the checkpoints
3. Open assignment index
4. Verify that the API gets the dates in All_dates
5. Should display multiple due dates on the index
5a. The front end for this should be addressed
in VICE-4290
To add checkpoints you can use this graphQL mutation in
graphiql
But set the DiscussionTopicId, and setId to the appropriate
values
mutation MyMutation {
__typename
updateDiscussionTopic(
input: {discussionTopicId: "108", assignment: {forCheckpoints: true}, checkpoints: [{checkpointLabel: reply_to_topic, dates: {type: override, dueAt: "2024-06-14T17:19:38Z", setType: CourseSection, setId: 6}, pointsPossible: 10, repliesRequired: 1}, {checkpointLabel: reply_to_entry, dates: {type: override, dueAt: "2024-06-24T17:19:38Z", setType: CourseSection, setId: 6}, pointsPossible: 10, repliesRequired: 1}]}
) {
discussionTopic {
_id
assignment {
checkpoints {
name
}
}
}
}
}
Change-Id: Ia5a21aea9809f746bde6ae48e47bffe1c69d17b2
Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/350494
Tested-by: Service Cloud Jenkins <svc.cloudjenkins@instructure.com>
QA-Review: Caleb Guanzon <cguanzon@instructure.com>
Reviewed-by: Omar Soto-Fortuño <omar.soto@instructure.com>
Product-Review: Caleb Guanzon <cguanzon@instructure.com>
2024-06-19 03:56:54 +08:00
|
|
|
elsif is_a?(AbstractAssignment) && wiki_page.present? # wiki pages can have assignments through mastery paths
|
2024-04-06 03:46:22 +08:00
|
|
|
ContextModule.not_deleted.where(id: wiki_page.context_module_tags.select(:context_module_id))
|
2024-02-01 07:06:48 +08:00
|
|
|
else
|
|
|
|
ContextModule.not_deleted.where(id: context_module_tags.select(:context_module_id))
|
|
|
|
end
|
2023-09-25 20:56:30 +08:00
|
|
|
end
|
|
|
|
|
2024-07-12 01:31:37 +08:00
|
|
|
def modules_without_overrides?
|
|
|
|
module_ids_with_overrides = context_module_overrides.map(&:context_module_id)
|
|
|
|
module_ids_without_overrides = module_ids.reject { |module_id| module_ids_with_overrides.include?(module_id) }
|
|
|
|
module_ids_without_overrides.any?
|
2024-03-06 05:42:15 +08:00
|
|
|
end
|
|
|
|
|
2024-07-10 01:16:40 +08:00
|
|
|
def self.preload_override_data_for_objects(learning_objects)
|
|
|
|
return unless Account.site_admin.feature_enabled? :selective_release_backend
|
|
|
|
return if learning_objects.empty?
|
|
|
|
|
2024-07-13 00:09:19 +08:00
|
|
|
preload_overrides(learning_objects)
|
2024-07-11 04:59:22 +08:00
|
|
|
preload_module_ids(learning_objects)
|
2024-07-12 01:31:37 +08:00
|
|
|
preload_module_overrides(learning_objects)
|
2024-07-10 01:16:40 +08:00
|
|
|
end
|
|
|
|
|
2024-07-13 00:09:19 +08:00
|
|
|
def self.preload_overrides(learning_objects)
|
|
|
|
learning_objects_overrides = AssignmentOverride.where(assignment_id: learning_objects.select { |lo| lo.is_a?(Assignment) }.map(&:id))
|
|
|
|
.or(AssignmentOverride.where(quiz_id: learning_objects.select { |lo| lo.is_a?(Quizzes::Quiz) }.map(&:id)))
|
|
|
|
.or(AssignmentOverride.where(discussion_topic_id: learning_objects.select { |lo| lo.is_a?(DiscussionTopic) }.map(&:id)))
|
|
|
|
.or(AssignmentOverride.where(wiki_page_id: learning_objects.select { |lo| lo.is_a?(WikiPage) }.map(&:id)))
|
2024-07-10 01:16:40 +08:00
|
|
|
learning_objects.each do |lo|
|
2024-07-13 00:09:19 +08:00
|
|
|
lo.preloaded_overrides = if lo.is_a?(AbstractAssignment)
|
|
|
|
learning_objects_overrides.select { |ao| ao.assignment_id == lo.id }
|
|
|
|
elsif lo.is_a?(Quizzes::Quiz)
|
|
|
|
learning_objects_overrides.select { |ao| ao.quiz_id == lo.id }
|
|
|
|
elsif lo.is_a?(DiscussionTopic)
|
|
|
|
learning_objects_overrides.select { |ao| ao.discussion_topic_id == lo.id }
|
|
|
|
elsif lo.is_a?(WikiPage)
|
|
|
|
learning_objects_overrides.select { |ao| ao.wiki_page_id == lo.id }
|
|
|
|
end
|
2024-07-10 01:16:40 +08:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2024-07-11 04:59:22 +08:00
|
|
|
def self.preload_module_ids(learning_objects)
|
|
|
|
assignments = learning_objects.select { |lo| lo.is_a?(AbstractAssignment) }
|
2024-07-12 05:46:18 +08:00
|
|
|
quizzes_with_assignments = Quizzes::Quiz.where(assignment_id: assignments.map(&:id))
|
|
|
|
quizzes = learning_objects.select { |lo| lo.is_a?(Quizzes::Quiz) } + quizzes_with_assignments
|
|
|
|
discussions_with_assignments = DiscussionTopic.where(assignment_id: assignments.map(&:id))
|
|
|
|
discussion_topics = learning_objects.select { |lo| lo.is_a?(DiscussionTopic) } + discussions_with_assignments
|
|
|
|
pages_with_assignments = WikiPage.where(assignment_id: assignments.map(&:id))
|
|
|
|
wiki_pages = learning_objects.select { |lo| lo.is_a?(WikiPage) } + pages_with_assignments
|
2024-07-11 04:59:22 +08:00
|
|
|
tags_scope = ContentTag.not_deleted.where(tag_type: "context_module")
|
|
|
|
module_ids = tags_scope.where(content_type: "Assignment", content_id: assignments.map(&:id))
|
|
|
|
.or(tags_scope.where(content_type: "Quizzes::Quiz", content_id: quizzes.map(&:id)))
|
|
|
|
.or(tags_scope.where(content_type: "DiscussionTopic", content_id: discussion_topics.map(&:id)))
|
|
|
|
.or(tags_scope.where(content_type: "WikiPage", content_id: wiki_pages.map(&:id)))
|
|
|
|
.distinct
|
|
|
|
.pluck(:content_type, :content_id, :context_module_id)
|
|
|
|
learning_objects.each do |lo|
|
|
|
|
lo.preloaded_module_ids = if lo.is_a?(Quizzes::Quiz)
|
|
|
|
module_ids.select { |m| m[0] == "Quizzes::Quiz" && m[1] == lo.id }.map(&:last)
|
|
|
|
elsif lo.is_a?(DiscussionTopic)
|
|
|
|
module_ids.select { |m| m[0] == "DiscussionTopic" && m[1] == lo.id }.map(&:last)
|
|
|
|
elsif lo.is_a?(WikiPage)
|
|
|
|
module_ids.select { |m| m[0] == "WikiPage" && m[1] == lo.id }.map(&:last)
|
2024-07-12 05:46:18 +08:00
|
|
|
elsif lo.is_a?(AbstractAssignment) && quizzes_with_assignments.map(&:assignment_id).include?(lo.id)
|
|
|
|
quiz_id = quizzes_with_assignments.find { |q| q.assignment_id == lo.id }.id
|
|
|
|
module_ids.select { |m| m[0] == "Quizzes::Quiz" && m[1] == quiz_id }.map(&:last)
|
|
|
|
elsif lo.is_a?(AbstractAssignment) && discussions_with_assignments.map(&:assignment_id).include?(lo.id)
|
|
|
|
discussion_id = discussions_with_assignments.find { |d| d.assignment_id == lo.id }.id
|
|
|
|
module_ids.select { |m| m[0] == "DiscussionTopic" && m[1] == discussion_id }.map(&:last)
|
|
|
|
elsif lo.is_a?(AbstractAssignment) && pages_with_assignments.map(&:assignment_id).include?(lo.id)
|
|
|
|
page_id = pages_with_assignments.find { |p| p.assignment_id == lo.id }.id
|
|
|
|
module_ids.select { |m| m[0] == "WikiPage" && m[1] == page_id }.map(&:last)
|
2024-07-11 04:59:22 +08:00
|
|
|
else
|
|
|
|
module_ids.select { |m| m[0] == "Assignment" && m[1] == lo.id }.map(&:last)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2024-07-12 01:31:37 +08:00
|
|
|
def self.preload_module_overrides(learning_objects)
|
|
|
|
all_module_ids = learning_objects.map(&:module_ids).flatten.uniq
|
|
|
|
all_module_overrides = AssignmentOverride.active.where(context_module_id: all_module_ids)
|
|
|
|
learning_objects.each do |lo|
|
|
|
|
lo.preloaded_module_overrides = all_module_overrides.select { |ao| lo.module_ids.include?(ao.context_module_id) }
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2024-07-10 01:16:40 +08:00
|
|
|
def course_overrides?
|
2024-07-13 00:09:19 +08:00
|
|
|
return assignment_overrides.active.where(set_type: "Course").exists? if @preloaded_overrides.nil?
|
2024-07-10 01:16:40 +08:00
|
|
|
|
2024-07-13 00:09:19 +08:00
|
|
|
@preloaded_overrides.any? { |ao| ao.set_type == "Course" && ao.active? }
|
2024-07-10 01:16:40 +08:00
|
|
|
end
|
|
|
|
|
2024-07-11 04:59:22 +08:00
|
|
|
def module_ids
|
|
|
|
return assignment_context_modules.pluck(:id) if @preloaded_module_ids.nil?
|
|
|
|
|
|
|
|
@preloaded_module_ids
|
|
|
|
end
|
|
|
|
|
2024-07-12 01:31:37 +08:00
|
|
|
def context_module_overrides
|
|
|
|
return AssignmentOverride.active.where(context_module_id: module_ids) if @preloaded_module_overrides.nil?
|
|
|
|
|
|
|
|
@preloaded_module_overrides
|
|
|
|
end
|
|
|
|
|
2014-02-12 06:11:25 +08:00
|
|
|
def multiple_due_dates?
|
|
|
|
if overridden
|
|
|
|
!!multiple_due_dates_apply_to?(overridden_for_user)
|
|
|
|
else
|
2016-09-08 23:28:13 +08:00
|
|
|
raise NotOverriddenError, "#{self.class.name} has not been overridden"
|
2012-12-13 03:14:17 +08:00
|
|
|
end
|
2014-02-12 06:11:25 +08:00
|
|
|
end
|
2012-12-13 03:14:17 +08:00
|
|
|
|
2014-02-12 06:11:25 +08:00
|
|
|
def multiple_due_dates_apply_to?(user)
|
|
|
|
return false if context.user_has_been_student?(user)
|
2012-12-13 03:14:17 +08:00
|
|
|
|
2014-02-12 06:11:25 +08:00
|
|
|
if context.user_has_been_observer?(user)
|
|
|
|
observed_student_due_dates(user).length > 1
|
|
|
|
elsif context.user_has_been_admin?(user)
|
|
|
|
dates = all_dates_visible_to(user)
|
|
|
|
dates && dates.map { |hash| self.class.due_date_compare_value(hash[:due_at]) }.uniq.size > 1
|
2012-12-13 03:14:17 +08:00
|
|
|
elsif context.user_has_no_enrollments?(user)
|
2014-02-12 06:11:25 +08:00
|
|
|
all_due_dates.length > 1
|
2012-12-13 03:14:17 +08:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def all_due_dates
|
2024-07-13 00:27:00 +08:00
|
|
|
due_at_overrides = preloaded_all_overrides ? preloaded_all_overrides.select { |ao| ao.active? && ao.due_at_overridden } : all_assignment_overrides.active.overriding_due_at
|
2014-12-04 00:36:34 +08:00
|
|
|
dates = due_at_overrides.map(&:as_hash)
|
|
|
|
dates << base_due_date_hash unless differentiated_assignments_applies?
|
|
|
|
dates
|
2014-04-17 03:45:28 +08:00
|
|
|
end
|
|
|
|
|
2016-02-03 07:32:04 +08:00
|
|
|
# returns a hash of observer, student, or admin to course ids.
|
|
|
|
# the observer bucket is additionally a hash with the values being a set
|
|
|
|
# of the users they observer (possibly including nil, for unassociated observers)
|
|
|
|
# note that #include?(course_id) will work equivalently on a Hash (of observers)
|
|
|
|
# or an array (of admins or students)
|
|
|
|
def self.precache_enrollments_for_multiple_assignments(assignments, user)
|
|
|
|
courses_user_has_been_enrolled_in = { observer: {}, student: [], admin: [] }
|
|
|
|
current_shard = Shard.current
|
|
|
|
Shard.partition_by_shard(assignments) do |shard_assignments|
|
|
|
|
Enrollment.where(course_id: shard_assignments.map(&:context), user_id: user)
|
|
|
|
.active
|
2017-03-14 06:08:22 +08:00
|
|
|
.distinct.
|
2016-02-03 07:32:04 +08:00
|
|
|
# duplicate the subquery logic of ObserverEnrollment.observed_users, where it verifies the observee exists
|
|
|
|
where("associated_user_id IS NULL OR EXISTS (
|
|
|
|
SELECT 1 FROM #{Enrollment.quoted_table_name} e2
|
|
|
|
WHERE e2.type IN ('StudentEnrollment', 'StudentViewEnrollment')
|
|
|
|
AND e2.workflow_state NOT IN ('rejected', 'completed', 'deleted', 'inactive')
|
|
|
|
AND e2.user_id=enrollments.associated_user_id
|
|
|
|
AND e2.course_id=enrollments.course_id)")
|
|
|
|
.pluck(:course_id, :type, :associated_user_id).each do |(course_id, type, associated_user_id)|
|
|
|
|
relative_course_id = Shard.relative_id_for(course_id, Shard.current, current_shard)
|
|
|
|
bucket = case type
|
|
|
|
when "ObserverEnrollment" then :observer
|
|
|
|
when "StudentEnrollment", "StudentViewEnrollment" then :student
|
|
|
|
# when 'TeacherEnrollment', 'TaEnrollment', 'DesignerEnrollment' then :admin
|
|
|
|
else; :admin
|
|
|
|
end
|
|
|
|
if bucket == :observer
|
|
|
|
observees = (courses_user_has_been_enrolled_in[bucket][relative_course_id] ||= Set.new)
|
|
|
|
observees << Shard.relative_id_for(associated_user_id, Shard.current, current_shard)
|
|
|
|
else
|
|
|
|
courses_user_has_been_enrolled_in[bucket] << relative_course_id
|
|
|
|
end
|
2014-04-17 03:45:28 +08:00
|
|
|
end
|
2013-12-05 06:09:17 +08:00
|
|
|
end
|
2016-02-03 07:32:04 +08:00
|
|
|
courses_user_has_been_enrolled_in
|
|
|
|
end
|
|
|
|
|
|
|
|
def all_dates_visible_to(user, courses_user_has_been_enrolled_in: nil)
|
|
|
|
return all_due_dates if user.nil?
|
|
|
|
|
|
|
|
if courses_user_has_been_enrolled_in
|
|
|
|
if courses_user_has_been_enrolled_in[:observer][context_id].try(:any?)
|
|
|
|
observed_student_due_dates(user, courses_user_has_been_enrolled_in[:observer][context_id].to_a)
|
|
|
|
elsif courses_user_has_been_enrolled_in[:student].include?(context_id) ||
|
|
|
|
courses_user_has_been_enrolled_in[:admin].include?(context_id) ||
|
|
|
|
courses_user_has_been_enrolled_in[:observer].include?(context_id)
|
|
|
|
overrides = overrides_for(user)
|
|
|
|
overrides = overrides.map(&:as_hash)
|
|
|
|
if !differentiated_assignments_applies? &&
|
|
|
|
(overrides.empty? || courses_user_has_been_enrolled_in[:admin].include?(context_id))
|
|
|
|
overrides << base_due_date_hash
|
|
|
|
end
|
|
|
|
overrides
|
|
|
|
else
|
|
|
|
all_due_dates
|
|
|
|
end
|
|
|
|
elsif ObserverEnrollment.observed_students(context, user).any?
|
|
|
|
observed_student_due_dates(user)
|
|
|
|
elsif context.user_has_been_student?(user) ||
|
|
|
|
context.user_has_been_admin?(user) ||
|
|
|
|
context.user_has_been_observer?(user)
|
|
|
|
overrides = overrides_for(user)
|
|
|
|
overrides = overrides.map(&:as_hash)
|
|
|
|
if !differentiated_assignments_applies? && (overrides.empty? || context.user_has_been_admin?(user))
|
|
|
|
overrides << base_due_date_hash
|
|
|
|
end
|
|
|
|
overrides
|
2021-11-22 23:49:53 +08:00
|
|
|
else
|
2016-02-03 07:32:04 +08:00
|
|
|
all_due_dates
|
|
|
|
end
|
make fancy midnight work for assignment overrides
also fixes an issue where some dates display as "Friday at 11:59pm" instead
of just "Friday"
Also does a little bit of refactoring and spec backfilling for the
override list presenter. The override list presenter now returns a much
more friendly list of "due date" hashes to the outside world to make it
easier to consume in views. Views don't have to format the dates by
passing in a hash anymore.
test plan:
- specs should pass
- as a teacher, create an assignment with overrides using the web
form. In one of the overrides, enter a day like March 1 at 12am.
- save the overrides
- Make sure fancy midnight works for lock dates and due dates, but not
unlock dates (12:00 am unlock date should show up as 12:00 am, not
11:59 pm)
- on the assignment's show page, you should just see "Friday", meaning
that the assignment is due at 11:59 pm on March 1.
- The "fancy midnight" scheme should work correctly for
assignments,quizzes,and discussion topics, including the default due
dates.
- Be sure to check that the dates show up correctly on the
assignment,quiz, and discussion show pages.
- Be sure to make an override that has a blank due_at, lock_at, and
unlock_at, but has a default due date, lock date, and unlock date.
The overrides should not inherit from the default due date (fixes
CNVS-4216)
fixes CNVS-4216, CNVS-4004, CNVS-3890
Change-Id: I8b5e10c074eb2a237a1298cb7def0cb32d3dcb7f
Reviewed-on: https://gerrit.instructure.com/18142
QA-Review: Amber Taniuchi <amber@instructure.com>
Tested-by: Jenkins <jenkins@instructure.com>
Reviewed-by: Simon Williams <simon@instructure.com>
2013-03-06 00:04:59 +08:00
|
|
|
end
|
|
|
|
|
2016-02-03 07:32:04 +08:00
|
|
|
def observed_student_due_dates(user, observed_student_ids = nil)
|
|
|
|
observed_students = if observed_student_ids
|
|
|
|
User.find(observed_student_ids)
|
|
|
|
else
|
|
|
|
ObserverEnrollment.observed_students(context, user).keys
|
|
|
|
end
|
|
|
|
dates = observed_students.map do |student|
|
2014-02-12 06:11:25 +08:00
|
|
|
all_dates_visible_to(student)
|
|
|
|
end
|
|
|
|
dates.flatten.uniq
|
2012-12-13 03:14:17 +08:00
|
|
|
end
|
|
|
|
|
2013-08-21 03:33:02 +08:00
|
|
|
def dates_hash_visible_to(user)
|
|
|
|
all_dates = all_dates_visible_to(user)
|
|
|
|
|
2014-02-12 06:11:25 +08:00
|
|
|
if all_dates
|
2014-04-17 03:45:28 +08:00
|
|
|
# remove base if all sections are set
|
2015-02-07 02:13:33 +08:00
|
|
|
overrides = all_dates.select { |d| d[:set_type] == "CourseSection" }
|
2015-06-26 03:35:23 +08:00
|
|
|
if overrides.count > 0 && overrides.count == context.active_section_count
|
2014-02-12 06:11:25 +08:00
|
|
|
all_dates.delete_if { |d| d[:base] }
|
|
|
|
end
|
2013-08-21 03:33:02 +08:00
|
|
|
|
2014-02-12 06:11:25 +08:00
|
|
|
formatted_dates_hash(all_dates)
|
|
|
|
else
|
|
|
|
[due_date_hash]
|
|
|
|
end
|
2014-02-13 17:23:06 +08:00
|
|
|
end
|
|
|
|
|
2015-10-07 01:39:07 +08:00
|
|
|
def teacher_due_date_for_display(user)
|
|
|
|
ao = overridden_for user
|
2018-04-24 05:33:03 +08:00
|
|
|
due_at || ao.due_at || all_due_dates.dig(0, :due_at)
|
2015-10-07 01:39:07 +08:00
|
|
|
end
|
|
|
|
|
2014-02-13 17:23:06 +08:00
|
|
|
def formatted_dates_hash(dates)
|
2018-04-24 05:33:03 +08:00
|
|
|
return [] if dates.blank?
|
2014-05-29 04:54:41 +08:00
|
|
|
|
2014-02-13 17:23:06 +08:00
|
|
|
dates = dates.sort_by do |date|
|
2013-08-21 03:33:02 +08:00
|
|
|
due_at = date[:due_at]
|
2014-03-18 04:54:26 +08:00
|
|
|
[due_at.present? ? CanvasSort::First : CanvasSort::Last, due_at.presence || CanvasSort::First]
|
2013-08-21 03:33:02 +08:00
|
|
|
end
|
|
|
|
|
2020-04-25 02:36:50 +08:00
|
|
|
dates.map { |h| h.slice(:id, :due_at, :unlock_at, :lock_at, :title, :base, :set_type, :set_id) }
|
2013-08-21 03:33:02 +08:00
|
|
|
end
|
|
|
|
|
2012-12-13 03:14:17 +08:00
|
|
|
def due_date_hash
|
2023-06-02 06:06:09 +08:00
|
|
|
hash = { due_at:, unlock_at:, lock_at: }
|
add sub_assignment data to assignment index
closes VICE-4293
flag=discussion_checkpoints
Test Plan
1. Create a checkpointed discussion
2. Have due dates or due date overrides on the checkpoints
3. Open assignment index
4. Verify that the API gets the dates in All_dates
5. Should display multiple due dates on the index
5a. The front end for this should be addressed
in VICE-4290
To add checkpoints you can use this graphQL mutation in
graphiql
But set the DiscussionTopicId, and setId to the appropriate
values
mutation MyMutation {
__typename
updateDiscussionTopic(
input: {discussionTopicId: "108", assignment: {forCheckpoints: true}, checkpoints: [{checkpointLabel: reply_to_topic, dates: {type: override, dueAt: "2024-06-14T17:19:38Z", setType: CourseSection, setId: 6}, pointsPossible: 10, repliesRequired: 1}, {checkpointLabel: reply_to_entry, dates: {type: override, dueAt: "2024-06-24T17:19:38Z", setType: CourseSection, setId: 6}, pointsPossible: 10, repliesRequired: 1}]}
) {
discussionTopic {
_id
assignment {
checkpoints {
name
}
}
}
}
}
Change-Id: Ia5a21aea9809f746bde6ae48e47bffe1c69d17b2
Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/350494
Tested-by: Service Cloud Jenkins <svc.cloudjenkins@instructure.com>
QA-Review: Caleb Guanzon <cguanzon@instructure.com>
Reviewed-by: Omar Soto-Fortuño <omar.soto@instructure.com>
Product-Review: Caleb Guanzon <cguanzon@instructure.com>
2024-06-19 03:56:54 +08:00
|
|
|
if is_a?(AbstractAssignment)
|
2021-11-16 04:42:40 +08:00
|
|
|
hash[:all_day] = all_day
|
|
|
|
hash[:all_day_date] = all_day_date
|
make fancy midnight work for assignment overrides
also fixes an issue where some dates display as "Friday at 11:59pm" instead
of just "Friday"
Also does a little bit of refactoring and spec backfilling for the
override list presenter. The override list presenter now returns a much
more friendly list of "due date" hashes to the outside world to make it
easier to consume in views. Views don't have to format the dates by
passing in a hash anymore.
test plan:
- specs should pass
- as a teacher, create an assignment with overrides using the web
form. In one of the overrides, enter a day like March 1 at 12am.
- save the overrides
- Make sure fancy midnight works for lock dates and due dates, but not
unlock dates (12:00 am unlock date should show up as 12:00 am, not
11:59 pm)
- on the assignment's show page, you should just see "Friday", meaning
that the assignment is due at 11:59 pm on March 1.
- The "fancy midnight" scheme should work correctly for
assignments,quizzes,and discussion topics, including the default due
dates.
- Be sure to check that the dates show up correctly on the
assignment,quiz, and discussion show pages.
- Be sure to make an override that has a blank due_at, lock_at, and
unlock_at, but has a default due date, lock date, and unlock date.
The overrides should not inherit from the default due date (fixes
CNVS-4216)
fixes CNVS-4216, CNVS-4004, CNVS-3890
Change-Id: I8b5e10c074eb2a237a1298cb7def0cb32d3dcb7f
Reviewed-on: https://gerrit.instructure.com/18142
QA-Review: Amber Taniuchi <amber@instructure.com>
Tested-by: Jenkins <jenkins@instructure.com>
Reviewed-by: Simon Williams <simon@instructure.com>
2013-03-06 00:04:59 +08:00
|
|
|
elsif assignment
|
2021-11-16 04:42:40 +08:00
|
|
|
hash[:all_day] = assignment.all_day
|
|
|
|
hash[:all_day_date] = assignment.all_day_date
|
2012-12-13 03:14:17 +08:00
|
|
|
end
|
|
|
|
|
2021-09-29 08:07:43 +08:00
|
|
|
if @applied_overrides && (override = @applied_overrides.find { |o| o.due_at == due_at })
|
2012-12-13 03:14:17 +08:00
|
|
|
hash[:override] = override
|
|
|
|
hash[:title] = override.title
|
2020-04-25 02:36:50 +08:00
|
|
|
hash[:set_type] = override.set_type
|
|
|
|
hash[:set_id] = override.set_id
|
2012-12-13 03:14:17 +08:00
|
|
|
end
|
|
|
|
|
|
|
|
hash
|
|
|
|
end
|
|
|
|
|
2014-02-12 06:11:25 +08:00
|
|
|
def base_due_date_hash
|
|
|
|
without_overrides.due_date_hash.merge(base: true)
|
2012-12-29 05:02:21 +08:00
|
|
|
end
|
|
|
|
|
2024-07-16 02:31:42 +08:00
|
|
|
def override_aware_due_date_hash(user, user_is_admin: false, assignment_object: self)
|
|
|
|
hash = {}
|
2024-07-18 04:35:09 +08:00
|
|
|
if user_is_admin && assignment_object.has_too_many_overrides && !(assignment_object.is_a?(AbstractAssignment) && assignment_object.has_sub_assignments)
|
2024-07-16 02:31:42 +08:00
|
|
|
hash[:has_many_overrides] = true
|
|
|
|
elsif assignment_object.multiple_due_dates_apply_to?(user)
|
|
|
|
hash[:vdd_tooltip] = OverrideTooltipPresenter.new(assignment_object, user).as_json
|
|
|
|
elsif (due_date = assignment_object.overridden_for(user).due_at) ||
|
|
|
|
(user_is_admin && (due_date = assignment_object.all_due_dates.dig(0, :due_at)))
|
|
|
|
hash[:due_date] = due_date
|
|
|
|
end
|
|
|
|
hash
|
|
|
|
end
|
|
|
|
|
2019-05-07 04:24:47 +08:00
|
|
|
def context_module_tag_info(user, context, user_is_admin: false, has_submission:)
|
2019-06-05 02:54:47 +08:00
|
|
|
return {} unless user
|
2021-09-23 00:25:11 +08:00
|
|
|
|
2015-12-05 05:15:30 +08:00
|
|
|
association(:context).target ||= context
|
2019-05-07 04:24:47 +08:00
|
|
|
tag_info = Rails.cache.fetch_with_batched_keys(
|
2019-12-03 22:05:31 +08:00
|
|
|
["context_module_tag_info3", user.cache_key(:enrollments), user.cache_key(:groups)].cache_key,
|
2019-05-07 04:24:47 +08:00
|
|
|
batch_object: self,
|
|
|
|
batched_keys: :availability
|
|
|
|
) do
|
2024-07-16 02:31:42 +08:00
|
|
|
override_aware_due_date_hash(user, user_is_admin:, assignment_object: self)
|
2015-12-05 05:15:30 +08:00
|
|
|
end
|
2022-02-11 08:28:56 +08:00
|
|
|
tag_info[:points_possible] = points_possible unless try(:quiz_type) == "survey"
|
2015-12-05 05:15:30 +08:00
|
|
|
|
2016-09-15 01:43:53 +08:00
|
|
|
if user && tag_info[:due_date]
|
2015-12-05 05:15:30 +08:00
|
|
|
if tag_info[:due_date] < Time.now &&
|
add sub_assignment data to assignment index
closes VICE-4293
flag=discussion_checkpoints
Test Plan
1. Create a checkpointed discussion
2. Have due dates or due date overrides on the checkpoints
3. Open assignment index
4. Verify that the API gets the dates in All_dates
5. Should display multiple due dates on the index
5a. The front end for this should be addressed
in VICE-4290
To add checkpoints you can use this graphQL mutation in
graphiql
But set the DiscussionTopicId, and setId to the appropriate
values
mutation MyMutation {
__typename
updateDiscussionTopic(
input: {discussionTopicId: "108", assignment: {forCheckpoints: true}, checkpoints: [{checkpointLabel: reply_to_topic, dates: {type: override, dueAt: "2024-06-14T17:19:38Z", setType: CourseSection, setId: 6}, pointsPossible: 10, repliesRequired: 1}, {checkpointLabel: reply_to_entry, dates: {type: override, dueAt: "2024-06-24T17:19:38Z", setType: CourseSection, setId: 6}, pointsPossible: 10, repliesRequired: 1}]}
) {
discussionTopic {
_id
assignment {
checkpoints {
name
}
}
}
}
}
Change-Id: Ia5a21aea9809f746bde6ae48e47bffe1c69d17b2
Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/350494
Tested-by: Service Cloud Jenkins <svc.cloudjenkins@instructure.com>
QA-Review: Caleb Guanzon <cguanzon@instructure.com>
Reviewed-by: Omar Soto-Fortuño <omar.soto@instructure.com>
Product-Review: Caleb Guanzon <cguanzon@instructure.com>
2024-06-19 03:56:54 +08:00
|
|
|
(is_a?(Quizzes::Quiz) || (is_a?(AbstractAssignment) && expects_submission?)) &&
|
2015-12-05 05:15:30 +08:00
|
|
|
!has_submission
|
|
|
|
tag_info[:past_due] = true
|
|
|
|
end
|
|
|
|
|
|
|
|
tag_info[:due_date] = tag_info[:due_date].utc.iso8601
|
|
|
|
end
|
2024-07-16 02:31:42 +08:00
|
|
|
|
2024-08-01 00:27:58 +08:00
|
|
|
if is_a?(Assignment) && checkpoints_parent?
|
2024-07-16 02:31:42 +08:00
|
|
|
tag_info[:sub_assignments] = sub_assignments.map do |sub_assignment|
|
|
|
|
sub_assignment_hash = {}
|
|
|
|
sub_assignment_hash[:sub_assignment_tag] = sub_assignment.sub_assignment_tag if sub_assignment.sub_assignment_tag
|
|
|
|
sub_assignment_hash[:points_possible] = sub_assignment.points_possible if sub_assignment.points_possible
|
|
|
|
sub_assignment_hash[:replies_required] = discussion_topic.reply_to_entry_required_count if sub_assignment_hash[:sub_assignment_tag] == CheckpointLabels::REPLY_TO_ENTRY
|
|
|
|
|
|
|
|
override_aware_due_date_hash(user, user_is_admin:, assignment_object: sub_assignment).merge(sub_assignment_hash)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2015-12-05 05:15:30 +08:00
|
|
|
tag_info
|
|
|
|
end
|
|
|
|
|
2012-12-29 05:02:21 +08:00
|
|
|
module ClassMethods
|
|
|
|
def due_date_compare_value(date)
|
|
|
|
# due dates are considered equal if they're the same up to the minute
|
2020-04-22 07:01:34 +08:00
|
|
|
return nil if date.nil?
|
2021-09-23 00:25:11 +08:00
|
|
|
|
2012-12-29 05:02:21 +08:00
|
|
|
date.to_i / 60
|
|
|
|
end
|
|
|
|
|
|
|
|
def due_dates_equal?(date1, date2)
|
|
|
|
due_date_compare_value(date1) == due_date_compare_value(date2)
|
|
|
|
end
|
|
|
|
end
|
2012-12-20 04:38:12 +08:00
|
|
|
end
|