lms-api 1.2.9 → 1.3.0

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: d6a2b3bff766499f69c000baa9f9f95ae75909c1
4
- data.tar.gz: a9c358a4fdbdffbf3e483d5eaede08b13727a035
3
+ metadata.gz: 777bf9afd9565fde961baae2844dc52c76805923
4
+ data.tar.gz: fac9b9652c3bd22324495f1773bac632299c89d4
5
5
  SHA512:
6
- metadata.gz: 96bf9ce9f000c0eb4bf7176d103c3c60b4320bf8c708f587f8865f008f2460bf81134e4947f86b6a189cba143ca0cacc6958393f7b0d63f74a1719ef291ed493
7
- data.tar.gz: a420885bedb76e1114eb4b97853a79dbe19b13e821d68d34ccb76240226d1e49c6b939d26856a641e2f61f93de31d4acf38fa2adbc8177518c9052c64c25ca5b
6
+ metadata.gz: 8a1b01e99e069189e1ffa1904946f44975f01dd47aaf4c6da0e6fe4d050823edfd2cbbf5edc888d8d501fdf5c049b851941c93c0ba44526ac96b4cf18878394d
7
+ data.tar.gz: f4bad4474f786009e32df7aec3a916a0b6e3a49ee30f2f30c72976394470f2778f4be7bba5a60fa00b76b36c80ec8727cc8505a951abcfd4ea6103f86b9a9601
data/lib/canvas_api.rb ADDED
@@ -0,0 +1,4 @@
1
+ module CanvasApi
2
+ end
3
+
4
+ require "canvas_api/builder"
@@ -0,0 +1,59 @@
1
+ require "canvas_api/render"
2
+
3
+ module CanvasApi
4
+
5
+ class Builder
6
+
7
+ #
8
+ # project_root: This is the directory where the canvas_urls.rb file will be written. This file contains all urls and functions for access to the Canvas API from this gem (lms_api).
9
+ # client_app_path: This where all client side Javascript for accessing the Canvas API will be written.
10
+ # server_app_path: This is where all server side Javascript for accessing the Canvas API will be written. Currently, this is generating GraphQL for Javascript
11
+ #
12
+ def self.build(project_root, client_app_path, server_app_path)
13
+ endpoint = "https://canvas.instructure.com/doc/api"
14
+ directory = HTTParty.get("#{endpoint}/api-docs.json")
15
+
16
+ lms_urls_rb = []
17
+ lms_urls_js = []
18
+ models = []
19
+ queries = []
20
+ mutations = []
21
+ directory["apis"].each do |api|
22
+ puts "Generating #{api['description']}"
23
+ resource = HTTParty.get("#{endpoint}#{api['path']}")
24
+ constants = []
25
+ resource["apis"].each do |resource_api|
26
+ resource_api["operations"].each do |operation|
27
+ parameters = operation["parameters"]
28
+ constants << CanvasApi::Render.new("./templates/constant.erb", api, resource, resource_api, operation, parameters, nil, nil).render
29
+ lms_urls_rb << CanvasApi::Render.new("./templates/rb_url.erb", api, resource, resource_api, operation, parameters, nil, nil).render
30
+ lms_urls_js << CanvasApi::Render.new("./templates/js_url.erb", api, resource, resource_api, operation, parameters, nil, nil).render
31
+ if "GET" == operation["method"].upcase
32
+ queries << CanvasApi::Render.new("./templates/graphql_query.erb", api, resource, resource_api, operation, parameters, nil, nil).render
33
+ else
34
+ mutations << CanvasApi::Render.new("./templates/graphql_mutation.erb", api, resource, resource_api, operation, parameters, nil, nil).render
35
+ end
36
+ end
37
+ end
38
+ resource["models"].map do |_name, model|
39
+ if model["properties"] # Don't generate models without properties
40
+ models << CanvasApi::Render.new("./templates/graphql_model.erb", api, resource, nil, nil, nil, nil, model).render
41
+ end
42
+ end
43
+ # Generate one file of constants for every LMS API
44
+ constants_renderer = CanvasApi::Render.new("./templates/constants.erb", api, resource, nil, nil, nil, constants, nil)
45
+ constants_renderer.save("#{client_app_path}/#{constants_renderer.name}.js")
46
+ end
47
+
48
+ CanvasApi::Render.new("./templates/rb_urls.erb", nil, nil, nil, nil, nil, lms_urls_rb, nil).save("#{project_root}/lib/lms/canvas_urls.rb")
49
+ CanvasApi::Render.new("./templates/js_urls.erb", nil, nil, nil, nil, nil, lms_urls_js, nil).save("#{server_app_path}/lib/canvas/urls.js")
50
+
51
+ # GraphQL - still not complete
52
+ CanvasApi::Render.new("./templates/graphql_types.erb", nil, nil, nil, nil, nil, models.compact, nil).save("#{server_app_path}/lib/canvas/graphql_types.js")
53
+ CanvasApi::Render.new("./templates/graphql_queries.erb", nil, nil, nil, nil, nil, queries, nil).save("#{server_app_path}/lib/canvas/graphql_queries.js")
54
+ CanvasApi::Render.new("./templates/graphql_mutations.erb", nil, nil, nil, nil, nil, mutations, nil).save("#{server_app_path}/lib/canvas/graphql_mutations.js")
55
+ end
56
+
57
+ end
58
+
59
+ end
@@ -0,0 +1,99 @@
1
+ require 'byebug'
2
+ module CanvasApi
3
+
4
+ module GraphQLHelpers
5
+
6
+ def graphql_type(name, property)
7
+ if property["$ref"]
8
+ "#{property['$ref']}, resolve: function(model){ return model.#{name}; }"
9
+ else
10
+ type = property["type"]
11
+ case type
12
+ when "integer", "string", "boolean", "datetime", "number"
13
+ graphql_primitive(type, property["format"])
14
+ when "array"
15
+ begin
16
+ type = if property["items"]["$ref"]
17
+ property["items"]["$ref"]
18
+ else
19
+ graphql_primitive(property["items"]["type"], property["items"]["format"])
20
+ end
21
+ rescue
22
+ puts "Unable to discover list type for '#{name}' ('#{property}'). Defaulting to GraphQLString"
23
+ type = "GraphQLString"
24
+ end
25
+ "new GraphQLList(#{type})"
26
+ when "object"
27
+ puts "Using string type for '#{name}' ('#{property}') of type object."
28
+ "GraphQLString"
29
+ else
30
+ puts "Unable to match '#{name}' requested property '#{property}' to GraphQL Type."
31
+ "GraphQLString"
32
+ end
33
+ end
34
+ end
35
+
36
+ def graphql_primitive(type, format)
37
+ case type
38
+ when "integer"
39
+ "GraphQLInt"
40
+ when "number"
41
+ if format == "float64"
42
+ "GraphQLFloat"
43
+ else
44
+ # TODO many of the LMS types with 'number' don't indicate a type so we have to guess
45
+ # Hopefully that changes. For now we go with float
46
+ "GraphQLFloat"
47
+ end
48
+ when "string"
49
+ "GraphQLString"
50
+ when "boolean"
51
+ "GraphQLBoolean"
52
+ when "datetime"
53
+ "GraphQLDateTime"
54
+ else
55
+ raise "Unable to match requested primitive '#{type}' to GraphQL Type."
56
+ end
57
+ end
58
+
59
+ def graphql_resolve(name, property)
60
+ if property["$ref"]
61
+ "resolve(model){ return model.#{name}; }"
62
+ elsif property["type"] == "array" && property["items"] && property["items"]["$ref"]
63
+ "resolve(model){ return model.#{name}; }"
64
+ end
65
+ "resolve(model){ return model.#{name}; }"
66
+ end
67
+
68
+ def graphql_fields(model, resource_name)
69
+ model["properties"].map do |name, property|
70
+ # HACK. This property doesn't have any metadata. Throw in a couple lines of code
71
+ # specific to this field.
72
+ if name == "created_source" && property == "manual|sis|api"
73
+ "#{name}: new GraphQLEnumType({ name: '#{name}', values: { manual: { value: 'manual' }, sis: { value: 'sis' }, api: { value: 'api' } } })"
74
+ else
75
+
76
+ description = ""
77
+ if property["description"].present? && property["example"].present?
78
+ description << "#{safe_js(property['description'])}. Example: #{safe_js(property['example'])}".gsub("..", "").gsub("\n", " ")
79
+ end
80
+
81
+ if type = graphql_type(name, property)
82
+ resolve = graphql_resolve(name, property)
83
+ resolve = "#{resolve}, " if resolve.present?
84
+ "#{name}: { type: #{type}, #{resolve}description: \"#{description}\" }"
85
+ end
86
+
87
+ end
88
+ end.compact
89
+ end
90
+
91
+ def safe_js(str)
92
+ str = str.join(", ") if str.is_a?(Array)
93
+ str = str.map { |_k, v| v }.join(", ") if str.is_a?(Hash)
94
+ return str unless str.is_a?(String)
95
+ str.gsub('"', "'")
96
+ end
97
+ end
98
+
99
+ end
@@ -0,0 +1,54 @@
1
+ module CanvasApi
2
+ module JsHelpers
3
+
4
+ def js_url_parts(api_url)
5
+ api_url.split(/(\{[a-z_]+\})/).map do |part|
6
+ if part[0] == "{"
7
+ arg = part.gsub(/[\{\}]/, "")
8
+ "args['#{arg}']"
9
+ else
10
+ %{"#{part}"}
11
+ end
12
+ end
13
+ end
14
+
15
+ def js_args(args)
16
+ if args.present?
17
+ "['#{args.join("', '")}']"
18
+ else
19
+ "[]"
20
+ end
21
+ end
22
+
23
+ def parameters_doc(operation)
24
+ if operation["parameters"].present?
25
+ parameters = operation["parameters"].
26
+ reject { |p| p["paramType"] == "path" }.
27
+ map { |p| "#{p['name']}#{p['required'] ? ' (required)' : ''}" }.
28
+ compact
29
+ if parameters.present?
30
+ "\n// const query = {\n// #{parameters.join("\n// ")}\n// }"
31
+ else
32
+ ""
33
+ end
34
+ else
35
+ ""
36
+ end
37
+ end
38
+
39
+ def key_args(args)
40
+ if args.blank?
41
+ ""
42
+ elsif args.length > 1
43
+ "#{nickname}_{#{args.join('}_{')}}"
44
+ else
45
+ "#{nickname}_#{args[0]}"
46
+ end
47
+ end
48
+
49
+ def reducer_key(nickname, args)
50
+ "#{nickname}#{key_args(args)}"
51
+ end
52
+
53
+ end
54
+ end
@@ -0,0 +1,73 @@
1
+ require "canvas_api/graphql_helpers"
2
+ require "canvas_api/js_helpers"
3
+ require "canvas_api/ruby_helpers"
4
+
5
+ module CanvasApi
6
+
7
+ class Render
8
+ include CanvasApi::GraphQLHelpers
9
+ include CanvasApi::JsHelpers
10
+ include CanvasApi::RubyHelpers
11
+ attr_accessor :template, :description, :resource, :api_url, :operation,
12
+ :args, :method, :api, :name, :resource_name, :resource_api,
13
+ :nickname, :notes, :content, :summary, :model, :model_name
14
+
15
+ def initialize(template, api, resource, resource_api, operation, parameters, content, model)
16
+ @template = File.read(File.expand_path(template, __dir__))
17
+ if api
18
+ @api = api
19
+ @name = @api["path"].gsub("/", "").gsub(".json", "")
20
+ @description = @api["description"]
21
+ end
22
+ if resource
23
+ @resource = resource
24
+ @resource_name = resource["resourcePath"].gsub("/", "")
25
+ end
26
+ if resource_api
27
+ @resource_api = resource_api
28
+ @api_url = resource_api["path"].gsub("/v1/", "")
29
+ @args = args(@api_url)
30
+ end
31
+ if operation
32
+ nickname = operation["nickname"]
33
+ nickname = "#{@name}_#{nickname}" if [
34
+ "upload_file",
35
+ "query_by_course",
36
+ "preview_processed_html",
37
+ "create_peer_review_courses",
38
+ "create_peer_review_sections",
39
+ "set_extensions_for_student_quiz_submissions"
40
+ ].include?(nickname)
41
+
42
+ @method = operation["method"]
43
+ @operation = operation
44
+ @nickname = nickname
45
+ @notes = operation["notes"].gsub("\n", "\n// ")
46
+ @summary = operation["summary"]
47
+ end
48
+ if parameters
49
+ @parameters = parameters.map { |p| p.delete("description"); p }
50
+ end
51
+ @content = content
52
+ @model = model
53
+ end
54
+
55
+ def args(api_url)
56
+ api_url.split("/").map do |part|
57
+ if part[0] == "{"
58
+ part.gsub(/[\{\}]/, "")
59
+ end
60
+ end.compact
61
+ end
62
+
63
+ def render
64
+ ERB.new(@template, nil, "-").result(binding).strip
65
+ end
66
+
67
+ def save(file)
68
+ File.write(file, render)
69
+ end
70
+
71
+ end
72
+
73
+ end
@@ -0,0 +1,9 @@
1
+ module CanvasApi
2
+ module RubyHelpers
3
+
4
+ def ruby_api_url(api_url)
5
+ api_url.gsub("{", "#\{")
6
+ end
7
+
8
+ end
9
+ end
@@ -0,0 +1,10 @@
1
+ <% parameters_doc = parameters_doc(operation) -%>
2
+ // <%=@summary%>
3
+ // <%=@notes%>
4
+ //
5
+ // API Docs: https://canvas.instructure.com/doc/api/<%=@resource_name%>.html
6
+ // API Url: <%=@api_url%>
7
+ //
8
+ // Example:<%=parameters_doc%>
9
+ // return canvasRequest(<%=@nickname%>, {<%=@args.join(', ')%>}<%= parameters_doc.present? ? ", query" : ""%>);
10
+ export const <%=@nickname.camelcase(:lower)%> = { type: '<%=@nickname.upcase%>', method: '<%=@method.downcase%>', key: '<%=reducer_key(@nickname, @args)%>', required: <%=js_args(@args)%> };
@@ -0,0 +1,4 @@
1
+ //
2
+ // <%= @description %>
3
+ //
4
+ <%=@content.join("\n\n")%>
@@ -0,0 +1,9 @@
1
+ const <%=@model['id']%> = new GraphQLObjectType({
2
+ name: "<%=@model['id']%>",
3
+ description: "<%=@description%>. API Docs: https://canvas.instructure.com/doc/api/<%=@name%>.html",
4
+ fields() {
5
+ return {
6
+ <%=graphql_fields(@model, @resource_name).join(",\n ")%>
7
+ };
8
+ }
9
+ });
File without changes
@@ -0,0 +1,6 @@
1
+ const Mutuation = new GraphQLObjectType({
2
+ name: 'Canvas Api Mutations',
3
+ fields: {
4
+ <%=@content.join("\n\n")%>
5
+ }
6
+ });
@@ -0,0 +1,7 @@
1
+ const Query = new GraphQLObjectType({
2
+ name: 'Canvas Api Queries',
3
+ description: "Root of the Canvas Api",
4
+ fields: () => ({
5
+ <%=@content.join("\n\n")%>
6
+ })
7
+ });
@@ -0,0 +1,7 @@
1
+ <%=@nickname%>: {
2
+ type: new GraphQLList(Post),
3
+ description: "<%=@description%>",
4
+ resolve: function(context, args) {
5
+ return canvasRequest(context, args, urls.<%=@nickname.upcase%>);
6
+ }
7
+ }
@@ -0,0 +1,100 @@
1
+ import {
2
+ GraphQLBoolean,
3
+ GraphQLFloat,
4
+ GraphQLID,
5
+ GraphQLInt,
6
+ GraphQLList,
7
+ GraphQLNonNull,
8
+ GraphQLObjectType,
9
+ GraphQLSchema,
10
+ GraphQLString
11
+ } from 'graphql';
12
+
13
+ import {
14
+ connectionArgs,
15
+ connectionDefinitions,
16
+ connectionFromArray,
17
+ fromGlobalId,
18
+ globalIdField,
19
+ mutationWithClientMutationId,
20
+ nodeDefinitions,
21
+ connectionFromPromisedArray
22
+ } from 'graphql-relay';
23
+
24
+ import {
25
+ GraphQLLimitedString,
26
+ GraphQLDateTime
27
+ } from 'graphql-custom-types';
28
+
29
+ /**
30
+ * We get the node interface and field from the Relay library.
31
+ *
32
+ * The first method defines the way we resolve an ID to its object.
33
+ * The second defines the way we resolve an object to its GraphQL type.
34
+ */
35
+ var {nodeInterface, nodeField} = nodeDefinitions(
36
+ (globalId) => {
37
+ var {type, id} = fromGlobalId(globalId);
38
+ if (type === 'User') {
39
+ return getUser(id);
40
+ } else if (type === 'Widget') {
41
+ return getWidget(id);
42
+ } else {
43
+ return null;
44
+ }
45
+ },
46
+ (obj) => {
47
+ if (obj instanceof User) {
48
+ return userType;
49
+ } else if (obj instanceof Widget) {
50
+ return widgetType;
51
+ } else {
52
+ return null;
53
+ }
54
+ }
55
+ );
56
+
57
+ <%=@content.join("\n\n")%>
58
+
59
+ const Query = new GraphQLObjectType({
60
+ name: 'Query',
61
+ description: 'Root query',
62
+ fields: () => ({
63
+ node: nodeField,
64
+ peopleRelay: {
65
+ type: PersonConnection,
66
+ description: 'Person connection test',
67
+ args: connectionArgs,
68
+ resolve (root, args) {
69
+ return connectionFromPromisedArray(Db.models.person.findAll(), args);
70
+ }
71
+ },
72
+ person: {
73
+ type: personType,
74
+ resolve (root, args) {
75
+ return Db.models.person.findOne({ where: args });
76
+ }
77
+ },
78
+ people: {
79
+ type: new GraphQLList(personType),
80
+ args: {
81
+ id: {
82
+ type: GraphQLInt
83
+ },
84
+ email: {
85
+ type: GraphQLString
86
+ }
87
+ },
88
+ resolve (root, args) {
89
+ return Db.models.person.findAll({ where: args });
90
+ }
91
+ }
92
+ })
93
+ });
94
+
95
+ const Schema = new GraphQLSchema({
96
+ query: Query,
97
+ //mutation: Mutuation
98
+ });
99
+
100
+ export default Schema;
@@ -0,0 +1 @@
1
+ <%=@nickname.upcase%>: { uri: function(args){return <%=js_url_parts(@api_url).join(' + ')%>}, method: "<%=@method%>", parameters: <%=@parameters.to_json%> }
@@ -0,0 +1,3 @@
1
+ module.exports = {
2
+ <%=@content.join(",\n ")%>
3
+ };
@@ -0,0 +1 @@
1
+ "<%=@nickname.upcase%>" => { uri: ->(<%=@args.map{|a| "#{a}:"}.join(', ')%>) { "<%=ruby_api_url(@api_url)%>" }, method: "<%=@method%>", parameters: <%=@parameters%> }
@@ -0,0 +1,5 @@
1
+ module LMS
2
+ CANVAS_URLs = {
3
+ <%=@content.join(",\n ")%>
4
+ }
5
+ end
data/lib/lms/canvas.rb CHANGED
@@ -282,6 +282,7 @@ module LMS
282
282
  end
