senkyoshi 1.0.3 → 1.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +13 -13
  3. data/lib/senkyoshi/collection.rb +16 -2
  4. data/lib/senkyoshi/configuration.rb +1 -1
  5. data/lib/senkyoshi/models/attachment.rb +2 -0
  6. data/lib/senkyoshi/models/content.rb +13 -25
  7. data/lib/senkyoshi/models/content_reviewed_criteria.rb +27 -0
  8. data/lib/senkyoshi/models/course.rb +4 -0
  9. data/lib/senkyoshi/models/course_toc.rb +21 -0
  10. data/lib/senkyoshi/models/file_resource.rb +14 -0
  11. data/lib/senkyoshi/models/forum.rb +14 -1
  12. data/lib/senkyoshi/models/grade_completed_criteria.rb +8 -0
  13. data/lib/senkyoshi/models/grade_criteria.rb +46 -0
  14. data/lib/senkyoshi/models/grade_range_criteria.rb +34 -0
  15. data/lib/senkyoshi/models/grade_range_percent_criteria.rb +28 -0
  16. data/lib/senkyoshi/models/gradebook.rb +6 -0
  17. data/lib/senkyoshi/models/heirarchy.rb +84 -0
  18. data/lib/senkyoshi/models/link.rb +15 -0
  19. data/lib/senkyoshi/models/module.rb +6 -0
  20. data/lib/senkyoshi/models/module_converter.rb +86 -0
  21. data/lib/senkyoshi/models/module_item.rb +8 -2
  22. data/lib/senkyoshi/models/outcome_definition.rb +10 -5
  23. data/lib/senkyoshi/models/qti.rb +1 -0
  24. data/lib/senkyoshi/models/question.rb +3 -2
  25. data/lib/senkyoshi/models/questions/matching.rb +5 -1
  26. data/lib/senkyoshi/models/questions/ordering.rb +5 -3
  27. data/lib/senkyoshi/models/rule.rb +46 -0
  28. data/lib/senkyoshi/models/rule_criteria.rb +146 -0
  29. data/lib/senkyoshi/models/wikipage.rb +12 -2
  30. data/lib/senkyoshi/version.rb +1 -1
  31. data/lib/senkyoshi/xml_parser.rb +35 -41
  32. data/lib/senkyoshi.rb +25 -5
  33. metadata +14 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: d7187188584379d9e573651fe9147e7bb50497ca
4
- data.tar.gz: e595d29609ad508dfc443cf1543b0eecdd382ea5
3
+ metadata.gz: 26c7126fd95fa6cf136131c92014471f65e2089e
4
+ data.tar.gz: 6a6a5352177a859ddbb3ac1c83fe9ac465e927a9
5
5
  SHA512:
6
- metadata.gz: c16a2de94714d3b58e2acaa22e17eb58fae79c1e17a13d8c987660ae60ffc3bf8e17d733b7ae1bc116e9d8e672a37d910c0efcce2952caad8d00f9032f94766f
7
- data.tar.gz: 4d333e8b3a2e0b6000a41d3b49d28a2a2fe17f3add5a1b0248ecda417fa333df4a0aaae9e0c4d980d9bbf599c971ac449dd41adcb840b4726cdbbcc97c715343
6
+ metadata.gz: bb156e023de570e457222738c42a34af1433c279ad9879501fcd7ed82d5a76538f2e3bfc191832bdcdcd03a54f4486a87abe2fbc9783d2eb9d54a7b415dff903
7
+ data.tar.gz: fb795ce77619b24d9b0ec6e55eeba40ece4e82b5f325b8b48a39be08d970349e96828395a1c29e34d63e4b8d60cd96d16b8fe11c57b844a9b140b11f780bb400
data/README.md CHANGED
@@ -4,6 +4,10 @@ Senkyoshi converts exported Blackboard packages into Canvas .imscc packages. It
4
4
 
5
5
  ## Installation
6
6
 
7
+ #### Canvas
8
+ See [Senkyoshi Canvas Plugin](https://github.com/atomicjolt/senkyoshi_canvas_plugin) if you want to enable this in your canvas app.
9
+
10
+ #### CLI
7
11
  Add this line to your application's Gemfile:
8
12
 
9
13
  ```ruby
@@ -32,24 +36,24 @@ Create a `senkyoshi.yml` and add credentials:
32
36
  # Generally looks like https://< mycanvas_instance >/api
33
37
  :canvas_url: <canvas instance api url>
34
38
 
35
- # Canvas tokens can be generated at <my_canvas_url>/profile/setting provided
36
- # that the user has the required priviledges
39
+ # Canvas tokens can be generated at <my_canvas_url>/profile/settings provided
40
+ # that the user has the required privileges
37
41
  :canvas_token: <canvas token>
38
42
 
39
- # Url of scorm manager. This could the the adhesion app
43
+ # URL of SCORM manager. This could be the Adhesion app
40
44
  # [https://github.com/atomicjolt/adhesion]
41
45
  :scorm_url: <scorm manager url>
42
46
 
43
- # This should be the endpoint to launch a given scorm course, in the case of
44
- # adhesion this will look like https://<adhesion url>/scorm_course
47
+ # This should be the endpoint to launch a given SCORM course. In the case of
48
+ # Adhesion, this will look like https://<adhesion url>/scorm_course
45
49
  :scorm_launch_url: <scorm launch url>
46
50
 
47
- # This is the secret to authenticate requests to the scorm manager. In the case
48
- # of adhesion you can generate a shared secret by logging into the server and
51
+ # This is the secret to authenticate requests to the SCORM manager. In the case
52
+ # of Adhesion, you can generate a shared secret by logging into the server and
49
53
  # running rake shared_auth, which will generate and save a token
50
54
  :scorm_shared_auth: <scorm manager token>
51
55
 
52
- # The account or sub-account id. This can be :self, :default, or an id
56
+ # The account or sub-account ID. This can be :self, :default, or an ID
53
57
  :account_id: <id>
54
58
  ```
55
59
 
@@ -63,7 +67,7 @@ This will take all your files in your source folder and convert them to your out
63
67
 
64
68
  Run converting files in parallel:
65
69
  ```sh
66
- time rake senkyoshi:imscc -m
70
+ time rake senkyoshi:imscc -m # The `time` command is optional.
67
71
  ```
68
72
 
69
73
  Delete entire outputs folder:
@@ -90,7 +94,3 @@ Bug reports and pull requests are welcome on GitHub at https://github.com/atomic
90
94
  ## License
91
95
 
92
96
  The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
93
-
94
-
95
- ### Things Not Quite Implemented
96
- People, Groups, Sets, Blogs
@@ -2,8 +2,8 @@ module Senkyoshi
2
2
  class Collection
3
3
  attr_reader :resources
4
4
 
5
- def initialize
6
- @resources = []
5
+ def initialize(resources = [])
6
+ @resources = resources
7
7
  end
8
8
 
9
9
  def add(resources)
@@ -16,6 +16,20 @@ module Senkyoshi
16
16
  end
17
17
  end
18
18
 
19
+ def find_by_id(id)
20
+ @resources.detect { |item| item.respond_to?(:id) && item.id == id }
21
+ end
22
+
23
+ def find_instances_of(class_name)
24
+ @resources.select { |res| res.class == class_name }
25
+ end
26
+
27
+ def find_instances_not_of(types)
28
+ @resources.select do |res|
29
+ types.each { |type| res.class != type }
30
+ end
31
+ end
32
+
19
33
  def each
20
34
  @resources.each do |resource|
21
35
  yield resource
@@ -20,7 +20,7 @@ module Senkyoshi
20
20
 
21
21
  def self._config
22
22
  @config ||= if File.exists? "senkyoshi.yml"
23
- YAML::load(File.read("senkyoshi.yml"))
23
+ YAML::safe_load(File.read("senkyoshi.yml"), [Symbol])
24
24
  else
25
25
  {}
26
26
  end
@@ -10,6 +10,8 @@ module Senkyoshi
10
10
  @module_type,
11
11
  @files.first.name,
12
12
  @url,
13
+ @indent,
14
+ @file_name,
13
15
  ).canvas_conversion
