senkyoshi 1.0.1 → 1.0.2

Sign up to get free protection for your applications and to get access to all the features.
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