canvas-lms/lib/cc/learning_outcomes.rb

151 lines
6.3 KiB
Ruby
Raw Normal View History

# frozen_string_literal: true
#
# Copyright (C) 2011 - 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/>.
#
module CC
module LearningOutcomes
include Outcomes::OutcomeFriendlyDescriptionResolver
def create_learning_outcomes(document=nil)
return nil unless @course.has_outcomes?
root_group = @course.root_outcome_group(false)
return nil unless root_group
@selectable_outcomes = @course.root_account.feature_enabled?(:selectable_outcomes_in_course_copy)
if document
outcomes_file = nil
rel_path = nil
else
outcomes_file = File.new(File.join(@canvas_resource_dir, CCHelper::LEARNING_OUTCOMES), 'w')
rel_path = File.join(CCHelper::COURSE_SETTINGS_DIR, CCHelper::LEARNING_OUTCOMES)
document = Builder::XmlMarkup.new(:target=>outcomes_file, :indent=>2)
end
document.instruct!
document.learningOutcomes(
"xmlns" => CCHelper::CANVAS_NAMESPACE,
"xmlns:xsi"=>"http://www.w3.org/2001/XMLSchema-instance",
"xsi:schemaLocation"=> "#{CCHelper::CANVAS_NAMESPACE} #{CCHelper::XSD_URI}"
) do |outs_node|
@exported_outcome_ids = []
process_outcome_group_content(outs_node, root_group)
if @manifest&.exporter&.for_master_migration || !export_object?(LearningOutcome.new, 'learning_outcomes')
# copy straggler outcomes that should be brought in implicitly
@course.linked_learning_outcomes.where.not(:id => @exported_outcome_ids).each do |item|
if export_object?(item, 'learning_outcomes')
process_learning_outcome(outs_node, item)
end
end
end
end
outcomes_file.close if outcomes_file
rel_path
end
def process_outcome_group(node, group, force_export = false)
migration_id = create_key(group)
node.learningOutcomeGroup(:identifier=>migration_id) do |group_node|
group_node.title group.title unless group.title.blank?
group_node.description @html_exporter.html_content(group.description) unless group.description.blank?
group_node.vendor_guid group.vendor_guid if group.vendor_guid.present?
Add 'source_learning_outcome_group_id' to course copy importers closes OUT-4548 flag=improved_outcomes_management Test plan: Step 1: Setup a course group with source_outcome_group_id present: You can do this in 2 differents way: * 1: In any course, grab a learning outcome group and set the "source_outcome_group_id" attribute. This id must be the group id of an account group. * 2: Or use the graphql import API to import a group from the account Follow instructions on g/259131, section "Import a whole group from account to course" * After you setup the group, let's say you setup the group called "group 1", grab the id of "group 1" and hold for now as GROUP_1_ID. * You can grab the id by using the web inspector and inspect the graphql request after selecting the group in the LHS Step 2: Export course content: * Go to course -> settings -> export course content Export type: course * Click "Create export" * Wait the export to finish and click in "New Export" Step 3: Verify exported content. * Go to finder and find the exported file, it should has the .imscc extension. Replace the extension to .zip and unzip it * Open the file course_settings -> learning_outcomes.xml * Assert it's populated some source_outcome_group_id nodes of learningOutcomeGroup node * rename it back to .imscc Step 4: Import the course content in a blank course at the same account. * Go to account -> "+ course" * Go to the new course -> settings * Import course content Content type: Canvas Course Export Package Source: the imscc file Content: All content Click "Import" Wait until its done Step 5: Assert import works properly * Go to course -> outcomes You should see same outcomes as the exported course * Grab the id of the "group 1" that was just imported, lets hold in IMPORTED_GROUP_1_ID. * Go to console: Assert the source_outcome_group_id of GROUP_1 is the same of IMPORTED_GROUP_1_ID Assert the source_outcome_group_id are present (not nil) LearningOutcomeGroup.find(GROUP_1_ID) LearningOutcomeGroup.find(IMPORTED_GROUP_1_ID) Step 6: Import the course content in a blank course IN A DIFFERENT account. * Same as step 4 Step 7: Assert import works properly * Same as step 5, but now the source_outcome_group_id should not be populated, since its belongs to a different account. Check this behavior in console Step 8: Assert course copy also copy the source_outcome_group_id * go to settings in course you want to copy > copy this course * check in console like step 4 Change-Id: Iada96de5b614441d86cdab96644d6110d7e3fe58 Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/268694 Tested-by: Service Cloud Jenkins <svc.cloudjenkins@instructure.com> Reviewed-by: Augusto Callejas <acallejas@instructure.com> Reviewed-by: Pat Renner <prenner@instructure.com> QA-Review: Martin Yosifov <martin.yosifov@instructure.com> Product-Review: Augusto Callejas <acallejas@instructure.com>
2021-07-09 04:55:47 +08:00
group_node.source_outcome_group_id group.source_outcome_group_id if group.source_outcome_group_id.present?
group_node.learningOutcomes do |lo_node|
process_outcome_group_content(lo_node, group, force_export)
end
end
end
def process_outcome_group_content(node, group, force_export = false)
group.child_outcome_groups.active.each do |item|
export_group = export_object?(item, 'learning_outcomes') || export_object?(item, 'learning_outcome_groups')
export_group ||= force_export if @selectable_outcomes
if export_group
process_outcome_group(node, item, @selectable_outcomes)
elsif @selectable_outcomes
# Skip importing this group, but continue with its contents
process_outcome_group_content(node, item, force_export)
end
learning outcomes refactor This list is *NOT* complete, some items may have snuck in that I forgot to note, and/or some of the noted items may not be completely functional yet. Specs need to be written around a lot of this, other specs will no doubt need to be fixed. Some things, particularly around LearningOutcomeGroups will need data migrations that aren't there yet. * remove LearningOutcome.non_rubric_outcomes? and replace with false where invoked * remove LearningOutcome.enabled? and replace with true where invoked * remove never-taken branches * remove the shared/aligned_outcomes partial and it's supporting javascript, since it's now empty * remove js handler for add_outcome_alignment_link and supporting method since it only occurred in never-taken branches * mix LearningOutcomeContext into Course and Account * replace LearningOutcomeGroup.default_for(context) with LearningOutcomeContext#root_outcome_group * rename LearningOutcome#content_tags to LearningOutcome#alignments * rename LearningOutcomeGroup#content_tags to LearningOutcomeGroup#child_links, and properly restrict * remove ContentTag[Alignment]#rubric_association_id, add ContentTag[Alignment]#has_rubric_association? that looks at the presence of the content's rubric_association_id * condition off the assignment having a rubric_association rather than filtering tags by has_rubric_association (which just looks back at the assignment). all or none of the assignment's alignments are forced to have the association (via the assignment). this was true in practice before, is now codified (and more efficient) * rename AssessmentQuestionBank#learning_outcome_tags to AssessmentQuestionBank#learning_outcome_alignments * rename Assignment#learning_outcome_tags to Assignment#learning_outcome_alignments * rename Rubric#learning_outcome_tags to Rubric#learning_outcome_alignments * move/rename (Course|Account)#learning_outcome_tags to LearningOutcomeContext#learning_outcome_links * move/rename Account#learning_outcomes (corrected) and Course#learning_outcomes to LearningOutcomeContext#linked_learning_outcomes * move/rename Account#created_learning_outcomes and Course#created_learning_outcomes to LearningOutcomeContext#created_learning_outcomes * clarify and correct usage of linked_learning_outcomes vs. created_learning_outcomes * move/rename (Account|Account)#learning_outcome_groups to LearningOutcomeContext#learning_outcome_groups * remove unused Account#associated_learning_outcomes * just remove one link to a learning outcome when deleting * merge Account#has_outcomes?, Course#has_outcomes? and Course#has_outcomes into LearningOutcomeContext#has_outcomes?, add a use in Context#active_record_types * kill LearningOutcomeGroup#root_learning_outcome_group (unused) * rename LearningOutcomeResult#content_tag to LearningOutcomeResult#alignment * kill unused (and broken) OutcomesController#add_outcome_group * kill unused OutcomesController#update_outcomes_for_asset * kill unused OutcomesController#outcomes_for_asset * remove unused (outside specs, correct specs) AssessmentQuestionBank#outcomes= * remove unused ContentTag#learning_outcome_content * replace ContentTag.learning_outcome_tags_for(asset) (only ever called with asset=an assignment) with call to Assignment#learning_outcome_alignments * remove unused ContentTag.not_rubric * remove (now) unused ContentTag.include_outcome * remove unused LearningOutcome#learning_outcome_group_associations * avoid explicit use of ContentTag in outcome-related specs * replace LearningOutcomeGroup#learning_outcome_tags with LearningOutcomeGroup#child_outcome_links (and only use for outcome links; not tags for child groups) * split ContentTag#create_outcome_result into Submission#create_outcome_result, QuizSubmission#create_outcome_result, and RubricAssessment#create_outcome_result. fix some bugs along the way * refactor ContentTag.outcome_tags_for_banks and some code from QuizSubmission#(track_outcomes|update_outcomes_for_assessment_questions) into QuizSubmission#questions_and_alignments * refactor RubricAssociation#update_outcome_relations and Rubric#update_alignments into LearningOutcome.update_alignments * don't use ContentTag#rubric_association with outcome alignments; use the tag's content's rubric_association in its place (they should have been equal anyways) * refactor LearningOutcome.available_in_context and @context.root_outcome_group.sorted_all_outcomes (only time sorted_all_outcomes is used) into LearningOutcomeContext#available_outcomes and LearningOutcomeContext#available_outcome * overhaul LearningOutcomeGroup#sorted_content and rename to LearningOutcomeGroup#sorted_children. it not returns ContentTags (outcome links) and LearningOutcomeGroups, vs. LearningOutcomes and LearningOutcomeGroups; fix usages appropriately * fix UI for arranging/deleting outcome links and groups within a group to refer to the outcome link rather than the outcome Change-Id: I85d99f2634f7206332cb1f5d5ea575b428988d4b Reviewed-on: https://gerrit.instructure.com/12590 Reviewed-by: Jacob Fugal <jacob@instructure.com> Tested-by: Jacob Fugal <jacob@instructure.com>
2012-07-13 01:16:13 +08:00
end
group.child_outcome_links.active.each do |item|
item = item.content
next unless force_export || export_object?(item, 'learning_outcomes')
process_learning_outcome(node, item)
end
end
def process_learning_outcome(node, item)
@exported_outcome_ids << item.id
add_exported_asset(item)
migration_id = create_key(item)
node.learningOutcome(:identifier=>migration_id) do |out_node|
out_node.title item.short_description if item.short_description.present?
out_node.description @html_exporter.html_content(item.description) if item.description.present?
out_node.calculation_method item.calculation_method if item.calculation_method.present?
out_node.calculation_int item.calculation_int if item.calculation_int.present?
out_node.vendor_guid item.vendor_guid if item.vendor_guid.present?
# Populate friendly_description for course export
if Account.site_admin.feature_enabled?(:outcomes_friendly_description) && item.context == @course
friendly_description = resolve_friendly_descriptions(@course.account, @course, item.id)
out_node.friendly_description friendly_description.first&.description if friendly_description.first.present?
end
if item.context != @course
out_node.is_global_outcome !item.context
out_node.external_identifier item.id
end
if item.alignments.polymorphic_where(context: @course).exists?
out_node.alignments do |alignments_node|
item.alignments.polymorphic_where(context: @course).each do |alignment|
alignments_node.alignment do |alignment_node|
alignment_node.content_type alignment.content_type
alignment_node.content_id create_key(alignment.content)
alignment_node.mastery_type alignment.tag
alignment_node.mastery_score alignment.mastery_score
alignment_node.position alignment.position
end
end
end
end
if item.data && criterion = item.data[:rubric_criterion]
out_node.points_possible criterion[:points_possible] if criterion[:points_possible]
out_node.mastery_points criterion[:mastery_points] if criterion[:mastery_points]
if criterion[:ratings] && criterion[:ratings].length > 0
out_node.ratings do |ratings_node|
criterion[:ratings].each do |rating|
ratings_node.rating do |rating_node|
rating_node.description rating[:description]
rating_node.points rating[:points]
end
end
end
end
end
end
end
end
end