14
16
  self
15
17
  end
@@ -20,6 +20,7 @@ module Senkyoshi
20
20
  "x-bb-module-page" => "WikiPage",
21
21
  "x-bb-lesson-plan" => "WikiPage",
22
22
  "x-bb-syllabus" => "WikiPage",
23
+ "x-bb-courselink" => "WikiPage",
23
24
  }.freeze
24
25
 
25
26
  MODULE_TYPES = {
@@ -55,6 +56,8 @@ module Senkyoshi
55
56
  def iterate_xml(xml, pre_data)
56
57
  @points = pre_data[:points] || 0
57
58
  @parent_title = pre_data[:parent_title]
59
+ @indent = pre_data[:indent]
60
+ @file_name = pre_data[:file_name]
58
61
  @title = xml.xpath("/CONTENT/TITLE/@value").first.text
59
62
  @url = xml.at("URL")["value"]
60
63
  @body = xml.xpath("/CONTENT/BODY/TEXT").first.text
@@ -65,6 +68,7 @@ module Senkyoshi
65
68
  @type = xml.xpath("/CONTENT/RENDERTYPE/@value").first.text
66
69
  @parent_id = pre_data[:parent_id]
67
70
  @module_type = MODULE_TYPES[self.class.name]
71
+ @referred_to_title = pre_data[:referred_to_title]
68
72
 
69
73
  if pre_data[:assignment_id] && !pre_data[:assignment_id].empty?
70
74
  @id = pre_data[:assignment_id]
@@ -78,18 +82,14 @@ module Senkyoshi
78
82
  end
79
83
 
80
84
  def set_module
81
- module_item = ModuleItem.new(@title, @module_type, @id, @url)
82
- module_item.canvas_conversion
83
- end
84
-
85
- def self.get_pre_data(xml, file_name)
86
- id = xml.xpath("/CONTENT/@id").first.text
87
- parent_id = xml.xpath("/CONTENT/PARENTID/@value").first.text
88
- {
89
- id: id,
90
- parent_id: parent_id,
91
- file_name: file_name,
92
- }
85
+ ModuleItem.new(
86
+ @title,
87
+ @module_type,
88
+ @id,
89
+ @url,
90
+ @indent,
91
+ @file_name,
92
+ ).canvas_conversion
93
93
  end
94
94
 
95
95
  def canvas_conversion(course, _resources = nil)
@@ -97,19 +97,7 @@ module Senkyoshi
97
97
  end
98
98
 
99
99
  def create_module(course)
100
- course.canvas_modules ||= []
101
- cc_module = course.canvas_modules.
102
- detect { |a| a.identifier == @parent_id }
103
- if cc_module
104
- cc_module.module_items << @module_item
105
- else
106
- title = @parent_title || @title
107
- cc_module = Module.new(title, @parent_id)
108
- cc_module = cc_module.canvas_conversion
109
- cc_module.module_items << @module_item
110
- course.canvas_modules << cc_module
111
- end
112
- course
100
+ super(course)
113
101
  end
114
102
  end
115
103
  end
@@ -0,0 +1,27 @@
1
+ require "senkyoshi/models/rule_criteria"
2
+
3
+ module Senkyoshi
4
+ class ContentReviewedCriteria < RuleCriteria
5
+ attr_reader(:reviewed_content_id)
6
+
7
+ def initialize(id, negated, reviewed_content_id)
8
+ super(id, negated)
9
+ @reviewed_content_id = reviewed_content_id
10
+ end
11
+
12
+ def self.from_xml(xml)
13
+ id = RuleCriteria.get_id xml
14
+ negated = Senkyoshi.true? RuleCriteria.get_negated(xml)
15
+ reviewed_content_id = xml.xpath("./REVIEWED_CONTENT_ID/@value").text
16
+ ContentReviewedCriteria.new(id, negated, reviewed_content_id)
17
+ end
18
+
19
+ def get_foreign_id
20
+ @reviewed_content_id
21
+ end
22
+
23
+ def get_completion_type
24
+ COMPLETION_TYPES[:must_view]
25
+ end
26
+ end
27
+ end
@@ -34,5 +34,9 @@ module Senkyoshi
34
34
  course.conclude_at = @conclude_at
35
35
  course
36
36
  end
37
+
38
+ def self.master_module(course)
39
+ course.canvas_modules.detect { |a| a.title == MASTER_MODULE }
40
+ end
37
41
  end
38
42
  end
@@ -0,0 +1,21 @@
1
+ require "senkyoshi/models/resource"
2
+ require "senkyoshi/models/content_file"
3
+
4
+ module Senkyoshi
5
+ class CourseToc
6
+ def self.get_pre_data(xml, file_name)
7
+ target_type = xml.xpath("/COURSETOC/TARGETTYPE/@value").first.text
8
+ if target_type != "MODULE" || target_type != "DIVIDER"
9
+ title = xml.xpath("/COURSETOC/LABEL/@value").first.text
10
+ internal_handle = xml.xpath("/COURSETOC/INTERNALHANDLE/@value").
11
+ first.text
12
+ {
13
+ title: title,
14
+ target_type: target_type,
15
+ original_file: file_name,
16
+ internal_handle: internal_handle,
17
+ }
18
+ end
19
+ end
20
+ end
21
+ end
@@ -19,5 +19,19 @@ module Senkyoshi
19
19
  def iterate_xml(_xml, _pre_data)
20
20
  self
21
21
  end
22
+
23
+ def create_module(course)
24
+ course.canvas_modules ||= []
25
+ cc_module = Course.master_module(course)
26
+ if cc_module
27
+ cc_module.module_items << @module_item
28
+ else
29
+ cc_module = Module.new(MASTER_MODULE, MASTER_MODULE)
30
+ cc_module = cc_module.canvas_conversion
31
+ cc_module.module_items << @module_item
32
+ course.canvas_modules << cc_module
33
+ end
34
+ course
35
+ end
22
36
  end
23
37
  end
@@ -9,9 +9,19 @@ module Senkyoshi
9
9
  @discussion_type = "threaded"
10
10
  end
11
11
 
12
- def iterate_xml(data, _)
12
+ def iterate_xml(data, pre_data)
13
13
  @title = Senkyoshi.get_attribute_value(data, "TITLE")
14
14
  @text = Senkyoshi.get_text(data, "TEXT")
15
+ if pre_data[:internal_handle]
16
+ @module_item = ModuleItem.new(
17
+ @title,
18
+ "DiscussionTopic",
19
+ @id,
20
+ nil,
21
+ pre_data[:indent],
22
+ @id,
23
+ ).canvas_conversion
24
+ end
15
25
  self
16
26
  end
17
27
 
@@ -22,6 +32,9 @@ module Senkyoshi
22
32
  discussion.identifier = @id
23
33
  discussion.discussion_type = @discussion_type
24
34
  course.discussions << discussion
35
+ if @module_item
36
+ course = create_module(course)
37
+ end
25
38
  course
26
39
  end
27
40
  end
@@ -0,0 +1,8 @@
1
+ require "senkyoshi/models/grade_criteria"
2
+ module Senkyoshi
3
+ class GradeCompletedCriteria < GradeCriteria
4
+ def get_completion_type
5
+ COMPLETION_TYPES[:must_submit]
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,46 @@
1
+ require "senkyoshi/models/rule_criteria"
2
+ module Senkyoshi
3
+ class GradeCriteria < RuleCriteria
4
+ attr_reader(:outcome_def_id)
5
+
6
+ def initialize(id, outcome_def_id, negated)
7
+ super(id, negated)
8
+ @outcome_def_id = outcome_def_id
9
+ @foreign_content_id = nil
10
+ end
11
+
12
+ def get_foreign_id
13
+ @foreign_asidata_id || @foreign_content_id
14
+ end
15
+
16
+ def self.get_outcome_def_id(xml)
17
+ xml.xpath("./OUTCOME_DEFINITION_ID/@value").text
18
+ end
19
+
20
+ def self.from_xml(xml)
21
+ id = RuleCriteria.get_id xml
22
+ negated = Senkyoshi.true? RuleCriteria.get_negated xml
23
+ outcome_def_id = GradeCriteria.get_outcome_def_id xml
24
+ new(id, outcome_def_id, negated)
25
+ end
26
+
27
+ ##
28
+ # use gradebook to match outcome_definition_id to the
29
+ # assignment content id
30
+ ##
31
+ def set_foreign_ids(resources, outcome_def_id)
32
+ gradebook = resources.find_instances_of(Gradebook).first
33
+
34
+ outcome = gradebook.find_outcome_def(outcome_def_id)
35
+ if outcome
36
+ @foreign_content_id = outcome.content_id
37
+ @foreign_asidata_id = outcome.asidataid
38
+ end
39
+ end
40
+
41
+ def canvas_conversion(course, content_id, resources)
42
+ set_foreign_ids(resources, @outcome_def_id)
43
+ super(course, content_id, resources)
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,34 @@
1
+ require "senkyoshi/models/grade_criteria"
2
+
3
+ module Senkyoshi
4
+ class GradeRangeCriteria < GradeCriteria
5
+ attr_reader(:min_score)
6
+
7
+ def initialize(id, outcome_def_id, negated, min_score)
8
+ super(id, outcome_def_id, negated)
9
+ @min_score = min_score
10
+ end
11
+
12
+ def self.get_min_score(xml)
13
+ xml.xpath("./MIN_SCORE/@value").text
14
+ end
15
+
16
+ def self.from_xml(xml)
17
+ id = RuleCriteria.get_id xml
18
+ negated = Senkyoshi.true? RuleCriteria.get_negated xml
19
+ outcome_def_id = GradeCriteria.get_outcome_def_id xml
20
+ min_score = GradeRangePercentCriteria.get_min_score xml
21
+ new(id, outcome_def_id, negated, min_score)
22
+ end
23
+
24
+ def get_completion_type
25
+ COMPLETION_TYPES[:min_score]
26
+ end
27
+
28
+ def make_completion(mod)
29
+ super(mod).tap do |completion_requirement|
30
+ completion_requirement.min_score = @min_score
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,28 @@
1
+ require "senkyoshi/models/grade_range_criteria"
2
+
3
+ module Senkyoshi
4
+ class GradeRangePercentCriteria < GradeRangeCriteria
5
+ def make_completion(mod)
6
+ super(mod).tap do |completion_requirement|
7
+ completion_requirement.min_score = GradeRangePercentCriteria.
8
+ get_percentage(@min_score, @points_possible)
9
+ end
10
+ end
11
+
12
+ def get_points_possible(resources, id)
13
+ resource = resources.find_by_id(id)
14
+ resource.points_possible if resource
15
+ end
16
+
17
+ def self.get_percentage(min_score, points_possible)
18
+ (min_score.to_f / 100) * points_possible.to_f
19
+ end
20
+
21
+ def canvas_conversion(course, content_id, resources)
22
+ set_foreign_ids(resources, @outcome_def_id)
23
+ @points_possible = get_points_possible(resources, get_foreign_id)
24
+
25
+ super(course, content_id, resources)
26
+ end
27
+ end
28
+ end
@@ -57,6 +57,12 @@ module Senkyoshi
57
57
  end
58
58
  end
59
59
 
60
+ def find_outcome_def(outcome_def_id)
61
+ @outcome_definitions.detect do |outcome_def|
62
+ outcome_def.id == outcome_def_id
63
+ end
64
+ end
65
+
60
66
  def canvas_conversion(course, resources = nil)
61
67
  convert_categories(course)
62
68
  @outcome_definitions.
@@ -0,0 +1,84 @@
1
+ require "senkyoshi/models/resource"
2
+
3
+ module Senkyoshi
4
+ class Heirarchy
5
+ def self.item_iterator(item, course_toc, discussion_boards)
6
+ if item.search("item").count.zero?
7
+ toc_item = setup_item(item, item.parent, course_toc)
8
+ toc_item[:indent] = 0
9
+ set_discussion_boards(discussion_boards, toc_item)
10
+ else
11
+ item.search("item").flat_map do |internal_item|
12
+ toc_item = setup_item(internal_item, item, course_toc)
13
+ toc_item[:indent] = get_indent(internal_item)
14
+ toc_item = set_discussion_boards(discussion_boards, toc_item)
15
+ toc_item
16
+ end
17
+ end
18
+ end
19
+
20
+ def self.get_indent(item, indent = -2)
21
+ return indent if item.parent.name == "organization"
22
+ indent += 1
23
+ get_indent(item.parent, indent)
24
+ end
25
+
26
+ def self.set_discussion_boards(discussion_boards, toc_item)
27
+ if toc_item[:internal_handle] == "discussion_board_entry"
28
+ resource = discussion_boards.select do |db|
29
+ title_attribute = db.attributes["title"] || db.attributes["bb:title"]
30
+ title_attribute.value == toc_item[:title]
31
+ end
32
+ if resource.count == 1
33
+ file_attribute = resource.first.attributes["file"] ||
34
+ resource.first.attributes["bb:file"]
35
+ toc_item[:file_name] = file_attribute.value.gsub(".dat", "")
36
+ end
37
+ end
38
+ toc_item
39
+ end
40
+
41
+ def self.setup_item(item, parent_item, course_toc)
42
+ if item.attributes["identifierref"]
43
+ title = item.at("title").text
44
+ if title == "--TOP--"
45
+ file_name = item.parent.attributes["identifierref"].value
46
+ title = item.parent.at("title").text
47
+ item_id = item.parent.attributes["identifierref"].
48
+ value.gsub("res", "")
49
+ else
50
+ file_name = item.attributes["identifierref"].value
51
+ if parent_item.attributes["identifierref"]
52
+ item_id = parent_item.attributes["identifierref"].
53
+ value.gsub("res", "")
54
+ else
55
+ item_id = item.attributes["identifierref"].value.gsub("res", "")
56
+ end
57
+ end
58
+ toc_item = course_toc.
59
+ detect { |ct| ct[:original_file] == file_name } || {}
60
+ toc_item[:file_name] = file_name
61
+ toc_item[:title] = title
62
+ toc_item[:parent_id] = get_parent_id(course_toc, item_id)
63
+ toc_item
64
+ end
65
+ end
66
+
67
+ def self.get_parent_id(course_toc, item_id)
68
+ header_ids = get_headers(course_toc, "SUBHEADER")
69
+ if header_ids.empty?
70
+ header_ids = get_headers(course_toc, "CONTENT")
71
+ end
72
+ header_id = header_ids.
73
+ reject { |x| x.to_i > item_id.to_i }.
74
+ min_by { |x| (x.to_i - item_id.to_i).abs }
75
+
76
+ header_id ? "res#{header_id}" : nil
77
+ end
78
+
79
+ def self.get_headers(course_toc, target_type)
80
+ course_toc.select { |ct| ct[:target_type] == target_type }.
81
+ map { |sh| sh[:original_file].gsub("res", "") }
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,15 @@
1
+ module Senkyoshi
2
+ ##
3
+ # Represents a link between Blackboard resources.
4
+ ##
5
+ class Link < FileResource
6
+ def self.get_pre_data(xml_data, _file)
7
+ {
8
+ referrer: xml_data.children.at("REFERRER").attributes["id"].value,
9
+ referred_to: xml_data.children.at("REFERREDTO").attributes["id"].value,
10
+ referred_to_title: xml_data.children.at("TITLE").
11
+ attributes["value"].value,
12
+ }
13
+ end
14
+ end
15
+ end
@@ -10,6 +10,12 @@ module Senkyoshi
10
10
  @module_items = []
11
11
  end
12
12
 
13
+ def self.find_module_from_item_id(modules, id)
14
+ modules.detect do |mod|
15
+ mod.module_items.detect { |item| item.identifierref == id }
16
+ end
17
+ end
18
+
13
19
  def canvas_conversion(*)
14
20
  CanvasCc::CanvasCC::Models::CanvasModule.new.tap do |cc_module|
15
21
  cc_module.identifier = @identifier
@@ -0,0 +1,86 @@
1
+ module Senkyoshi
2
+ class ModuleConverter
3
+ def self.set_modules(course, pre_data)
4
+ master_module = Course.master_module(course)
5
+ subheaders = get_subheaders(pre_data)
6
+ pre_data.each do |data|
7
+ if check_module_header(data, subheaders)
8
+ course = add_canvas_module(course, data)
9
+ elsif data[:target_type] == "CONTENT"
10
+ module_item = create_module_subheader(data)
11
+ course = add_canvas_module_item(course, module_item, data)
12
+ elsif data[:target_type] == nil || data[:target_type] == "APPLICATION"
13
+ module_item = master_module.module_items.
14
+ detect { |i| i.identifier == data[:file_name] }
15
+ if module_item
16
+ course = add_canvas_module_item(course, module_item, data)
17
+ end
18
+ end
19
+ end
20
+ course.canvas_modules.delete(master_module)
21
+ course
22
+ end
23
+
24
+ def self.check_module_header(data, subheaders)
25
+ data[:target_type] == "SUBHEADER" || data[:target_type] == "CONTENT" &&
26
+ (subheaders.empty? || !data[:parent_id])
27
+ end
28
+
29
+ def self.get_subheaders(pre_data)
30
+ pre_data.select { |ct| ct[:target_type] == "SUBHEADER" }.
31
+ map { |sh| sh[:original_file].gsub("res", "") }
32
+ end
33
+
34
+ def self.create_module_subheader(data)
35
+ ModuleItem.new(
36
+ data[:title],
37
+ "ContextModuleSubHeader",
38
+ data[:file_name],
39
+ nil,
40
+ data[:indent],
41
+ data[:file_name],
42
+ ).canvas_conversion
43
+ end
44
+
45
+ def self.add_canvas_module(course, data)
46
+ canvas_module = Module.new(data[:title], data[:file_name])
47
+ course.canvas_modules << canvas_module.canvas_conversion
48
+ course
49
+ end
50
+
51
+ def self.add_canvas_module_item(course, module_item, data)
52
+ parent_module = get_subheader_parent(course, data)
53
+ if !parent_module
54
+ parent_module = Module.new(course.title, MAIN_CANVAS_MODULE).
55
+ canvas_conversion
56
+ course.canvas_modules << parent_module
57
+ end
58
+ parent_module.module_items ||= {}
59
+ parent_module.module_items << module_item
60
+ course
61
+ end
62
+
63
+ def self.get_subheader_parent(course, data)
64
+ if !data[:parent_id]
65
+ parent_module = course.canvas_modules.
66
+ detect { |a| a.identifier == MAIN_CANVAS_MODULE }
67
+ else
68
+ parent_module = course.canvas_modules.
69
+ detect { |a| a.identifier == data[:parent_id] }
70
+ if !parent_module
71
+ course.canvas_modules.
72
+ reject { |a| a.title == MASTER_MODULE }.
73
+ each do |cc_module|
74
+ if cc_module.module_items
75
+ items = cc_module.module_items.flatten
76
+ item = items.
77
+ detect { |i| i.identifier == data[:parent_id] }
78
+ parent_module = cc_module if item
79
+ end
80
+ end
81
+ end
82
+ end
83
+ parent_module
84
+ end
85
+ end
86
+ end
@@ -2,15 +2,20 @@ require "senkyoshi/models/resource"
2
2
 
3
3
  module Senkyoshi
4
4
  class ModuleItem < Resource
5
- def initialize(title, type, identifierref, url)
5
+ def initialize(title, type, identifierref, url, indent, id)
6
6
  @title = title
7
- @identifier = Senkyoshi.create_random_hex
7
+ @identifier = id || Senkyoshi.create_random_hex
8
8
  @content_type = type
9
9
  @identifierref = identifierref
10
+ @indent = indent
10
11
  @workflow_state = "active"
11
12
  @url = url
12
13
  end
13
14
 
15
+ def self.find_item_from_id_ref(module_items, id_ref)
16
+ module_items.detect { |item| item.identifierref == id_ref }
17
+ end
18
+
14
19
  def canvas_conversion(*)
15
20
  CanvasCc::CanvasCC::Models::ModuleItem.new.tap do |item|
16
21
  item.title = @title
@@ -18,6 +23,7 @@ module Senkyoshi
18
23
  item.content_type = @content_type
19
24
  item.identifierref = @identifierref
20
25
  item.workflow_state = @workflow_state
26
+ item.indent = @indent
21
27
  item.url = @url
22
28
  end
23
29
  end
@@ -5,12 +5,15 @@ module Senkyoshi
5
5
  class OutcomeDefinition < Resource
6
6
  include Senkyoshi
7
7
  attr_reader :id, :content_id, :asidataid, :is_user_created
8
- def self.from(xml, category)
9
- outcome_definition = OutcomeDefinition.new(category)
8
+ def self.from(xml, category, id = nil)
9
+ outcome_definition = OutcomeDefinition.new(category, id)
10
10
  outcome_definition.iterate_xml(xml)
11
11
  end
12
12
 
13
- def initialize(category)
13
+ def initialize(category, id, content_id = nil, asidataid = nil)
14
+ @id = id
15
+ @content_id = content_id
16
+ @asidataid = asidataid
14
17
  @category = category
15
18
  end
16
19
 
@@ -20,7 +23,9 @@ module Senkyoshi
20
23
  @id = xml.xpath("./@id").text
21
24
  @title = xml.xpath("./TITLE/@value").text
22
25
  @points_possible = xml.xpath("./POINTSPOSSIBLE/@value").text
23
- @is_user_created = true? xml.xpath("./ISUSERCREATED/@value").text
26
+ @is_user_created = Senkyoshi.true?(
27
+ xml.xpath("./ISUSERCREATED/@value").text,
28
+ )
24
29
  self
25
30
  end
26
31
 
@@ -37,7 +42,7 @@ module Senkyoshi
37
42
  def canvas_conversion(course, _ = nil)
38
43
  assignment_group = AssignmentGroup.find_or_create(course, @category)
39
44
  assignment = CanvasCc::CanvasCC::Models::Assignment.new
40
- assignment.identifier = Senkyoshi.create_random_hex
45
+ assignment.identifier = @id
41
46
  assignment.assignment_group_identifier_ref = assignment_group.identifier
42
47
  assignment.title = @title
43
48
  assignment.position = 1
@@ -11,6 +11,7 @@ QTI_TYPE = {
11
11
 
12
12
  module Senkyoshi
13
13
  class QTI < FileResource
14
+ attr_reader(:points_possible)
14
15
  def self.from(data, pre_data, _resource_xids = nil)
15
16
  type = data.at("bbmd_assessmenttype").text
16
17
  qti_class = Senkyoshi.const_get QTI_TYPE[type]
@@ -102,7 +102,7 @@ module Senkyoshi
102
102
  end
103
103
 
104
104
  def get_fraction(answer_text)
105
- if @correct_answers && answer_text == @correct_answers["name"]
105
+ if @correct_answers && answer_text.to_s == @correct_answers["name"].to_s
106
106
  @correct_answers["fraction"].to_f
107
107
  else
108
108
  @incorrect_answers["fraction"].to_f
@@ -125,7 +125,8 @@ module Senkyoshi
125
125
  if score_number > 0
126
126
  @correct_answers["fraction"] = score_number.to_f / @max_score.to_f
127
127
  else
128
- @correct_answers["fraction"] = 0
128
+ # mark as correct when there is no score for the answer
129
+ @correct_answers["fraction"] = 1
129
130
  end
130
131
  end
131
132
  end
@@ -51,7 +51,11 @@ module Senkyoshi
51
51
  end
52
52
 
53
53
  answers << answer
54
- @matches << { id: id, question_text: question, answer_text: answer }
54
+ @matches << {
55
+ id: Senkyoshi.create_random_hex,
56
+ question_text: question,
57
+ answer_text: answer,
58
+ }
55
59
  end
56
60
  end
57
61
  @distractors = matches_array.reject { |i| answers.include?(i) }
@@ -32,9 +32,11 @@ module Senkyoshi
32
32
  def set_order_answers(resprocessing)
33
33
  order_answers = {}
34
34
  correct = resprocessing.at("respcondition[title=correct]")
35
- correct.at("and").children.each_with_index do |varequal, index|
36
- id = varequal.text
37
- order_answers[id] = index + 1
35
+ if correct
36
+ correct.at("and").children.each_with_index do |varequal, index|
37
+ id = varequal.text
38
+ order_answers[id] = index + 1
39
+ end
38
40
  end
39
41
  order_answers
40
42
  end
@@ -0,0 +1,46 @@
1
+ require "senkyoshi/models/grade_completed_criteria"
2
+ require "senkyoshi/models/content_reviewed_criteria"
3
+ require "senkyoshi/models/grade_range_criteria"
4
+ require "senkyoshi/models/grade_range_percent_criteria"
5
+ require "senkyoshi/models/resource"
6
+
7
+ module Senkyoshi
8
+ class Rule < FileResource
9
+ attr_reader(:title, :content_id, :criteria_list, :id)
10
+
11
+ CRITERIA_MAP = {
12
+ grade_completed_criteria: GradeCompletedCriteria,
13
+ content_reviewed_criteria: ContentReviewedCriteria,
14
+ grade_range_criteria: GradeRangeCriteria,
15
+ grade_range_percent_criteria: GradeRangePercentCriteria,
16
+ }.freeze
17
+
18
+ def initialize(resource_id)
19
+ super(resource_id)
20
+ @criteria_list = []
21
+ end
22
+
23
+ def get_criteria_list(xml)
24
+ xml.children.select { |child_xml| !child_xml.blank? }.
25
+ map do |child_xml|
26
+ criteria = CRITERIA_MAP[child_xml.name.downcase.to_sym]
27
+ criteria.from_xml(child_xml) if !criteria.nil?
28
+ end.compact
29
+ end
30
+
31
+ def iterate_xml(xml, _pre_data = nil)
32
+ @title = xml.xpath("./TITLE/@value").text
33
+ @content_id = xml.xpath("./CONTENT_ID/@value").text
34
+ @id = xml.xpath("./@id").text
35
+ @criteria_list = get_criteria_list(xml.xpath("./CRITERIA_LIST"))
36
+ self
37
+ end
38
+
39
+ def canvas_conversion(course, _resources)
40
+ @criteria_list.each do |criteria|
41
+ criteria.canvas_conversion(course, @content_id, _resources)
42
+ end
43
+ course
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,146 @@
1
+ require "senkyoshi"
2
+
3
+ module Senkyoshi
4
+ class RuleCriteria
5
+ include Senkyoshi
6
+ attr_reader(:id, :negated, :content_id, :asidata_id)
7
+
8
+ COMPLETION_TYPES = {
9
+ must_view: "must_view",
10
+ must_submit: "must_submit",
11
+ min_score: "min_score",
12
+ must_contribute: "must_contribute",
13
+ }.freeze
14
+
15
+ def initialize(id, negated)
16
+ @id = id
17
+ @negated = negated
18
+ end
19
+
20
+ def add_if_unique(collection, item)
21
+ if collection.detect do |collection_item|
22
+ yield(collection_item, item)
23
+ end.nil?
24
+ collection << item
25
+ end
26
+ collection
27
+ end
28
+
29
+ def add_to_module_if_unique(items, item)
30
+ add_if_unique(
31
+ items, item
32
+ ) { |a, b| a.identifierref == b.identifierref }
33
+ end
34
+
35
+ def self.get_id(xml)
36
+ xml.xpath("./@id").text
37
+ end
38
+
39
+ def self.get_negated(xml)
40
+ xml.xpath("./NEGATED/@value").text
41
+ end
42
+
43
+ def self.from_xml(xml)
44
+ new(RuleCriteria.get_id(xml), RuleCriteria.get_negated(xml))
45
+ end
46
+
47
+ ##
48
+ # Determine two identifierrefs are pointed to by items in the same module
49
+ def self.in_same_module?(modules, content_id, resource_id)
50
+ content_module = Module.find_module_from_item_id(modules, content_id)
51
+ resource_module = Module.find_module_from_item_id(modules, resource_id)
52
+ return nil if content_module.nil? || resource_module.nil?
53
+ content_module == resource_module
54
+ end
55
+
56
+ def self.module_prerequisite?(modules, content_id, resource_id)
57
+ in_same_module?(modules, content_id, resource_id) == false
58
+ end
59
+
60
+ def self.module_completion_requirement?(modules, content_id, resource_id)
61
+ in_same_module?(modules, content_id, resource_id) == true
62
+ end
63
+
64
+ ##
65
+ # Returns the most applicable id. If we have an assignment id return that,
66
+ # otherwise return the content id.
67
+ ##
68
+ def get_id
69
+ @asidata_id || @content_id
70
+ end
71
+
72
+ ##
73
+ # Factory method to construct a completion requirement. Should be passed
74
+ # the module that the completion requirement should belong to
75
+ ##
76
+ def make_completion(mod)
77
+ CanvasCc::CanvasCC::Models::ModuleCompletionRequirement.new.tap do |req|
78
+ mod_item = ModuleItem.find_item_from_id_ref(
79
+ mod.module_items, get_foreign_id
80
+ )
81
+
82
+ req.identifierref = mod_item.identifier if mod_item
83
+ req.type = get_completion_type
84
+ end
85
+ end
86
+
87
+ ##
88
+ # Factory method to construct a module prerequisite. Should be passed the
89
+ # prerequisite module
90
+ ##
91
+ def make_prereq(prereq_module)
92
+ CanvasCc::CanvasCC::Models::ModulePrerequisite.new.tap do |prereq|
93
+ prereq.identifierref = prereq_module.identifier
94
+ prereq.type = "context_module"
95
+ end
96
+ end
97
+
98
+ ##
99
+ # Use gradebook to find assignment id if applicable
100
+ ##
101
+ def set_ids(content_id, resources)
102
+ @content_id = content_id
103
+ gradebook = resources.find_instances_of(Gradebook).first
104
+ if gradebook
105
+ outcome_def = gradebook.outcome_definitions.detect do |out_def|
106
+ out_def.content_id == content_id
107
+ end
108
+ @asidata_id = outcome_def.asidataid if outcome_def
109
+ end
110
+ end
111
+
112
+ def canvas_conversion(course, content_id, resources)
113
+ set_ids(content_id, resources)
114
+
115
+ mod = Module.find_module_from_item_id course.canvas_modules, get_id
116
+
117
+ is_completion = RuleCriteria.module_completion_requirement?(
118
+ course.canvas_modules, get_id, get_foreign_id
119
+ )
120
+
121
+ is_prereq = RuleCriteria.module_prerequisite?(
122
+ course.canvas_modules, get_id, get_foreign_id
123
+ )
124
+
125
+ if is_completion
126
+ add_to_module_if_unique(
127
+ mod.completion_requirements, make_completion(mod)
128
+ )
129
+ elsif is_prereq
130
+ prereq_module = Module.find_module_from_item_id(
131
+ course.canvas_modules, get_foreign_id
132
+ )
133
+
134
+ add_to_module_if_unique(
135
+ mod.prerequisites, make_prereq(prereq_module)
136
+ )
137
+
138
+ add_to_module_if_unique(
139
+ prereq_module.completion_requirements, make_completion(prereq_module)
140
+ )
141
+ end
142
+
143
+ course
144
+ end
145
+ end
146
+ end
@@ -35,6 +35,7 @@ module Senkyoshi
35
35
 
36
36
  def _set_body(original_body, url, extendeddata)
37
37
  body = original_body.dup
38
+
38
39
  if !url.empty?
39
40
  body = %{
40
41
  <a href="#{url}">
@@ -43,12 +44,21 @@ module Senkyoshi
43
44
  #{body}
44
45
  }
45
46
  end
47
+ if @referred_to_title.present?
48
+ body = %{
49
+ <a href="$CANVAS_COURSE_REFERENCE$#{@referred_to_title}">
50
+ Course Link: #{@referred_to_title}
51
+ </a>
52
+ #{body}
53
+ }
54
+ end
46
55
  if extendeddata
47
56
  body = %{
48
57
  #{body}
49
58
  #{_extendeddata(extendeddata)}
50
59
  }
51
60
  end
61
+
52
62
  body
53
63
  end
54
64
 
@@ -63,10 +73,10 @@ module Senkyoshi
63
73
  end
64
74
 
65
75
  def _component_label(node)
66
- visible = true?(node.search("vislableToStudents/@value").to_s)
76
+ visible = Senkyoshi.true?(node.search("vislableToStudents/@value").to_s)
67
77
  if visible
68
78
  component_label = node.search("componentLabel/@value").to_s
69
- overridden = true?(node.search("labelOverridden/@value").to_s)
79
+ overridden = Senkyoshi.true?(node.search("labelOverridden/@value").to_s)
70
80
  if overridden
71
81
  component_label
72
82
  else
@@ -1,3 +1,3 @@
1
1
  module Senkyoshi
2
- VERSION = "1.0.3".freeze
2
+ VERSION = "1.0.4".freeze
3
3
  end
@@ -20,21 +20,27 @@ require "senkyoshi/models/assessment"
20
20
  require "senkyoshi/models/question_bank"
21
21
  require "senkyoshi/models/survey"
22
22
 
23
+ require "senkyoshi/models/heirarchy"
24
+
23
25
  require "senkyoshi/models/announcement"
24
26
  require "senkyoshi/models/answer"
25
- require "senkyoshi/models/qti"
26
27
  require "senkyoshi/models/assignment"
27
28
  require "senkyoshi/models/assignment_group"
29
+ require "senkyoshi/models/attachment"
28
30
  require "senkyoshi/models/blog"
29
31
  require "senkyoshi/models/content"
30
32
  require "senkyoshi/models/content_file"
31
33
  require "senkyoshi/models/course"
34
+ require "senkyoshi/models/course_toc"
35
+ require "senkyoshi/models/external_url"
32
36
  require "senkyoshi/models/file"
33
37
  require "senkyoshi/models/forum"
34
38
  require "senkyoshi/models/gradebook"
35
39
  require "senkyoshi/models/group"
40
+ require "senkyoshi/models/link"
36
41
  require "senkyoshi/models/module"
37
42
  require "senkyoshi/models/module_item"
43
+ require "senkyoshi/models/qti"
38
44
  require "senkyoshi/models/question"
39
45
  require "senkyoshi/models/quiz"
40
46
  require "senkyoshi/models/resource"
@@ -43,7 +49,7 @@ require "senkyoshi/models/staff_info"
43
49
  require "senkyoshi/models/wikipage"
44
50
  require "senkyoshi/models/attachment"
45
51
  require "senkyoshi/models/external_url"
46
-
52
+ require "senkyoshi/models/rule"
47
53
  require "senkyoshi/exceptions"
48
54
 
49
55
  module Senkyoshi
@@ -57,28 +63,21 @@ module Senkyoshi
57
63
  content: "Content",
58
64
  staffinfo: "StaffInfo",
59
65
  gradebook: "Gradebook",
66
+ rule: "Rule",
60
67
  }.freeze
61
68
 
62
69
  PRE_RESOURCE_TYPE = {
63
- content: "Content",
70
+ coursetoc: "CourseToc",
64
71
  gradebook: "Gradebook",
72
+ link: "Link",
65
73
  courseassessment: "QTI",
66
74
  }.freeze
67
75
 
68
- def self.parse_manifest(zip_file, manifest, resource_xids)
69
- doc = Nokogiri::XML.parse(manifest)
70
- resources = doc.at("resources")
71
- organizations = doc.at("organizations")
72
- iterate_xml(organizations, resources, zip_file, resource_xids).
73
- flatten - ["", nil]
74
- end
75
-
76
- def self.iterate_xml(organizations, resources, zip_file, resource_xids)
77
- pre_data = pre_iterator(organizations, resources, zip_file)
76
+ def self.iterate_xml(resources, zip_file, resource_xids, pre_data)
78
77
  staff_info = StaffInfo.new
79
78
  iterator_master(resources, zip_file) do |xml_data, type, file|
80
79
  if RESOURCE_TYPE[type.to_sym]
81
- single_pre_data = get_single_pre_data(pre_data, file)
80
+ single_pre_data = get_single_pre_data(pre_data, file) || {}
82
81
  res_class = Senkyoshi.const_get RESOURCE_TYPE[type.to_sym]
83
82
  case type
84
83
  when "staffinfo"
@@ -87,12 +86,12 @@ module Senkyoshi
87
86
  res_class.from(xml_data, single_pre_data, resource_xids)
88
87
  end
89
88
  end
90
- end
89
+ end.flatten - ["", nil]
91
90
  end
92
91
 
93
92
  def self.get_single_pre_data(pre_data, file)
94
- pre_data.detect do |d|
95
- d[:file_name] == file || d[:assignment_id] == file
93
+ pre_data.detect do |data_item|
94
+ data_item[:file_name] == file || data_item[:assignment_id] == file
96
95
  end || { file_name: file }
97
96
  end
98
97
 
@@ -119,8 +118,9 @@ module Senkyoshi
119
118
  pre_data[type].push(data) if data
120
119
  end
121
120
  end
121
+ pre_data["content"] = build_heirarchy(organizations, resources,
122
+ pre_data["coursetoc"]) - ["", nil]
122
123
  pre_data = connect_content(pre_data)
123
- build_heirarchy(organizations, pre_data)
124
124
  end
125
125
 
126
126
  def self.connect_content(pre_data)
@@ -130,39 +130,33 @@ module Senkyoshi
130
130
  detect { |g| g[:content_id] == content[:file_name] }
131
131
  content.merge!(gradebook) if gradebook
132
132
  end
133
+
133
134
  if pre_data["courseassessment"]
134
135
  course_assessment = pre_data["courseassessment"].
135
136
  detect { |ca| ca[:original_file_name] == content[:assignment_id] }
136
137
  content.merge!(course_assessment) if course_assessment
137
138
  end
138
- end
139
- pre_data["content"]
140
- end
141
139
 
142
- def self.build_heirarchy(organizations, pre_data)
143
- unset_id = "{unset id}"
144
- parents = pre_data.
145
- select { |p| p[:parent_id] == unset_id }
146
- parents_ids = parents.map { |u| u[:id] }
147
- pre_data.each do |content|
148
- parent_id = content[:parent_id]
149
- parent = pre_data.detect { |p| p[:id] == parent_id }
150
- if parent_id == unset_id
151
- content[:title] = get_title(organizations, content)
152
- elsif parent[:parent_id] == unset_id
153
- content[:parent_title] = parent[:title]
140
+ if pre_data["link"]
141
+ matching_link = pre_data["link"].detect do |link|
142
+ link[:referrer] == content[:file_name]
143
+ end
144
+
145
+ if matching_link
146
+ content[:referred_to_title] = matching_link[:referred_to_title]
147
+ end
154
148
  end
155
- next if parents_ids.include?(content[:id])
156
- next if parents_ids.include?(parent_id)
157
- parents_ids << parent_id
158
- parent[:parent_id] = parent[:id]
159
- parent[:parent_title] = nil
160
149
  end
150
+
151
+ pre_data["content"]
161
152
  end
162
153
 
163
- def self.get_title(organizations, content)
164
- item = organizations.at("item[@identifierref=#{content[:file_name]}]")
165
- item.parent.at("title").text
154
+ def self.build_heirarchy(organizations, resources, course_toc)
155
+ discussion_boards = resources.
156
+ search("resource[type=\"resource/x-bb-discussionboard\"]")
157
+ organizations.at("organization").children.flat_map do |item|
158
+ Heirarchy.item_iterator(item, course_toc, discussion_boards)
159
+ end
166
160
  end
167
161
 
168
162
  ##
data/lib/senkyoshi.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  require "senkyoshi/version"
2
2
  require "senkyoshi/xml_parser"
3
+ require "senkyoshi/models/module_converter"
3
4
  require "senkyoshi/canvas_course"
4
5
  require "senkyoshi/collection"
5
6
  require "senkyoshi/configuration"
@@ -15,6 +16,8 @@ require "senkyoshi/exceptions"
15
16
  module Senkyoshi
16
17
  FILE_BASE = "$IMS-CC-FILEBASE$".freeze
17
18
  DIR_BASE = "$CANVAS_COURSE_REFERENCE$/files/folder".freeze
19
+ MAIN_CANVAS_MODULE = "aj_main_module".freeze
20
+ MASTER_MODULE = "master_module".freeze
18
21
 
19
22
  class << self
20
23
  attr_writer :configuration
@@ -41,9 +44,17 @@ module Senkyoshi
41
44
  resource_xids = resources.resources.
42
45
  map(&:xid).
43
46
  select { |r| r.include?("xid-") }
44
- resources.add(Senkyoshi.parse_manifest(file, manifest, resource_xids))
45
47
 
46
- course = create_canvas_course(resources, zip_path)
48
+ xml = Nokogiri::XML.parse(manifest)
49
+ xml_resources = xml.at("resources")
50
+ xml_organizations = xml.at("organizations")
51
+
52
+ pre_data = Senkyoshi.pre_iterator(xml_organizations, xml_resources, file)
53
+ resources.add(
54
+ Senkyoshi.iterate_xml(xml_resources, file, resource_xids, pre_data),
55
+ )
56
+
57
+ course = create_canvas_course(resources, zip_path, pre_data)
47
58
  build_file(course, imscc_path, resources)
48
59
  end
49
60
  end
@@ -73,12 +84,21 @@ module Senkyoshi
73
84
  resources.each(&:cleanup)
74
85
  end
75
86
 
76
- def self.create_canvas_course(resources, zip_name)
87
+ def self.create_canvas_course(resources, zip_name, pre_data)
77
88
  course = CanvasCc::CanvasCC::Models::Course.new
78
89
  course.course_code = zip_name
79
- resources.each do |resource|
90
+
91
+ # Wait until after we set modules to convert Rules
92
+ resources.find_instances_not_of([Rule]).each do |resource|
80
93
  course = resource.canvas_conversion(course, resources)
81
94
  end
95
+
96
+ course = ModuleConverter.set_modules(course, pre_data)
97
+
98
+ resources.find_instances_of(Rule).each do |rule|
99
+ course = rule.canvas_conversion(course, resources)
100
+ end
101
+
82
102
  course
83
103
  end
84
104
 
@@ -91,7 +111,7 @@ module Senkyoshi
91
111
  end
92
112
  end
93
113
 
94
- def true?(obj)
114
+ def self.true?(obj)
95
115
  obj.to_s == "true"
96
116
  end
97
117
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: senkyoshi
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.3
4
+ version: 1.0.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Atomic Jolt
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-01-23 00:00:00.000000000 Z
11
+ date: 2017-01-31 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: pry-byebug
@@ -177,14 +177,23 @@ files:
177
177
  - lib/senkyoshi/models/blog.rb
178
178
  - lib/senkyoshi/models/content.rb
179
179
  - lib/senkyoshi/models/content_file.rb
180
+ - lib/senkyoshi/models/content_reviewed_criteria.rb
180
181
  - lib/senkyoshi/models/course.rb
182
+ - lib/senkyoshi/models/course_toc.rb
181
183
  - lib/senkyoshi/models/external_url.rb
182
184
  - lib/senkyoshi/models/file.rb
183
185
  - lib/senkyoshi/models/file_resource.rb
184
186
  - lib/senkyoshi/models/forum.rb
187
+ - lib/senkyoshi/models/grade_completed_criteria.rb
188
+ - lib/senkyoshi/models/grade_criteria.rb
189
+ - lib/senkyoshi/models/grade_range_criteria.rb
190
+ - lib/senkyoshi/models/grade_range_percent_criteria.rb
185
191
  - lib/senkyoshi/models/gradebook.rb
186
192
  - lib/senkyoshi/models/group.rb
193
+ - lib/senkyoshi/models/heirarchy.rb
194
+ - lib/senkyoshi/models/link.rb
187
195
  - lib/senkyoshi/models/module.rb
196
+ - lib/senkyoshi/models/module_converter.rb
188
197
  - lib/senkyoshi/models/module_item.rb
189
198
  - lib/senkyoshi/models/outcome_definition.rb
190
199
  - lib/senkyoshi/models/qti.rb
@@ -209,6 +218,8 @@ files:
209
218
  - lib/senkyoshi/models/questions/true_false.rb
210
219
  - lib/senkyoshi/models/quiz.rb
211
220
  - lib/senkyoshi/models/resource.rb
221
+ - lib/senkyoshi/models/rule.rb
222
+ - lib/senkyoshi/models/rule_criteria.rb
212
223
  - lib/senkyoshi/models/scorm_package.rb
213
224
  - lib/senkyoshi/models/staff_info.rb
214
225
  - lib/senkyoshi/models/survey.rb
@@ -236,7 +247,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
236
247
  version: '0'
237
248
  requirements: []
238
249
  rubyforge_project:
239
- rubygems_version: 2.4.8
250
+ rubygems_version: 2.5.1
240
251
  signing_key:
241
252
  specification_version: 4
242
253
  summary: Converts Blackboard zip file to Canvas Common Cartridge