importing more canvas export types

Added external feeds, grading standards, and learning outcomes

refs #3396

Change-Id: I138f22a6d5793e21ba49388e16be75d5f80f0e12
Reviewed-on: https://gerrit.instructure.com/3043
Tested-by: Hudson <hudson@instructure.com>
Reviewed-by: Brian Palmer <brianp@instructure.com>
This commit is contained in:
Bracken Mosbacker 2011-04-12 14:29:49 -06:00 committed by Brian Palmer
parent bb8bc24cd7
commit 84c048e5e6
12 changed files with 363 additions and 26 deletions

View File

@ -1189,6 +1189,8 @@ class Course < ActiveRecord::Base
Rubric.process_migration(data, migration)
@assignment_group_no_drop_assignments = {}
AssignmentGroup.process_migration(data, migration)
ExternalFeed.process_migration(data, migration)
GradingStandard.process_migration(data, migration)
migration.fast_update_progress(40)
Quiz.process_migration(data, migration, question_data)
migration.fast_update_progress(50)

View File

@ -183,4 +183,32 @@ class ExternalFeed < ActiveRecord::Base
)
end
end
def self.process_migration(data, migration)
tools = data['external_feeds'] ? data['external_feeds']: []
to_import = migration.to_import 'external_feeds'
tools.each do |tool|
if tool['migration_id'] && (!to_import || to_import[tool['migration_id']])
import_from_migration(tool, migration.context)
end
end
end
def self.import_from_migration(hash, context, item=nil)
hash = hash.with_indifferent_access
return nil if hash[:migration_id] && hash[:external_feeds_to_import] && !hash[:external_feeds_to_import][hash[:migration_id]]
item ||= find_by_context_id_and_context_type_and_migration_id(context.id, context.class.to_s, hash[:migration_id]) if hash[:migration_id]
item ||= context.external_feeds.new
item.migration_id = hash[:migration_id]
item.url = hash[:url]
item.title = hash[:title]
item.feed_type = hash[:feed_type]
item.feed_purpose = hash[:purpose]
item.verbosity = hash[:verbosity]
item.header_match = hash[:header_match] unless hash[:header_match].blank?
item.save!
context.imported_migration_items << item if context.imported_migration_items && item.new_record?
item
end
end

View File

@ -107,4 +107,33 @@ class GradingStandard < ActiveRecord::Base
# "F" => 0.0
# }
end
def self.process_migration(data, migration)
standards = data['grading_standards'] ? data['grading_standards']: []
to_import = migration.to_import 'grading_standards'
standards.each do |tool|
if tool['migration_id'] && (!to_import || to_import[tool['migration_id']])
import_from_migration(tool, migration.context)
end
end
end
def self.import_from_migration(hash, context, item=nil)
hash = hash.with_indifferent_access
return nil if hash[:migration_id] && hash[:grading_standards_to_import] && !hash[:grading_standards_to_import][hash[:migration_id]]
item ||= find_by_context_id_and_context_type_and_migration_id(context.id, context.class.to_s, hash[:migration_id]) if hash[:migration_id]
item ||= context.grading_standards.new
item.migration_id = hash[:migration_id]
item.title = hash[:title]
begin
item.data = JSON.parse hash[:data]
rescue
#todo - add to message to display to user
end
item.save!
context.imported_migration_items << item if context.imported_migration_items && item.new_record?
item
end
end

View File

@ -169,9 +169,10 @@ class LearningOutcome < ActiveRecord::Base
def self.process_migration(data, migration)
outcomes = data['learning_outcomes'] ? data['learning_outcomes'] : []
to_import = migration.to_import 'learning_outcomes'
outcomes.each do |outcome|
if !to_import || to_import[migration_id]
if outcome[:type] == 'learning_outcome_group'
LearningOutcomeGroup.import_from_migration(outcome, migration.context)
else
import_from_migration(outcome, migration.context)
end
end
@ -183,12 +184,20 @@ class LearningOutcome < ActiveRecord::Base
item ||= context.learning_outcomes.new
item.context = context
item.migration_id = hash[:migration_id]
item.short_description = hash[:title]
item.description = hash[:description]
if hash[:ratings]
item.data = {:rubric_criterion=>{:ratings=>hash[:ratings]}}
item.data[:rubric_criterion][:mastery_points] = hash[:mastery_points]
item.data[:rubric_criterion][:points_possible] = hash[:points_possible]
item.data[:rubric_criterion][:description] = item.short_description || item.description
end
item.save!
context.imported_migration_items << item if context.imported_migration_items && item.new_record?
log = LearningOutcomeGroup.default_for(context)
log = hash[:learning_outcome_group] || LearningOutcomeGroup.default_for(context)
log.add_item(item)
item

