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