senkyoshi 1.0.1 → 1.0.2

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 63edf40048f5120e752632b9543bad9e4e94b89b
4
- data.tar.gz: 5025f718ff91feb1119a5ce0ce28a69f31419ec0
3
+ metadata.gz: 13edfb731484992561073e292a28f75a15bfaa5d
4
+ data.tar.gz: 528e83f4e9f0a60f88a40cd8d503e35b07fa8655
5
5
  SHA512:
6
- metadata.gz: a4e99212a4e440653bd0c08965154968bead3244dd51b9a899de53234229c09b986011ce05bff159867d33a325a20f7ba51745084b42ccae102deaf27e5f3ba7
7
- data.tar.gz: 1ff2837fb0e44abe572bbdd09f4449a4dfeb4174c667a6de777db390c4f14f60aa9b009677749695d80a6e2ac80d8bfb6a19d03c39f57a76b772a4c2450a77d0
6
+ metadata.gz: 470eafc042455cf9221aac1dca5544fb2ad335c5c9ab5f4fc5c368bd270c6ee40d25ffcb4991f5625351f21ead8220f3178315bf9076d2c455f1d421b9debb1d
7
+ data.tar.gz: 57c7176e3b952dbf02dff18ba3546ac02a2ebe35b72bafd3f8b52f220cb9cf759f21c25ab9518d10ebadaafa4fe1eb6f739888badba46d5909de7f39a403cc12
data/README.md CHANGED
@@ -57,13 +57,13 @@ Create a `senkyoshi.yml` and add credentials
57
57
 
58
58
  Run the rake task to convert from .zip to .imscc
59
59
  ```sh
60
- rake imscc
60
+ rake senkyoshi:imscc
61
61
  ```
62
62
  This will take all your files in your source folder and convert them to your outputs folder
63
63
 
64
64
  Run converting files in parallel
65
65
  ```sh
66
- time rake imscc -m
66
+ time rake senkyoshi:imscc -m
67
67
  ```
68
68
 
69
69
  Delete entire outputs folder
@@ -71,6 +71,11 @@ Delete entire outputs folder
71
71
  rake clean