View File

@ -155,6 +155,32 @@ class LearningOutcomeGroup < ActiveRecord::Base
outcome
end
def self.import_from_migration(hash, context, item=nil)
hash = hash.with_indifferent_access
item ||= find_by_context_id_and_context_type_and_migration_id(context.id, context.class.to_s, hash[:migration_id]) if hash[:migration_id]
item ||= context.learning_outcome_groups.new
item.context = context
item.migration_id = hash[:migration_id]
item.title = hash[:title]
item.description = hash[:description]
item.save!
context.imported_migration_items << item if context.imported_migration_items && item.new_record?
if hash[:outcomes]
hash[:outcomes].each do |outcome|
outcome[:learning_outcome_group] = item
LearningOutcome.import_from_migration(outcome, context)
end
end
log = LearningOutcomeGroup.default_for(context)
log.add_item(item)
item
end
named_scope :active, lambda{
{:conditions => ['learning_outcome_groups.workflow_state != ?', 'deleted'] }
}

View File

@ -17,11 +17,17 @@
#
module CC
module ExternalFeeds
def create_external_feeds
def create_external_feeds(document=nil)
return nil unless @course.external_feeds.count > 0
feed_file = File.new(File.join(@canvas_resource_dir, CCHelper::EXTERNAL_FEEDS), 'w')
rel_path = File.join(CCHelper::COURSE_SETTINGS_DIR, CCHelper::EXTERNAL_FEEDS)
document = Builder::XmlMarkup.new(:target=>feed_file, :indent=>2)
if document
feed_file = nil
rel_path = nil
else
feed_file = File.new(File.join(@canvas_resource_dir, CCHelper::EXTERNAL_FEEDS), 'w')
rel_path = File.join(CCHelper::COURSE_SETTINGS_DIR, CCHelper::EXTERNAL_FEEDS)
document = Builder::XmlMarkup.new(:target=>feed_file, :indent=>2)
end
document.instruct!
document.externalFeeds(
"xmlns" => CCHelper::CANVAS_NAMESPACE,
@ -41,7 +47,7 @@ module CC
end
end
feed_file.close
feed_file.close if feed_file
rel_path
end
end

View File

@ -17,12 +17,18 @@
#
module CC
module GradingStandards
def create_grading_standards
def create_grading_standards(document=nil)
return nil unless @course.grading_standards.count > 0
standards_file = File.new(File.join(@canvas_resource_dir, CCHelper::GRADING_STANDARDS), 'w')
rel_path = File.join(CCHelper::COURSE_SETTINGS_DIR, CCHelper::GRADING_STANDARDS)
document = Builder::XmlMarkup.new(:target=>standards_file, :indent=>2)
if document
standards_file = nil
rel_path = nil
else
standards_file = File.new(File.join(@canvas_resource_dir, CCHelper::GRADING_STANDARDS), 'w')
rel_path = File.join(CCHelper::COURSE_SETTINGS_DIR, CCHelper::GRADING_STANDARDS)
document = Builder::XmlMarkup.new(:target=>standards_file, :indent=>2)
end
document.instruct!
document.gradingStandards(
"xmlns" => CCHelper::CANVAS_NAMESPACE,
@ -38,7 +44,7 @@ module CC
end
end
standards_file.close
standards_file.close if standards_file
rel_path
end
end

View File

@ -23,5 +23,6 @@ module CC
end
end
require 'cc/importer/learning_outcomes_converter'
require 'cc/importer/course_settings'
require 'cc/importer/cc_converter'

View File

@ -18,15 +18,19 @@
module CC::Importer
module CourseSettings
include CC::Importer
include LearningOutcomesConverter
def course_settings_doc(file)
def settings_doc(file)
open_file_xml File.join(@unzipped_file_path, COURSE_SETTINGS_DIR, file)
end
def convert_non_dependant_course_settings
@course[:course] = convert_course_settings(course_settings_doc(COURSE_SETTINGS))
@course[:assignment_groups] = convert_assignment_groups(course_settings_doc(ASSIGNMENT_GROUPS))
@course[:external_tools] = convert_external_tools(course_settings_doc(EXTERNAL_TOOLS))
@course[:course] = convert_course_settings(settings_doc(COURSE_SETTINGS))
@course[:assignment_groups] = convert_assignment_groups(settings_doc(ASSIGNMENT_GROUPS))
@course[:external_tools] = convert_external_tools(settings_doc(EXTERNAL_TOOLS))
@course[:external_feeds] = convert_external_feeds(settings_doc(EXTERNAL_FEEDS))
@course[:grading_standards] = convert_grading_standards(settings_doc(GRADING_STANDARDS))
@course[:learning_outcomes] = convert_learning_outcomes(settings_doc(LEARNING_OUTCOMES))
end
def convert_course_settings(doc)
@ -100,5 +104,38 @@ module CC::Importer
tools
end
def convert_external_feeds(doc)
feeds = []
return feeds unless doc
doc.css('externalFeed').each do |node|
feed = {}
feed['migration_id'] = node['identifier']
feed['title'] = get_node_val(node, 'title')
feed['url'] = get_node_val(node, 'url')
feed['feed_type'] = get_node_val(node, 'feed_type')
feed['purpose'] = get_node_val(node, 'purpose')
feed['verbosity'] = get_node_val(node, 'verbosity')
feed['header_match'] = get_node_val(node, 'header_match')
feeds << feed
end
feeds
end
def convert_grading_standards(doc)
standards = []
return standards unless doc
doc.css('gradingStandard').each do |node|
standard = {}
standard['migration_id'] = node['identifier']
standard['title'] = get_node_val(node, 'title')
standard['data'] = get_node_val(node, 'data')
standards << standard
end
standards
end
end
end

View File

@ -0,0 +1,83 @@
#
# Copyright (C) 2011 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::Importer
module LearningOutcomesConverter
include CC::Importer
def convert_learning_outcomes(doc)
outcomes = []
return outcomes unless doc
doc.at_css('learningOutcomes').children.each do |child|
if child.name == 'learningOutcome'
outcomes << process_learning_outcome(child)
elsif child.name == 'learningOutcomeGroup'
outcomes << process_outcome_group(child)
end
end
outcomes
end
def process_outcome_group(node)
group = {}
group[:migration_id] = node['identifier']
group[:title] = get_val_if_child(node, 'title')
group[:type] = 'learning_outcome_group'
group[:description] = get_val_if_child(node, 'description')
group[:outcomes] = []
node.css('learningOutcome').each do |out_node|
group[:outcomes] << process_learning_outcome(out_node)
end
group
end
def process_learning_outcome(node)
outcome = {}
outcome[:migration_id] = node['identifier']
outcome[:title] = get_node_val(node, 'title')
outcome[:type] = 'learning_outcome'
outcome[:description] = get_val_if_child(node, 'description')
outcome[:mastery_points] = get_float_val(node, 'mastery_points')
outcome[:points_possible] = get_float_val(node, 'points_possible')
outcome[:ratings] = []
node.css('rating').each do |r_node|
rating = {}
rating[:description] = get_node_val(r_node, 'description')
rating[:points] = get_float_val(r_node, 'points')
outcome[:ratings] << rating
end
outcome
end
# You can't do a css selector that only looks for direct
# descendants of the current node, so you have to iterate
# over the children and see if it's there.
def get_val_if_child(node, name)
if child = node.children.find{|c|c.name == name}
return child.text
end
nil
end
end
end

View File

@ -17,15 +17,21 @@
#
module CC
module LearningOutcomes
def create_learning_outcomes
def create_learning_outcomes(document=nil)
return nil unless LearningOutcome.active.find_all_by_context_id_and_context_type(@course.id, 'Course').count > 0
return nil unless @course.learning_outcome_groups.find_by_learning_outcome_group_id(nil)
root_group = @course.learning_outcome_groups.find_by_learning_outcome_group_id(nil)
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
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)
document.instruct!
document.learningOutcomes(
"xmlns" => CCHelper::CANVAS_NAMESPACE,
@ -35,7 +41,7 @@ module CC
process_outcome_group_content(outs_node, root_group)
end
outcomes_file.close
outcomes_file.close if outcomes_file
rel_path
end

View File

@ -62,7 +62,7 @@ describe "Common Cartridge importing" do
end
end
it "should convert assignment groups" do
it "should import assignment groups" do
ag1 = @copy_from.assignment_groups.new
ag1.name = "Boring assignments"
ag1.position = 1
@ -116,7 +116,7 @@ describe "Common Cartridge importing" do
ag2_2.rules.should == "drop_lowest:2\ndrop_highest:5\nnever_drop:%s\n" % a_2.id
end
it "should convert external tools" do
it "should import external tools" do
tool1 = @copy_from.context_external_tools.new
tool1.url = 'http://instructure.com'
tool1.name = 'instructure'
@ -163,5 +163,109 @@ describe "Common Cartridge importing" do
t2.consumer_key.should == 'fake'
t2.shared_secret.should == 'fake'
end
it "should import external feeds" do
ef = @copy_from.external_feeds.new
ef.url = "http://search.twitter.com/search.atom?q=instructure"
ef.title = "Instructure on Twitter"
ef.feed_type = "rss/atom"
ef.feed_purpose = 'announcements'
ef.verbosity = 'full'
ef.header_match = "canvas"
ef.save!
#export to xml
builder = Builder::XmlMarkup.new(:indent=>2)
@resource.create_external_feeds(builder)
#convert to json
doc = Nokogiri::XML(builder.target!)
hash = @converter.convert_external_feeds(doc)
#import json into new course
ExternalFeed.process_migration({'external_feeds'=>hash}, @migration)
@copy_to.save!
ef_2 = @copy_to.external_feeds.find_by_migration_id(CC::CCHelper.create_key(ef))
ef_2.url.should == ef.url
ef_2.title.should == ef.title
ef_2.feed_type.should == ef.feed_type
ef_2.feed_purpose.should == ef.feed_purpose
ef_2.verbosity.should == ef.verbosity
ef_2.header_match.should == ef.header_match
end
it "should import grading standards" do
gs = @copy_from.grading_standards.new
gs.title = "Standard eh"
gs.data = [["A", 1], ["A-", 0.92], ["B+", 0.88], ["B", 0.84], ["B!-", 0.82], ["C+", 0.79], ["C", 0.76], ["C-", 0.73], ["D+", 0.69], ["D", 0.66], ["D-", 0.63], ["F", 0.6]]
gs.save!
#export to xml
builder = Builder::XmlMarkup.new(:indent=>2)
@resource.create_grading_standards(builder)
#convert to json
doc = Nokogiri::XML(builder.target!)
hash = @converter.convert_grading_standards(doc)
#import json into new course
GradingStandard.process_migration({'grading_standards'=>hash}, @migration)
@copy_to.save!
gs_2 = @copy_to.grading_standards.find_by_migration_id(CC::CCHelper.create_key(gs))
gs_2.title.should == gs.title
gs_2.data.should == gs.data
end
it "should import learning outcomes" do
lo = @copy_from.learning_outcomes.new
lo.context = @copy_from
lo.short_description = "Lone outcome"
lo.description = "<p>Descriptions are boring</p>"
lo.workflow_state = 'active'
lo.data = {:rubric_criterion=>{:mastery_points=>3, :ratings=>[{:description=>"Exceeds Expectations", :points=>5}, {:description=>"Meets Expectations", :points=>3}, {:description=>"Does Not Meet Expectations", :points=>0}], :description=>"First outcome", :points_possible=>5}}
lo.save!
default = LearningOutcomeGroup.default_for(@copy_from)
default.add_item(lo)
lo_g = @copy_from.learning_outcome_groups.new
lo_g.context = @copy_from
lo_g.title = "Lone outcome group"
lo_g.description = "<p>Groupage</p>"
lo_g.save!
lo2 = @copy_from.learning_outcomes.new
lo2.context = @copy_from
lo2.short_description = "outcome in group"
lo2.workflow_state = 'active'
lo2.data = {:rubric_criterion=>{:mastery_points=>2, :ratings=>[{:description=>"e", :points=>50}, {:description=>"me", :points=>2}, {:description=>"Does Not Meet Expectations", :points=>0.5}], :description=>"First outcome", :points_possible=>5}}
lo2.save!
lo_g.add_item(lo2)
default.add_item(lo_g)
#export to xml
builder = Builder::XmlMarkup.new(:indent=>2)
@resource.create_learning_outcomes(builder)
#convert to json
doc = Nokogiri::XML(builder.target!)
hash = @converter.convert_learning_outcomes(doc)
#import json into new course
LearningOutcome.process_migration({'learning_outcomes'=>hash}, @migration)
@copy_to.save!
lo_2 = @copy_to.learning_outcomes.find_by_migration_id(CC::CCHelper.create_key(lo))
lo_2.short_description.should == lo.short_description
lo_2.description.should == lo.description
lo_2.data.with_indifferent_access.should == lo.data.with_indifferent_access
lo2_2 = @copy_to.learning_outcomes.find_by_migration_id(CC::CCHelper.create_key(lo2))
lo2_2.short_description.should == lo2.short_description
lo2_2.description.should == lo2.description
lo2_2.data.with_indifferent_access.should == lo2.data.with_indifferent_access
lo_g_2 = @copy_to.learning_outcome_groups.find_by_migration_id(CC::CCHelper.create_key(lo_g))
lo_g_2.title.should == lo_g.title
lo_g_2.description.should == lo_g.description
lo_g_2.sorted_content.length.should == 1
end
end