github-to-canvas 0.0.54 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/bin/github-to-canvas +147 -25
- data/lib/github-to-canvas.rb +125 -39
- data/lib/github-to-canvas/canvas_dotfile.rb +9 -9
- data/lib/github-to-canvas/canvas_interface.rb +329 -68
- data/lib/github-to-canvas/course_creation_interface.rb +9 -0
- data/lib/github-to-canvas/create_canvas_lesson.rb +8 -32
- data/lib/github-to-canvas/github_interface.rb +35 -0
- data/lib/github-to-canvas/repository_converter.rb +127 -34
- data/lib/github-to-canvas/repository_interface.rb +33 -0
- data/lib/github-to-canvas/update_canvas_lesson.rb +0 -32
- data/lib/github-to-canvas/version.rb +1 -1
- metadata +7 -5
@@ -1,7 +1,145 @@
|
|
1
|
+
require 'byebug'
|
1
2
|
require 'json'
|
2
3
|
require 'rest-client'
|
4
|
+
require 'yaml'
|
3
5
|
class CanvasInterface
|
4
6
|
|
7
|
+
def self.create_course(course_info)
|
8
|
+
# POST /api/v1/accounts/:account_id/courses
|
9
|
+
url = "#{ENV['CANVAS_API_PATH']}/accounts/1/courses"
|
10
|
+
payload = {
|
11
|
+
'course[name]' => course_info[:name],
|
12
|
+
'course[course_code]' => course_info[:course_code]
|
13
|
+
}
|
14
|
+
response = RestClient.post(url, payload, self.headers)
|
15
|
+
JSON.parse(response)
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.create_module(course_id, module_info)
|
19
|
+
# POST /api/v1/courses/:course_id/modules
|
20
|
+
url = "#{ENV['CANVAS_API_PATH']}/courses/#{course_id}/modules"
|
21
|
+
payload = {
|
22
|
+
'module[name]' => module_info[:name]
|
23
|
+
}
|
24
|
+
response = RestClient.post(url, payload, headers={
|
25
|
+
"Authorization" => "Bearer #{ENV['CANVAS_API_KEY']}"
|
26
|
+
})
|
27
|
+
JSON.parse(response)
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.create_lesson(options, name, html)
|
31
|
+
if options[:type] == 'discussion'
|
32
|
+
url = "#{ENV['CANVAS_API_PATH']}/courses/#{options[:course_id]}/#{options[:type]}_topics"
|
33
|
+
else
|
34
|
+
url = "#{ENV['CANVAS_API_PATH']}/courses/#{options[:course_id]}/#{options[:type]}s"
|
35
|
+
end
|
36
|
+
payload = self.build_payload(options, name, html)
|
37
|
+
begin
|
38
|
+
response = RestClient.post(url, payload, self.headers)
|
39
|
+
rescue
|
40
|
+
puts "Something went wrong while pushing lesson #{options[:id]} to course #{options[:course_id]}"
|
41
|
+
abort
|
42
|
+
end
|
43
|
+
if ![200, 201].include? response.code
|
44
|
+
puts "Canvas push failed. #{response.code} status code returned "
|
45
|
+
abort
|
46
|
+
end
|
47
|
+
JSON.parse(response.body)
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.create_quiz(options, quiz_data)
|
51
|
+
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.add_to_module(course_id, module_info, lesson_info)
|
55
|
+
# POST /api/v1/courses/:course_id/modules/:module_id/items
|
56
|
+
url = "#{ENV['CANVAS_API_PATH']}/courses/#{course_id}/modules/#{module_info["id"]}/items"
|
57
|
+
|
58
|
+
if lesson_info["type"] == "Page"
|
59
|
+
payload = {
|
60
|
+
'module_item[title]' => lesson_info["title"],
|
61
|
+
'module_item[type]' => lesson_info["type"],
|
62
|
+
'module_item[indent]' => 0,
|
63
|
+
'module_item[completion_requirement][type]' => 'must_view'
|
64
|
+
}
|
65
|
+
elsif lesson_info["type"] == "Quiz"
|
66
|
+
puts "Quiz needs to be added manually - #{lesson_info['title']} - lesson_info["
|
67
|
+
else
|
68
|
+
|
69
|
+
payload = {
|
70
|
+
'module_item[title]' => lesson_info["title"],
|
71
|
+
'module_item[type]' => lesson_info["type"],
|
72
|
+
'module_item[indent]' => 1,
|
73
|
+
'module_item[completion_requirement][type]' => 'must_submit'
|
74
|
+
}
|
75
|
+
end
|
76
|
+
begin
|
77
|
+
byebug
|
78
|
+
response = RestClient.post(url, payload, self.headers)
|
79
|
+
rescue
|
80
|
+
puts "Something went wrong while add lesson #{lesson_info["id"]} to module #{module_info["id"]} in course #{course_id}" if lesson_info["type"] == "Assignment"
|
81
|
+
puts "Something went wrong while add lesson #{lesson_info["page_url"]} to module #{module_info["id"]} in course #{course_id}" if lesson_info["type"] == "Page"
|
82
|
+
abort
|
83
|
+
end
|
84
|
+
|
85
|
+
|
86
|
+
response
|
87
|
+
|
88
|
+
end
|
89
|
+
|
90
|
+
def self.update_existing_lesson(options, name, html)
|
91
|
+
if options[:type] == "discussion"
|
92
|
+
url = "#{ENV['CANVAS_API_PATH']}/courses/#{options[:course_id]}/#{options[:type]}_topics/#{options[:id]}"
|
93
|
+
else
|
94
|
+
url = "#{ENV['CANVAS_API_PATH']}/courses/#{options[:course_id]}/#{options[:type]}s/#{options[:id]}"
|
95
|
+
end
|
96
|
+
payload = self.build_payload(options, name, html)
|
97
|
+
|
98
|
+
begin
|
99
|
+
headers = self.headers
|
100
|
+
if options[:type] == 'page'
|
101
|
+
response = RestClient.get(url, headers)
|
102
|
+
lesson_info = JSON.parse(response)
|
103
|
+
url = url.sub(/[^\/]+$/, lesson_info["page_id"].to_s)
|
104
|
+
end
|
105
|
+
response = RestClient.put(url, payload, headers)
|
106
|
+
rescue
|
107
|
+
puts "Something went wrong while pushing lesson #{options[:id]} to course #{options[:course_id]}"
|
108
|
+
puts "Make sure you are working on lessons that are not locked"
|
109
|
+
abort
|
110
|
+
""
|
111
|
+
end
|
112
|
+
JSON.parse(response.body)
|
113
|
+
end
|
114
|
+
|
115
|
+
def self.update_all_related_lessons(options, name, html)
|
116
|
+
# Read the local .canvas file if --id <ID> is not used. Otherwise, use provided ID (--course <COURSE> also required)
|
117
|
+
if !options[:id]
|
118
|
+
canvas_data = CanvasDotfile.read_canvas_data
|
119
|
+
response = nil
|
120
|
+
canvas_data[:lessons] = canvas_data[:lessons].map { |lesson|
|
121
|
+
response = self.update_existing_lesson(lesson, name, html)
|
122
|
+
options[:id] = lesson[:id]
|
123
|
+
options[:course_id] = lesson[:course_id]
|
124
|
+
options[:type] = lesson[:type]
|
125
|
+
|
126
|
+
}
|
127
|
+
RepositoryInterface.local_repo_post_submission(options, response)
|
128
|
+
puts "Canvas lesson updated. Lesson available at #{response['html_url']}"
|
129
|
+
else
|
130
|
+
# If an ID (and course) are provided, they are used instead of the .canvas file
|
131
|
+
# Gets the current lesson's type (page or assignment)
|
132
|
+
|
133
|
+
options[:type] = self.get_lesson_info(options[:course_id], options[:id])[1]
|
134
|
+
|
135
|
+
# Implements update on Canvas
|
136
|
+
response = self.update_existing_lesson(options, name, html)
|
137
|
+
RepositoryInterface.local_repo_post_submission(options, response)
|
138
|
+
puts "Canvas lesson updated. Lesson available at #{response['html_url']}"
|
139
|
+
end
|
140
|
+
|
141
|
+
end
|
142
|
+
|
5
143
|
def self.get_lesson_info(course, id)
|
6
144
|
|
7
145
|
lesson_types = ["quizzes", "assignments", "pages", "discussion_topics"]
|
@@ -14,6 +152,7 @@ class CanvasInterface
|
|
14
152
|
info = ""
|
15
153
|
lesson_type_urls.each do |url|
|
16
154
|
begin
|
155
|
+
|
17
156
|
response = RestClient.get(url, headers={
|
18
157
|
"Authorization" => "Bearer #{ENV['CANVAS_API_KEY']}"
|
19
158
|
})
|
@@ -41,34 +180,64 @@ class CanvasInterface
|
|
41
180
|
return
|
42
181
|
end
|
43
182
|
|
183
|
+
url = "#{ENV['CANVAS_API_PATH']}/courses/#{course}"
|
184
|
+
response = RestClient.get(url, self.headers)
|
185
|
+
course_data = JSON.parse(response)
|
186
|
+
|
187
|
+
# /api/v1/courses/:course_id/modules
|
188
|
+
course_info = {
|
189
|
+
name: course_data['name'],
|
190
|
+
id: course_data['id'],
|
191
|
+
modules: []
|
192
|
+
}
|
193
|
+
|
44
194
|
begin
|
45
|
-
results = []
|
46
195
|
index = 1
|
47
196
|
|
48
197
|
while !!index
|
49
|
-
url = "#{ENV['CANVAS_API_PATH']}/courses/#{course}/
|
198
|
+
url = "#{ENV['CANVAS_API_PATH']}/courses/#{course}/modules?page=#{index}&per_page=20"
|
50
199
|
index += 1
|
51
|
-
response = RestClient.get(url, headers
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
results = results + pages
|
200
|
+
response = RestClient.get(url, self.headers)
|
201
|
+
modules = JSON.parse(response.body)
|
202
|
+
|
203
|
+
if ([200, 201].include? response.code) && (!modules.empty?)
|
204
|
+
course_info[:modules] = course_info[:modules] + modules
|
57
205
|
else
|
58
206
|
index = nil
|
59
207
|
end
|
60
208
|
end
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
209
|
+
|
210
|
+
course_info[:modules] = course_info[:modules].map do |mod|
|
211
|
+
new_mod = {
|
212
|
+
id: mod['id'],
|
213
|
+
name: mod['name'],
|
214
|
+
lessons: []
|
215
|
+
}
|
216
|
+
index = 1
|
217
|
+
while !!index
|
218
|
+
url = "#{ENV['CANVAS_API_PATH']}/courses/#{course}/modules/#{mod['id']}/items?page=#{index}&per_page=20"
|
219
|
+
index += 1
|
220
|
+
response = RestClient.get(url, headers={
|
221
|
+
"Authorization" => "Bearer #{ENV['CANVAS_API_KEY']}"
|
222
|
+
})
|
223
|
+
lessons = JSON.parse(response.body)
|
224
|
+
lessons = lessons.map do |lesson|
|
225
|
+
lesson = lesson.slice("id","title","name","indent","type","html_url","page_url","url","completion_requirement", "published")
|
226
|
+
lesson["repository"] = ""
|
227
|
+
lesson['id'] = lesson['url'].gsub(/^(.*[\\\/])/,'')
|
228
|
+
lesson
|
229
|
+
end
|
230
|
+
if ([200, 201].include? response.code) && (!lessons.empty?)
|
231
|
+
new_mod[:lessons] = new_mod[:lessons] + lessons
|
232
|
+
else
|
233
|
+
index = nil
|
234
|
+
end
|
235
|
+
|
236
|
+
end
|
237
|
+
new_mod
|
238
|
+
end
|
239
|
+
|
240
|
+
puts course_info.to_yaml
|
72
241
|
|
73
242
|
rescue
|
74
243
|
puts "Something went wrong while getting info about course #{course}"
|
@@ -76,82 +245,174 @@ class CanvasInterface
|
|
76
245
|
end
|
77
246
|
end
|
78
247
|
|
79
|
-
def self.
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
abort
|
84
|
-
end
|
85
|
-
JSON.parse(response.body)
|
86
|
-
end
|
248
|
+
def self.map_course_info(file_to_convert)
|
249
|
+
course_info = YAML.load(File.read("#{Dir.pwd}/#{file_to_convert}"))
|
250
|
+
course_info[:modules] = course_info[:modules].map do |mod|
|
251
|
+
mod[:lessons] = mod[:lessons].map do |lesson|
|
87
252
|
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
253
|
+
url = lesson["url"]
|
254
|
+
response = RestClient.get(url, headers={
|
255
|
+
"Authorization" => "Bearer #{ENV['CANVAS_API_KEY']}"
|
256
|
+
})
|
257
|
+
begin
|
258
|
+
lesson_data = JSON.parse(response)
|
259
|
+
contents = lesson_data["body"] if lesson["type"] == "Page"
|
260
|
+
contents = lesson_data["message"] if lesson["type"] == "Discussion"
|
261
|
+
contents = lesson_data["description"] if lesson["type"] == "Assignment" || lesson["type"] == "Quiz"
|
262
|
+
if contents.nil?
|
263
|
+
repo = ""
|
264
|
+
else
|
265
|
+
if contents[/data-repo=\"(.*?)"/]
|
266
|
+
repo = contents[/data-repo=\"(.*?)"/]
|
267
|
+
repo = repo.slice(11..-2)
|
268
|
+
elsif contents[/class=\"fis-git-link\" href=\"(.*?)"/]
|
269
|
+
repo = contents[/class=\"fis-git-link\" href=\"(.*?)"/]
|
270
|
+
repo = repo.slice(27..-2)
|
271
|
+
else
|
272
|
+
repo = ""
|
273
|
+
end
|
274
|
+
end
|
275
|
+
rescue
|
276
|
+
puts 'Error while mapping course info.'
|
277
|
+
abort
|
278
|
+
end
|
279
|
+
|
280
|
+
if repo != nil && repo != ""
|
281
|
+
if repo.include?('https://github.com/learn-co-curriculum/')
|
282
|
+
lesson["repository"] = repo
|
283
|
+
else
|
284
|
+
lesson["repository"] = "https://github.com/learn-co-curriculum/" + repo
|
285
|
+
end
|
286
|
+
end
|
287
|
+
sleep(1)
|
288
|
+
lesson
|
289
|
+
end
|
290
|
+
mod
|
102
291
|
end
|
292
|
+
puts course_info.to_yaml
|
103
293
|
end
|
104
294
|
|
105
|
-
def self.
|
106
|
-
|
107
|
-
url = "#{ENV['CANVAS_API_PATH']}/courses/#{course_id}/#{type}_topics/#{id}"
|
108
|
-
else
|
109
|
-
url = "#{ENV['CANVAS_API_PATH']}/courses/#{course_id}/#{type}s/#{id}"
|
110
|
-
end
|
111
|
-
payload = self.build_payload(type, name, new_readme, only_update_content)
|
112
|
-
begin
|
113
|
-
RestClient.put(url, payload, headers={
|
114
|
-
"Authorization" => "Bearer #{ENV['CANVAS_API_KEY']}"
|
115
|
-
})
|
116
|
-
rescue
|
117
|
-
puts "Something went wrong while pushing lesson #{id} to course #{course_id}"
|
118
|
-
nil
|
119
|
-
end
|
295
|
+
def self.submit_to_canvas(course_id, type, name, readme)
|
296
|
+
|
120
297
|
end
|
121
298
|
|
122
|
-
|
123
|
-
|
124
|
-
|
299
|
+
|
300
|
+
|
301
|
+
|
302
|
+
def self.build_payload(options, name, html)
|
303
|
+
if options[:only_update_content]
|
304
|
+
if options[:type] == "assignment"
|
125
305
|
payload = {
|
126
|
-
'assignment[description]' =>
|
306
|
+
'assignment[description]' => html
|
127
307
|
}
|
128
|
-
elsif type == "discussion"
|
308
|
+
elsif options[:type] == "discussion"
|
129
309
|
payload = {
|
130
|
-
'message' =>
|
310
|
+
'message' => html
|
131
311
|
}
|
132
312
|
else
|
133
313
|
payload = {
|
134
|
-
'wiki_page[body]' =>
|
314
|
+
'wiki_page[body]' => html
|
135
315
|
}
|
136
316
|
end
|
137
317
|
else
|
138
|
-
if type == "assignment"
|
318
|
+
if options[:type] == "assignment"
|
139
319
|
payload = {
|
140
320
|
'assignment[name]' => name,
|
141
|
-
'assignment[description]' =>
|
321
|
+
'assignment[description]' => html,
|
322
|
+
'assignment[submission_types][]' => "online_url",
|
323
|
+
'assignment[grading_type]' => 'pass_fail',
|
324
|
+
'assignment[points_possible]' => 1
|
142
325
|
}
|
143
|
-
elsif type == "discussion"
|
326
|
+
elsif options[:type] == "discussion"
|
144
327
|
payload = {
|
145
328
|
'title' => name,
|
146
|
-
'message' =>
|
329
|
+
'message' => html
|
147
330
|
}
|
148
331
|
else
|
149
332
|
payload = {
|
150
333
|
'wiki_page[title]' => name,
|
151
|
-
'wiki_page[body]' =>
|
334
|
+
'wiki_page[body]' => html,
|
152
335
|
'wiki_page[editing_roles]' => "teachers"
|
153
336
|
}
|
154
337
|
end
|
155
338
|
end
|
156
339
|
end
|
340
|
+
|
341
|
+
def self.read_lesson(url)
|
342
|
+
types = ["page", "assignment", "quiz", "discussion"]
|
343
|
+
type = types.find {|type| url.match(type)}
|
344
|
+
if !url.include?(ENV['CANVAS_API_PATH'])
|
345
|
+
url = url.sub(/^.*\/\/.*?\//,"#{ENV['CANVAS_API_PATH']}/")
|
346
|
+
end
|
347
|
+
|
348
|
+
response = RestClient.get(url, headers={
|
349
|
+
"Authorization" => "Bearer #{ENV['CANVAS_API_KEY']}"
|
350
|
+
})
|
351
|
+
lesson_info = JSON.parse(response)
|
352
|
+
lesson_info = lesson_info.slice("title",
|
353
|
+
"name",
|
354
|
+
"description",
|
355
|
+
"body",
|
356
|
+
"message",
|
357
|
+
"shuffle_answers",
|
358
|
+
"allowed_attempts",
|
359
|
+
"question_count"
|
360
|
+
)
|
361
|
+
lesson_info["type"] = type.capitalize
|
362
|
+
if lesson_info["type"] == "Quiz"
|
363
|
+
url = url + "/questions"
|
364
|
+
response = RestClient.get(url, headers={
|
365
|
+
"Authorization" => "Bearer #{ENV['CANVAS_API_KEY']}"
|
366
|
+
})
|
367
|
+
lesson_info["questions"] = JSON.parse(response)
|
368
|
+
lesson_info["questions"] = lesson_info["questions"].map do |question|
|
369
|
+
question.slice("id",
|
370
|
+
"position",
|
371
|
+
"question_name",
|
372
|
+
"question_type",
|
373
|
+
"question_text",
|
374
|
+
"points_possible",
|
375
|
+
"correct_comments_html",
|
376
|
+
"incorrect_comments_html",
|
377
|
+
"neutral_comments_html",
|
378
|
+
"answers"
|
379
|
+
)
|
380
|
+
end
|
381
|
+
end
|
382
|
+
lesson_info.to_yaml
|
383
|
+
end
|
384
|
+
|
385
|
+
def self.create_lesson_from_remote(course_id, module_id, lesson_type, raw_url, yaml_file)
|
386
|
+
url = "#{ENV['CANVAS_API_PATH']}/courses/#{course_id}/modules/#{module_id}/items"
|
387
|
+
if yaml_file
|
388
|
+
data = YAML.load(File.read("#{Dir.pwd}/#{yaml_file}"))
|
389
|
+
payload = {
|
390
|
+
'module_item[type]' => data["type"],
|
391
|
+
'module_item[title]' => data["title"]
|
392
|
+
}
|
393
|
+
else
|
394
|
+
|
395
|
+
end
|
396
|
+
|
397
|
+
|
398
|
+
end
|
399
|
+
|
400
|
+
def self.headers
|
401
|
+
{
|
402
|
+
"Authorization" => "Bearer #{ENV['CANVAS_API_KEY']}"
|
403
|
+
}
|
404
|
+
end
|
405
|
+
|
406
|
+
# def self.create_quiz_from_remote(course_id, module_id, lesson_type, raw_url)
|
407
|
+
# url = "#{ENV['CANVAS_API_PATH']}/courses/#{course_id}/quizzes"
|
408
|
+
# payload = {
|
409
|
+
# 'quiz[title]' =>
|
410
|
+
# }
|
411
|
+
# /api/v1/courses/:course_id/quizzes
|
412
|
+
# data = YAML.load(File.read("#{Dir.pwd}/#{yaml_file}"))
|
413
|
+
# payload = {
|
414
|
+
# 'module_item[type]' => data["type"],
|
415
|
+
# 'module_item[title]' => data["title"]
|
416
|
+
# }
|
417
|
+
# end
|
157
418
|
end
|