283
283
 
284
284
  def self.lms_url(type, params, payload = nil)
285
+ params = params.to_h
285
286
  endpoint = LMS::CANVAS_URLs[type]
286
287
  parameters = endpoint[:parameters]
287
288
 
@@ -311,7 +312,7 @@ module LMS
311
312
  uri_proc = endpoint[:uri]
312
313
  path_parameters = parameters.select { |p| p["paramType"] == "path" }.
313
314
  map { |p| p["name"].to_sym }
314
- args = params.slice(*path_parameters).symbolize_keys
315
+ args = params.slice(*path_parameters)
315
316
  uri = args.blank? ? uri_proc.call : uri_proc.call(**args)
316
317
 
317
318
  # Handle scopes in the url. These API endpoints allow for additional path
data/lib/lms/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module LMS
2
- VERSION = "1.2.9".freeze
2
+ VERSION = "1.3.0".freeze
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lms-api
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.9
4
+ version: 1.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Atomic Jolt
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2017-03-14 00:00:00.000000000 Z
13
+ date: 2017-04-14 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: activesupport
@@ -107,6 +107,24 @@ files:
107
107
  - MIT-LICENSE
108
108
  - README.md
109
109
  - Rakefile
110
+ - lib/canvas_api.rb
111
+ - lib/canvas_api/builder.rb
112
+ - lib/canvas_api/graphql_helpers.rb
113
+ - lib/canvas_api/js_helpers.rb
114
+ - lib/canvas_api/render.rb
115
+ - lib/canvas_api/ruby_helpers.rb
116
+ - lib/canvas_api/templates/constant.erb
117
+ - lib/canvas_api/templates/constants.erb
118
+ - lib/canvas_api/templates/graphql_model.erb
119
+ - lib/canvas_api/templates/graphql_mutation.erb
120
+ - lib/canvas_api/templates/graphql_mutations.erb
121
+ - lib/canvas_api/templates/graphql_queries.erb
122
+ - lib/canvas_api/templates/graphql_query.erb
123
+ - lib/canvas_api/templates/graphql_types.erb
124
+ - lib/canvas_api/templates/js_url.erb
125
+ - lib/canvas_api/templates/js_urls.erb
126
+ - lib/canvas_api/templates/rb_url.erb
127
+ - lib/canvas_api/templates/rb_urls.erb
110
128
  - lib/lms/canvas.rb
111
129
  - lib/lms/canvas_urls.rb
112
130
  - lib/lms/helper_urls.rb
@@ -145,7 +163,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
145
163
  version: '0'
146
164
  requirements: []
147
165
  rubyforge_project:
148
- rubygems_version: 2.4.8
166
+ rubygems_version: 2.6.8
149
167
  signing_key:
150
168
  specification_version: 4
151
169
  summary: Wrapper for the Instructure Canvas API