72
72
  ```
73
73
 
74
+ Upload to canvas to process
75
+ ```sh
76
+ rake senkyoshi:upload
77
+ ```
78
+
74
79
  ## Development
75
80
 
76
81
  After checking out the repo, run `bundle install` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
@@ -3,11 +3,16 @@ require "senkyoshi/config"
3
3
  require "senkyoshi/models/scorm_package"
4
4
  require "rest-client"
5
5
 
6
+ require "openssl"
7
+ OpenSSL::SSL::VERIFY_PEER = OpenSSL::SSL::VERIFY_NONE
8
+
6
9
  module Senkyoshi
7
10
  ##
8
11
  # This class represents a canvas course for which we are uploading data to
9
12
  ##
10
13
  class CanvasCourse
14
+ attr_reader :scorm_packages
15
+
11
16
  ##
12
17
  # A new canvas course accepts the metadata for a course
13
18
  # and the pandarus course resourse
@@ -48,14 +53,12 @@ module Senkyoshi
48
53
  ##
49
54
  def self.from_metadata(metadata, blackboard_export = nil)
50
55
  course_name = metadata[:name] || metadata[:title]
51
- courses = client.list_active_courses_in_account(Senkyoshi.account_id)
52
- canvas_course = courses.detect { |course| course.name == course_name } ||
53
- client.create_new_course(
54
- Senkyoshi.account_id,
55
- course: {
56
- name: course_name,
57
- },
58
- )
56
+ canvas_course = client.create_new_course(
57
+ Senkyoshi.account_id,
58
+ course: {
59
+ name: course_name,
60
+ },
61
+ )
59
62
  CanvasCourse.new(metadata, canvas_course, blackboard_export)
60
63
  end
61
64
 
@@ -76,6 +79,7 @@ module Senkyoshi
76
79
  assignment__external_tool_tag_attributes__: {
77
80
  url: url,
78
81
  },
82
+ assignment__points_possible__: scorm_package["points_possible"],
79
83
  }
80
84
 
81
85
  CanvasCourse.client.create_assignment(
@@ -101,7 +105,9 @@ module Senkyoshi
101
105
  },
102
106
  SharedAuthorization: Senkyoshi.scorm_shared_auth,
103
107
  ) do |resp|
104
- JSON.parse(resp.body)["response"]
108
+ result = JSON.parse(resp.body)["response"]
109
+ result["points_possible"] = scorm_package.points_possible
110
+ result
105
111
  end
106
112
  end
107
113
  end
@@ -1,99 +1,10 @@
1
- require "senkyoshi/models/assignment_group"
2
- require "senkyoshi/models/assignment"
3
- require "senkyoshi/models/question"
4
- require "senkyoshi/models/resource"
1
+ require "senkyoshi/models/qti"
5
2
 
6
3
  module Senkyoshi
7
- class Assessment < Resource
8
- def initialize
9
- @title = ""
10
- @description = ""
11
- @quiz_type = "assignment"
12
- @points_possible = 0
13
- @items = []
14
- @group_name = ""
15
- @workflow_state = "published"
16
- @available = true
17
- end
18
-
4
+ class Assessment < QTI
19
5
  def iterate_xml(data, pre_data)
20
- pre_data ||= {}
21
- @id = pre_data[:assignment_id] || Senkyoshi.create_random_hex
22
- @title = data.at("assessment").attributes["title"].value
23
- @points_possible = data.at("qmd_absolutescore_max").text
24
- @description = data.at("presentation_material").
25
- at("mat_formattedtext").text
26
- @group_name = data.at("bbmd_assessmenttype").text
27
- case @group_name.downcase
28
- when "survey"
29
- @quiz_type = "survey"
30
- end
31
- data.at("section").children.map do |item|
32
- @items.push(item) if item.name == "item"
33
- end
34
- self
35
- end
36
-
37
- def canvas_conversion(course, resources)
38
- if @items.count > 0
39
- assessment = CanvasCc::CanvasCC::Models::Assessment.new
40
- assessment.identifier = @id
41
- course = create_assignment_group(course, resources)
42
- assignment = create_assignment
43
- assignment.quiz_identifier_ref = assessment.identifier
44
- course.assignments << assignment
45
- assessment = setup_assessment(assessment, assignment, resources)
46
- course.assessments << assessment
47
- end
48
- course
49
- end
50
-
51
- def setup_assessment(assessment, assignment, resources)
52
- assessment.title = @title
53
- assessment.description = fix_html(@description, resources)
54
- assessment.available = @available
55
- assessment.quiz_type = @quiz_type
56
- assessment.points_possible = @points_possible
57
- assessment = create_items(assessment, resources)
58
- assessment.assignment = assignment
59
- assessment
60
- end
61
-
62
- def create_items(assessment, resources)
63
- @items = @items - ["", nil]
64
- questions = @items.map do |item|
65
- Question.from(item)
66
- end
67
- assessment.items = []
68
- questions.each do |item|
69
- assessment = item.canvas_conversion(assessment, resources)
70
- end
71
- assessment
72
- end
73
-
74
- def create_assignment_group(course, resources)
75
- group = course.assignment_groups.detect { |a| a.title == @group_name }
76
- if group
77
- @group_id = group.identifier
78
- else
79
- @group_id = Senkyoshi.create_random_hex
80
- assignment_group = AssignmentGroup.new(@group_name, @group_id)
81
- course = assignment_group.canvas_conversion(course, resources)
82
- end
83
- course
84
- end
85
-
86
- def create_assignment
87
- assignment = CanvasCc::CanvasCC::Models::Assignment.new
88
- assignment.identifier = Senkyoshi.create_random_hex
89
- assignment.assignment_group_identifier_ref = @group_id
90
- assignment.title = @title
91
- assignment.position = 1
92
- assignment.submission_types << "online_quiz"
93
- assignment.grading_type = "points"
94
- assignment.workflow_state = @workflow_state
95
- assignment.points_possible = @points_possible
96
- assignment
6
+ @quiz_type = "assignment"
7
+ super
97
8
  end
98
9
  end
99
10
  end
@@ -0,0 +1,21 @@
1
+ require "senkyoshi/models/content"
2
+ require "senkyoshi/models/module_item"
3
+
4
+ module Senkyoshi
5
+ class Attachment < Content
6
+ def iterate_xml(xml, pre_data)
7
+ super
8
+ @module_item = ModuleItem.new(
9
+ @title,
10
+ @module_type,
11
+ @files.first.name,
12
+ @url,
13
+ ).canvas_conversion
14
+ self
15
+ end
16
+
17
+ def canvas_conversion(course, _resource)
18
+ create_module(course)
19
+ end
20
+ end
21
+ end
@@ -1,4 +1,5 @@
1
1
  require "senkyoshi/models/resource"
2
+ require "senkyoshi/models/content_file"
2
3
 
3
4
  module Senkyoshi
4
5
  class Content < Resource
@@ -8,11 +9,11 @@ module Senkyoshi
8
9
  "x-bb-assignment" => "Assignment",
9
10
  "x-bbpi-selfpeer-type1" => "Assignment",
10
11
  "x-bb-document" => "WikiPage",
11
- "x-bb-file" => "WikiPage",
12
- "x-bb-audio" => "WikiPage",
13
- "x-bb-image" => "WikiPage",
14
- "x-bb-video" => "WikiPage",
15
- "x-bb-externallink" => "WikiPage",
12
+ "x-bb-file" => "Attachment",
13
+ "x-bb-audio" => "Attachment",
14
+ "x-bb-image" => "Attachment",
15
+ "x-bb-video" => "Attachment",
16
+ "x-bb-externallink" => "ExternalUrl",
16
17
  "x-bb-blankpage" => "WikiPage",
17
18
  "x-bb-lesson" => "WikiPage",
18
19
  "x-bb-folder" => "WikiPage",
@@ -21,11 +22,28 @@ module Senkyoshi
21
22
  "x-bb-syllabus" => "WikiPage",
22
23
  }.freeze
23
24
 
24
- attr_accessor(:title, :body, :id, :files)
25
+ MODULE_TYPES = {
26
+ "Senkyoshi::Attachment" => "Attachment",
27
+ "Senkyoshi::Assignment" => "Assignment",
28
+ "Senkyoshi::ExternalUrl" => "ExternalUrl",
29
+ "Senkyoshi::WikiPage" => "WikiPage",
30
+ "Senkyoshi::Quiz" => "Quizzes::Quiz",
31
+ }.freeze
32
+
33
+ attr_accessor(:title, :body, :id, :files, :url)
34
+ attr_reader(:extendeddata)
25
35
 
26
- def self.from(xml, pre_data)
36
+ def self.from(xml, pre_data, resource_xids)
27
37
  type = xml.xpath("/CONTENT/CONTENTHANDLER/@value").first.text
28
38
  type.slice! "resource/"
39
+ xml.xpath("//FILES/FILE").each do |file|
40
+ file_name = ContentFile.clean_xid file.at("NAME").text
41
+ is_attachment = CONTENT_TYPES[type] == "Attachment"
42
+ if !resource_xids.include?(file_name) && is_attachment
43
+ type = "x-bb-document"
44
+ break
45
+ end
46
+ end
29
47
  if content_type = CONTENT_TYPES[type]
30
48
  content_class = Senkyoshi.const_get content_type
31
49
  content = content_class.new
@@ -35,25 +53,33 @@ module Senkyoshi
35
53
 
36
54
  def iterate_xml(xml, pre_data)
37
55
  @points = pre_data[:points] || 0
56
+ @parent_title = pre_data[:parent_title]
38
57
  @title = xml.xpath("/CONTENT/TITLE/@value").first.text
39
58
  @url = xml.at("URL")["value"]
40
59
  @body = xml.xpath("/CONTENT/BODY/TEXT").first.text
60
+ @extendeddata = xml.at("/CONTENT/EXTENDEDDATA/ENTRY")
61
+ if @extendeddata
62
+ @extendeddata = @extendeddata.text
63
+ end
41
64
  @type = xml.xpath("/CONTENT/RENDERTYPE/@value").first.text
42
65
  @parent_id = pre_data[:parent_id]
43
- bb_type = xml.xpath("/CONTENT/CONTENTHANDLER/@value").first.text
44
- bb_type.slice! "resource/"
45
- @module_type = CONTENT_TYPES[bb_type]
66
+ @module_type = MODULE_TYPES[self.class.name]
46
67
  @id = xml.xpath("/CONTENT/@id").first.text
47
68
  if pre_data[:assignment_id] && !pre_data[:assignment_id].empty?
48
69
  @id = pre_data[:assignment_id]
49
70
  end
50
- @module_item = set_module if @module_type
51
71
  @files = xml.xpath("//FILES/FILE").map do |file|
52
72
  ContentFile.new(file)
53
73
  end
74
+ @module_item = set_module if @module_type
54
75
  self
55
76
  end
56
77
 
78
+ def set_module
79
+ module_item = ModuleItem.new(@title, @module_type, @id, @url)
80
+ module_item.canvas_conversion
81
+ end
82
+
57
83
  def get_pre_data(xml, file_name)
58
84
  id = xml.xpath("/CONTENT/@id").first.text
59
85
  parent_id = xml.xpath("/CONTENT/PARENTID/@value").first.text
@@ -64,12 +90,6 @@ module Senkyoshi
64
90
  }
65
91
  end
66
92
 
67
- def set_module
68
- @module_type = "Quizzes::Quiz" if @module_type == "Quiz"
69
- module_item = ModuleItem.new(@title, @module_type, @id)
70
- module_item.canvas_conversion
71
- end
72
-
73
93
  def canvas_conversion(course, _resources = nil)
74
94
  course
75
95
  end
@@ -81,7 +101,8 @@ module Senkyoshi
81
101
  if cc_module
82
102
  cc_module.module_items << @module_item
83
103
  else
84
- cc_module = Module.new(@title, @parent_id)
104
+ title = @parent_title || @title
105
+ cc_module = Module.new(title, @parent_id)
85
106
  cc_module = cc_module.canvas_conversion
86
107
  cc_module.module_items << @module_item
87
108
  course.canvas_modules << cc_module
@@ -2,17 +2,34 @@ require "senkyoshi/models/resource"
2
2
 
3
3
  module Senkyoshi
4
4
  class ContentFile < Resource
5
- attr_accessor(:id, :name, :linkname)
5
+ attr_reader(:id, :name, :linkname)
6
6
 
7
7
  def initialize(xml)
8
8
  @id = xml.xpath("./@id").first.text
9
- @name = xml.xpath("./NAME").first.text
10
9
  @linkname = xml.xpath("./LINKNAME/@value").first.text
10
+ @name = ContentFile.clean_xid xml.xpath("./NAME").first.text
11
11
  end
12
12
 
13
- def canvas_conversion(*)
13
+ ##
14
+ # Remove leading slash if necessary so that ContentFile.name will match
15
+ # the Senkyoshi.xid
16
+ ##
17
+ def self.clean_xid(xid)
18
+ if xid.start_with? "/"
19
+ xid[1..-1]
20
+ else
21
+ xid
22
+ end
23
+ end
24
+
25
+ def self.correct_linkname(canvas_file)
26
+ canvas_file.file_path.split("/").last
27
+ end
28
+
29
+ def canvas_conversion(canvas_file = nil)
30
+ path = canvas_file ? canvas_file.file_path : @linkname
14
31
  query = "?canvas_download=1&amp;canvas_qs_wrap=1"
15
- href = "$IMS_CC_FILEBASE$/#{IMPORTED_FILES_DIRNAME}/#{@linkname}#{query}"
32
+ href = "$IMS_CC_FILEBASE$/#{path}#{query}"
16
33
  %{
17
34
  <a
18
35
  class="instructure_scribd_file instructure_file_link"
@@ -0,0 +1,9 @@
1
+ require "senkyoshi/models/content"
2
+
3
+ module Senkyoshi
4
+ class ExternalUrl < Content
5
+ def canvas_conversion(course, _resource)
6
+ create_module(course)
7
+ end
8
+ end
9
+ end
@@ -3,23 +3,23 @@ require "senkyoshi/exceptions"
3
3
 
4
4
  module Senkyoshi
5
5
  class SenkyoshiFile < Resource
6
- attr_accessor(:id, :location, :name)
7
- @@dir = nil
6
+ attr_accessor(:xid, :location, :path)
8
7
 
9
8
  FILE_BLACKLIST = [
10
9
  "*.dat",
11
10
  ].freeze
12
11
 
13
12
  def initialize(zip_entry)
14
- path = zip_entry.name
15
- id = File.basename(path)
16
- xid = id[/__(xid-[0-9]+_[0-9]+)/, 1]
17
- name = id.gsub(/__xid-[0-9]+_[0-9]+/, "")
18
-
13
+ @path = strip_xid zip_entry.name
19
14
  @location = extract_file(zip_entry) # Location of file on local filesystem
20
- @name = name
21
- @id = id
22
- @xid = xid
15
+
16
+ base_name = File.basename(zip_entry.name)
17
+ @xid = base_name[/__(xid-[0-9]+_[0-9]+)/, 1] ||
18
+ Senkyoshi.create_random_hex
19
+ end
20
+
21
+ def strip_xid(name)
22
+ name.gsub(/__xid-[0-9]+_[0-9]+/, "")
23
23
  end
24
24
 
25
25
  def matches_xid?(xid)
@@ -27,9 +27,9 @@ module Senkyoshi
27
27
  end
28
28
 
29
29
  def extract_file(entry)
30
- @@dir ||= Dir.mktmpdir
30
+ @dir ||= Dir.mktmpdir
31
31
 
32
- name = "#{@@dir}/#{entry.name}"
32
+ name = "#{@dir}/#{entry.name}"
33
33
  path = File.dirname(name)
34
34
  FileUtils.mkdir_p path unless Dir.exist? path
35
35
  entry.extract(name)
@@ -38,9 +38,9 @@ module Senkyoshi
38
38
 
39
39
  def canvas_conversion(course, _resources = nil)
40
40
  file = CanvasCc::CanvasCC::Models::CanvasFile.new
41
- file.identifier = @id
41
+ file.identifier = @xid
42
42
  file.file_location = @location
43
- file.file_path = "#{IMPORTED_FILES_DIRNAME}/#{@name}"
43
+ file.file_path = @path
44
44
  file.hidden = false
45
45
 
46
46
  course.files << file
@@ -50,8 +50,8 @@ module Senkyoshi
50
50
  ##
51
51
  # Remove temporary files
52
52
  ##
53
- def self.cleanup
54
- FileUtils.rm_r @@dir unless @@dir.nil?
53
+ def cleanup
54
+ FileUtils.rm_r @dir unless @dir.nil?
55
55
  end
56
56
 
57
57
  ##
@@ -64,14 +64,15 @@ module Senkyoshi
64
64
  ##
65
65
  # Determine whether or not a file is a metadata file or not
66
66
  ##
67
- def self.metadata_file?(file_names, file)
67
+ def self.metadata_file?(entry_names, file)
68
68
  if File.extname(file.name) == ".xml"
69
69
  # Detect and skip metadata files.
70
- concrete_file = File.join(
70
+ non_meta_file = File.join(
71
71
  File.dirname(file.name),
72
72
  File.basename(file.name, ".xml"),
73
73
  )
74
- file_names.include?(concrete_file)
74
+
75
+ entry_names.include?(non_meta_file)
75
76
  else
76
77
  false
77
78
  end
@@ -89,9 +90,9 @@ module Senkyoshi
89
90
  ##
90
91
  # Determine if a file should be included in course files or not
91
92
  ##
92
- def self.valid_file?(file_names, scorm_paths, file)
93
+ def self.valid_file?(entry_names, scorm_paths, file)
93
94
  return false if SenkyoshiFile.blacklisted? file
94
- return false if SenkyoshiFile.metadata_file? file_names, file
95
+ return false if SenkyoshiFile.metadata_file? entry_names, file
95
96
  return false if SenkyoshiFile.belongs_to_scorm_package? scorm_paths, file
96
97
  true
97
98
  end
@@ -3,16 +3,13 @@ module Senkyoshi
3
3
  def get_pre_data(data, _)
4
4
  categories = get_categories(data)
5
5
  data.search("OUTCOMEDEFINITIONS").children.map do |outcome|
6
- content_id = outcome.at("CONTENTID").attributes["value"].value
7
- assignment_id = outcome.at("ASIDATAID").attributes["value"].value
8
6
  category_id = outcome.at("CATEGORYID").attributes["value"].value
9
- category = categories[category_id]
10
- points = outcome.at("POINTSPOSSIBLE").attributes["value"].value
11
7
  {
12
- category: category,
13
- points: points,
14
- content_id: content_id,
15
- assignment_id: assignment_id,
8
+ category: categories[category_id],
9
+ points: outcome.at("POINTSPOSSIBLE").attributes["value"].value,
10
+ content_id: outcome.at("CONTENTID").attributes["value"].value,
11
+ assignment_id: outcome.at("ASIDATAID").attributes["value"].value,
12
+ due_at: outcome.at("DUE").attributes["value"].value,
16
13
  }
17
14
  end
18
15
  end
@@ -2,12 +2,13 @@ require "senkyoshi/models/resource"
2
2
 
3
3
  module Senkyoshi
4
4
  class ModuleItem < Resource
5
- def initialize(title, type, identifierref)
5
+ def initialize(title, type, identifierref, url)
6
6
  @title = title
7
7
  @identifier = Senkyoshi.create_random_hex
8
8
  @content_type = type
9
9
  @identifierref = identifierref
10
10
  @workflow_state = "active"
11
+ @url = url
11
12
  end
12
13
 
13
14
  def canvas_conversion(*)
@@ -17,6 +18,7 @@ module Senkyoshi
17
18
  item.content_type = @content_type
18
19
  item.identifierref = @identifierref
19
20
  item.workflow_state = @workflow_state
21
+ item.url = @url
20
22
  end
21
23
  end
22
24
  end