lms-api 1.21.0 → 1.24.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -6,10 +6,14 @@ module CanvasApi
6
6
  CONFLICTING_NAMES = ["end", "context", "next", "module"]
7
7
 
8
8
  def graphql_type(name, property, return_type = false, model = nil, input_type = false)
9
- if property["$ref"]
9
+ if property["nickname"] == "get_bulk_user_progress"
10
+ # custom type specific to the get_bulk_user_progress endpoint. The type from canvas is void but
11
+ # we have to use a custom type in order to return data.
12
+ "LMSGraphQL::Types::CanvasBespoke::CanvasModuleUser"
13
+ elsif property["$ref"]
10
14
  canvas_name(property['$ref'], input_type)
11
15
  elsif property["allowableValues"]
12
- enum_class_name(model, name)
16
+ enum_class_name(model, name, input_type)
13
17
  else
14
18
  type = property["type"].downcase
15
19
  case type
@@ -28,7 +32,7 @@ module CanvasApi
28
32
  elsif property["items"]["$ref"] == "[String]"
29
33
  "[String]"
30
34
  elsif property["items"]["$ref"] == "DateTime" || property["items"]["$ref"] == "Date"
31
- "[LMSGraphQL::Types::DateTimeType]"
35
+ "[GraphQL::Types::ISO8601DateTime]"
32
36
  elsif property["items"]["$ref"]
33
37
  # HACK on https://canvas.instructure.com/doc/api/submissions.json
34
38
  # the ref value is set to a full sentence rather than a
@@ -41,26 +45,36 @@ module CanvasApi
41
45
  "[#{canvas_name(property["items"]["$ref"], input_type)}]"
42
46
  end
43
47
  else
44
- graphql_primitive(name, property["items"]["type"].downcase, property["items"]["format"])
48
+ array_type = graphql_primitive(name, property["items"]["type"].downcase, property["items"]["format"])
49
+ array_type = "[#{array_type}]" if array_type != "[ID]"
50
+ array_type
45
51
  end
46
52
  rescue
47
53
  puts "Unable to discover list type for '#{name}' ('#{property}'). Defaulting to String"
48
- type = "String"
54
+ type = "[String]"
49
55
  end
50
56
  type
51
57
  when "object"
52
58
  puts "Using string type for '#{name}' ('#{property}') of type object."
53
59
  "String"
54
60
  else
55
- if property["type"] == "TermsOfService"
61
+ if property["type"] == "array of outcome ids"
62
+ "[String]"
63
+ elsif property["type"] == "TermsOfService"
56
64
  # HACK There's no TermsOfService object so we return a string
57
65
  "String"
58
66
  elsif property["type"] == "list of content items"
59
67
  # HACK There's no list of content items object so we return an array of string
60
68
  "[String]"
69
+ elsif property["type"] == "uuid"
70
+ # uuid is a string
71
+ "String"
61
72
  elsif property["type"].include?('{ "unread_count": "integer" }')
62
73
  # HACK TODO this should probably be a different type.
63
74
  "Int"
75
+ elsif property["type"].include?('{ "count": "integer" }')
76
+ # HACK TODO this should probably be a different type.
77
+ "Int"
64
78
  elsif return_type
65
79
  canvas_name(property["type"], input_type)
66
80
  else
@@ -74,6 +88,12 @@ module CanvasApi
74
88
  def canvas_name(type, input_type = false)
75
89
  # Remove chars and fix spelling errors
76
90
  name = type.split('|').first.strip.gsub(" ", "_").singularize.gsub("MediaTrackk", "MediaTrack")
91
+
92
+ # Handle comment in type
93
+ if name.include?("BlackoutDate_The_result_(which_should_match_the_input_with_maybe_some_different_IDs).")
94
+ name = "BlackoutDate"
95
+ end
96
+
77
97
  "LMSGraphQL::Types::Canvas::Canvas#{name}#{input_type ? 'Input' : ''}"
78
98
  end
79
99
 
@@ -96,19 +116,19 @@ module CanvasApi
96
116
  when "boolean"
97
117
  "Boolean"
98
118
  when "datetime"
99
- "LMSGraphQL::Types::DateTimeType"
119
+ "GraphQL::Types::ISO8601DateTime"
100
120
  when "date"
101
- "LMSGraphQL::Types::DateTimeType"
121
+ "GraphQL::Types::ISO8601DateTime"
102
122
  else
103
123
  raise "Unable to match requested primitive '#{type}' to GraphQL Type."
104
124
  end
105
125
  end
106
126
 
107
- def enum_class_name(model, field_name)
108
- "#{model['id'].classify}#{field_name.classify}Enum"
127
+ def enum_class_name(model, field_name, input_type)
128
+ "#{model['id'].classify}#{input_type ? 'Input' : ''}#{field_name.classify}Enum"
109
129
  end
110
130
 
111
- def graphql_field_enums(model)
131
+ def graphql_field_enums(model, input_type = false)
112
132
  return unless model["properties"]
113
133
  enums = model["properties"].map do |name, property|
114
134
  if property["allowableValues"]
@@ -116,7 +136,7 @@ module CanvasApi
116
136
  "value \"#{value}\""
117
137
  end.join("\n ")
118
138
  <<-CODE
119
- class #{enum_class_name(model, name)} < ::GraphQL::Schema::Enum
139
+ class #{enum_class_name(model, name, input_type)} < ::GraphQL::Schema::Enum
120
140
  #{values}
121
141
  end
122
142
  CODE
@@ -190,7 +210,7 @@ field :#{name.underscore}, #{type}, "#{description}", resolver_method: :resolve_
190
210
  end
191
211
 
192
212
  def is_basic_type(type)
193
- ["Int", "String", "Boolean", "LMSGraphQL::Types::DateTimeType", "Float", "ID"].include?(type)
213
+ ["Int", "String", "Boolean", "GraphQL::Types::ISO8601DateTime", "Float", "ID"].include?(type)
194
214
  end
195
215
 
196
216
  def no_brackets_period(str)
@@ -198,12 +218,18 @@ field :#{name.underscore}, #{type}, "#{description}", resolver_method: :resolve_
198
218
  end
199
219
 
200
220
  def make_file_name(str)
201
- str.underscore.split("/").last.split("|").first.gsub("canvas_", "").gsub(" ", "_").strip.singularize
221
+ str.underscore.split("/").last.split("|").first.gsub(/^canvas_?/, "").gsub(" ", "_").strip.singularize
222
+ end
223
+
224
+ def make_bespoke_file_name(str)
225
+ str.underscore.split("/").last.split("|").first.gsub(" ", "_").strip.singularize
202
226
  end
203
227
 
204
228
  def require_from_operation(operation)
205
229
  type = no_brackets_period(type_from_operation(@operation))
206
- if !is_basic_type(type)
230
+ if type.include?("CanvasBespoke")
231
+ "require_relative \"../../types/canvas_bespoke/#{make_bespoke_file_name(type)}\""
232
+ elsif !is_basic_type(type)
207
233
  "require_relative \"../../types/canvas/#{make_file_name(type)}\""
208
234
  end
209
235
  end
@@ -230,22 +256,6 @@ field :#{name.underscore}, #{type}, "#{description}", resolver_method: :resolve_
230
256
  str.gsub('"', "'")
231
257
  end
232
258
 
233
- def nested_arg(str)
234
- # TODO/HACK we are replacing values from the string here to get things to work for now.
235
- # However, removing these symbols means that the methods that use the arguments
236
- # generated herein will have bugs and be unusable.
237
- str.gsub("[", "_").
238
- gsub("]", "").
239
- gsub("*", "star").
240
- gsub("<", "_").
241
- gsub(">", "_").
242
- gsub("`", "").
243
- gsub("https://canvas.instructure.com/lti/", "").
244
- gsub("https://www.instructure.com/", "").
245
- gsub("https://purl.imsglobal.org/spec/lti/claim/", "").
246
- gsub(".", "")
247
- end
248
-
249
259
  def params_as_string(parameters, paramTypes)
250
260
  filtered = parameters.select{ |p| paramTypes.include?(p["paramType"]) }
251
261
  if filtered && !filtered.empty?
@@ -1,20 +1,26 @@
1
+ require "canvas_api/helpers"
1
2
  require "canvas_api/js_graphql_helpers"
2
3
  require "canvas_api/js_helpers"
3
4
  require "canvas_api/ruby_helpers"
4
5
  require "canvas_api/rb_graphql_helpers"
6
+ require "canvas_api/go_helpers"
5
7
  require "byebug"
8
+
6
9
  module CanvasApi
7
10
 
8
11
  class Render
12
+ include CanvasApi
9
13
  include CanvasApi::GraphQLHelpers
10
14
  include CanvasApi::JsHelpers
11
15
  include CanvasApi::RubyHelpers
16
+ include CanvasApi::GoHelpers
12
17
  attr_accessor :template, :description, :resource, :api_url, :operation,
13
18
  :args, :method, :api, :name, :resource_name, :resource_api,
14
19
  :nickname, :notes, :content, :summary, :model, :model_name
15
20
 
16
- def initialize(template, api, resource, resource_api, operation, parameters, content, model)
21
+ def initialize(template, api, resource, resource_api, operation, parameters, content, model, api_path)
17
22
  @template = File.read(File.expand_path(template, __dir__))
23
+ @api_path = api_path
18
24
  if api
19
25
  @api = api
20
26
  @name = @api["path"].gsub("/", "").gsub(".json", "")
@@ -31,6 +37,11 @@ module CanvasApi
31
37
  end
32
38
  if operation
33
39
  nickname = operation["nickname"]
40
+
41
+ if nickname == "save_enabled_account_calendars_creates_and_updates_enabled_account_calendars_and_mark_feature_as_seen_user_preferences_argument_mark_feature_as_seen_optional_boolean_flag_to_mark_account_calendars_feature_as_seen_argument_enabled_account_calendars_optional_array_array_of_account_ids_to_remember_in_calendars_list_of_user_curl_https_canvas_api_v_calendar_events_save_enabled_account_calendars_x_post_f_mark_feature_as_seen_true_f_enabled_account_calendars_f_enabled_account_calendars_h_authorization_bearer_token"
42
+ nickname = "save_enabled_account_calendars"
43
+ end
44
+
34
45
  nickname = "#{@name}_#{nickname}" if [
35
46
  "upload_file",
36
47
  "query_by_course",
@@ -47,7 +58,12 @@ module CanvasApi
47
58
  @summary = operation["summary"]
48
59
  end
49
60
  if parameters
50
- @parameters = parameters.map { |p| p.delete("description"); p }
61
+ # Keep a copy of full parameters for code that will include the description
62
+ @full_parameters = Marshal.load Marshal::dump(parameters)
63
+ # Strip description from parameters so that canvas_urls.rb
64
+ # doesn't error out on bad chars in the descriptions
65
+ tmp_params = Marshal.load Marshal::dump(parameters)
66
+ @parameters = tmp_params.map { |p| p.delete("description"); p }
51
67
  end
52
68
  @content = content
53
69
  @model = model
@@ -0,0 +1,241 @@
1
+ <% string_utils_required = @parameters.any?{|p| p["enum"] && !is_x_param?(p["name"]) } -%>
2
+ <% errors_required = @parameters.any?{|p| p["enum"] || is_required_field(p) }-%>
3
+ <% query_params = go_query_params(@parameters) -%>
4
+ <% path_params = go_path_params(@parameters) -%>
5
+ <% form_params = go_form_params(@parameters) -%>
6
+ <% return_type = go_return_type(operation) -%>
7
+ package requests
8
+
9
+ import (
10
+ <% if return_type && return_type != "bool" && return_type != "string" || @nickname == "assign_unassigned_members" || form_params -%>
11
+ "encoding/json"
12
+ <% end -%>
13
+ <% if path_params || errors_required -%>
14
+ "fmt"
15
+ <% end -%>
16
+ <% if !!return_type -%>
17
+ "io/ioutil"
18
+ <% end -%>
19
+ <% if is_paged?(@operation) -%>
20
+ "net/http"
21
+ <% end -%>
22
+ "net/url"
23
+ <% if return_type == "int64" -%>
24
+ "strconv"
25
+ <% end -%>
26
+ <% if errors_required || path_params -%>
27
+ "strings"
28
+ <% end -%>
29
+ <% if time_required?(@parameters) -%>
30
+ "time"
31
+ <% end -%>
32
+
33
+ <% if query_params || go_form_params(@parameters) -%>
34
+ "github.com/google/go-querystring/query"
35
+ <% end -%>
36
+
37
+ "github.com/atomicjolt/canvasapi"
38
+ <% if go_require_models(@parameters, @nickname, return_type)-%>
39
+ "github.com/atomicjolt/canvasapi/models"
40
+ <% end -%>
41
+ <% if string_utils_required -%>
42
+ "github.com/atomicjolt/string_utils"
43
+ <% end -%>
44
+ )
45
+
46
+ // <%= go_name(@nickname) %> <%= @notes %>
47
+ // https://canvas.instructure.com/doc/api<%=@api_path.gsub(".json", ".html")%>
48
+ <% if params = go_path_params(@full_parameters)-%>
49
+ //
50
+ // Path Parameters:
51
+ // <%= params.map { |p| go_parameter_doc(p) }.join("\n// ") %>
52
+ <% end -%>
53
+ <% if params = go_query_params(@full_parameters) -%>
54
+ //
55
+ // Query Parameters:
56
+ // <%= params.map { |p| go_parameter_doc(p) }.join("\n// ") %>
57
+ <% end -%>
58
+ <% if params = go_form_params(@full_parameters) -%>
59
+ //
60
+ // Form Parameters:
61
+ // <%= params.map { |p| go_parameter_doc(p) }.join("\n// ") %>
62
+ <% end -%>
63
+ //
64
+ type <%= struct_name(@nickname) %> struct {
65
+ <% if params = go_path_params(@parameters) -%>
66
+ Path struct {
67
+ <%= go_struct_fields(@nickname, params) %>
68
+ } `json:"path"`
69
+ <% end -%>
70
+
71
+ <% if query_params -%>
72
+ Query struct {
73
+ <%= go_struct_fields(@nickname, query_params) %>
74
+ } `json:"query"`
75
+ <% end -%>
76
+
77
+ <% if form_params -%>
78
+ Form struct {
79
+ <%= go_struct_fields(@nickname, form_params) %>
80
+ } `json:"form"`
81
+ <% end -%>
82
+ }
83
+
84
+ func (t *<%= struct_name(@nickname) %>) GetMethod() string {
85
+ return "<%=@method%>"
86
+ }
87
+
88
+ func (t *<%= struct_name(@nickname) %>) GetURLPath() string {
89
+ <% if params = go_path_params(@parameters) -%>
90
+ path := "<%= go_api_url %>"
91
+ <% params.each do |p| -%>
92
+ path = strings.ReplaceAll(path, "{<%=p["name"]%>}", fmt.Sprintf("%v", t.Path.<%=go_name(p["name"])%>))
93
+ <% end -%>
94
+ return path
95
+ <% else -%>
96
+ return ""
97
+ <% end -%>
98
+ }
99
+
100
+ func (t *<%= struct_name(@nickname) %>) GetQuery()(string, error) {
101
+ <% if query_params -%>
102
+ v, err := query.Values(t.Query)
103
+ if err != nil {
104
+ return "", err
105
+ }
106
+ return v.Encode(), nil
107
+ <% else -%>
108
+ return "", nil
109
+ <% end -%>
110
+ }
111
+
112
+ func (t *<%= struct_name(@nickname) %>) GetBody() (url.Values, error) {
113
+ <% if form_params -%>
114
+ return query.Values(t.Form)
115
+ <% else -%>
116
+ return nil, nil
117
+ <% end -%>
118
+ }
119
+
120
+ func (t *<%= struct_name(@nickname) %>) GetJSON() ([]byte, error) {
121
+ <% if form_params -%>
122
+ j, err := json.Marshal(t.Form)
123
+ if err != nil {
124
+ return nil, nil
125
+ }
126
+ return j, nil
127
+ <% else -%>
128
+ return nil, nil
129
+ <% end -%>
130
+ }
131
+
132
+ func (t *<%= struct_name(@nickname) %>) HasErrors() error {
133
+ <% if errors_required -%>
134
+ errs := []string{}
135
+ <% end -%>
136
+ <% @parameters.each do |p| -%>
137
+ <% type = go_type(p["name"], p) -%>
138
+ <% if is_required_field(p) -%>
139
+ <% if type == "time.Time" -%>
140
+ if t.<%=go_param_path(p)%>.IsZero() {
141
+ errs = append(errs, "'<%=go_param_path(p)%>' is required")
142
+ }
143
+ <% else -%>
144
+ if t.<%=go_param_path(p)%> == <%=go_param_empty_value(p)%> {
145
+ errs = append(errs, "'<%=go_param_path(p)%>' is required")
146
+ }
147
+ <% end -%>
148
+ <% end -%>
149
+ <% if p["enum"] && ["string", "[]string"].include?(type) -%>
150
+ <% if p["type"] == "array" || type == "[]string" -%>
151
+ for _, v := range t.<%=go_param_path(p)%> {
152
+ if v != "" && !string_utils.Include([]string{"<%= p["enum"].join("\", \"") %>"}, v) {
153
+ errs = append(errs, "<%=go_name(p["name"])%> must be one of <%= p["enum"].join(", ") %>")
154
+ }
155
+ }
156
+ <% elsif !is_x_param?(p["name"]) -%>
157
+ if t.<%=go_param_path(p)%> != "" && !string_utils.Include([]string{"<%= p["enum"].join("\", \"") %>"}, t.<%=go_param_path(p)%>) {
158
+ errs = append(errs, "<%=go_name(p["name"])%> must be one of <%= p["enum"].join(", ") %>")
159
+ }
160
+ <% end -%>
161
+ <% end -%>
162
+ <% end -%>
163
+ <% if errors_required -%>
164
+ if len(errs) > 0 {
165
+ return fmt.Errorf(strings.Join(errs, ", "))
166
+ }
167
+ <% end -%>
168
+ return nil
169
+ }
170
+
171
+ func (t *<%= struct_name(@nickname) %>) Do(c *canvasapi.Canvas<%=next_param(@operation)%>) <%=go_do_return_value(@operation, @nickname)%> {
172
+ <% ret_type = go_return_type(@operation, true) -%>
173
+ <% if ret_type -%>
174
+ <% if is_paged?(@operation) -%>
175
+ var err error
176
+ var response *http.Response
177
+ if next != nil {
178
+ response, err = c.Send(next, t.GetMethod(), nil)
179
+ } else {
180
+ response, err = c.SendRequest(t)
181
+ }
182
+
183
+ if err != nil {
184
+ return nil, nil, err
185
+ }
186
+ <% else -%>
187
+ response, err := c.SendRequest(t)
188
+ <% end -%>
189
+ <% else -%>
190
+ _, err := c.SendRequest(t)
191
+ <% end -%>
192
+ if err != nil {
193
+ <%= go_do_return_statement(operation, @nickname) %>
194
+ }
195
+
196
+ <% if ret_type -%>
197
+ body, err := ioutil.ReadAll(response.Body)
198
+ response.Body.Close()
199
+ if err != nil {
200
+ <%= go_do_return_statement(operation, @nickname) %>
201
+ }
202
+ <% if @nickname == "assign_unassigned_members" -%>
203
+ groupMembership := models.GroupMembership{}
204
+ progress := models.Progress{}
205
+ if t.Form.Sync {
206
+ err = json.Unmarshal(body, &groupMembership)
207
+ if err != nil {
208
+ return nil, nil, err
209
+ }
210
+ } else {
211
+ err = json.Unmarshal(body, &progress)
212
+ if err != nil {
213
+ return nil, nil, err
214
+ }
215
+ }
216
+ <% elsif ret_type == "bool" -%>
217
+ ret := string(body) == "true"
218
+ <% elsif ret_type == "string" -%>
219
+ ret := string(body)
220
+ <% elsif ret_type == "int64" -%>
221
+ ret := strconv.ParseInt(string(body), 10, 64)
222
+ <% else -%>
223
+ ret := <%=ret_type%>
224
+ err = json.Unmarshal(body, &ret)
225
+ if err != nil {
226
+ <%= go_do_return_statement(operation, @nickname) %>
227
+ }
228
+ <% end -%>
229
+ <% end -%>
230
+
231
+ <% if @operation["type"] == "array" -%>
232
+ pagedResource, err := canvasapi.ExtractPagedResource(response.Header)
233
+ if err != nil {
234
+ return nil, nil, err
235
+ }
236
+ <% end -%>
237
+
238
+ <%= go_do_final_return_statement(operation, @nickname) %>
239
+ }
240
+
241
+ <%= go_render_child_structs %>
@@ -0,0 +1,44 @@
1
+ <%
2
+ fields, time_required = struct_fields(@model, @resource_name)
3
+ field_validations = go_field_validation(@model)
4
+ -%>
5
+ package models
6
+
7
+ <% if time_required || field_validations -%>
8
+ import (
9
+ <% if time_required -%>
10
+ "time"
11
+ <% end -%>
12
+ <% if (field_validations && field_validations.length > 0) -%>
13
+ "fmt"
14
+
15
+ "github.com/atomicjolt/string_utils"
16
+ <% end -%>
17
+ )
18
+ <% end -%>
19
+
20
+ type <%=struct_name(@model['id'])%> struct {
21
+ <%=fields&.join("\n ")%>
22
+ }
23
+
24
+ func (t *<%=struct_name(@model['id'])%>) HasErrors() error {
25
+ <% if field_validations && field_validations.length > 0 -%>
26
+ var s []string
27
+ errs := []string{}
28
+ <%field_validations.each do |name, prop|-%>
29
+ s = []string{<%=prop[:values].join(",")%>}
30
+ <% if prop[:type] == "array" %>
31
+ for _, v := range t.<%=go_name(name)%> {
32
+ if v != "" && !string_utils.Include(s, v) {
33
+ errs = append(errs, fmt.Sprintf("expected '<%=go_name(name)%>' to be one of %v", s))
34
+ }
35
+ }
36
+ <% else -%>
37
+ if t.<%=go_name(name)%> != "" && !string_utils.Include(s, t.<%=go_name(name)%>) {
38
+ errs = append(errs, fmt.Sprintf("expected '<%=go_name(name)%>' to be one of %v", s))
39
+ }
40
+ <% end -%>
41
+ <% end -%>
42
+ <% end -%>
43
+ return nil
44
+ }
@@ -1,3 +1,3 @@
1
1
  field :<%= @nickname %>,
2
- resolver: LMSGraphQL::Resolvers::Canvas::<%= @nickname.classify %>,
3
- description: "<%= @summary %>. <%= @notes.gsub(/\n+/, " ").gsub("//", " ").gsub('"', "'") %>"
2
+ resolver: LMSGraphQL::Resolvers::Canvas::<%= graphql_resolver_class(@nickname) %>,
3
+ description: "<%= @summary %>. <%= @notes.gsub(/\n+/, " ").gsub("//", " ").gsub('"', "'") %>"
@@ -5,9 +5,9 @@ module LMSGraphQL
5
5
  module Types
6
6
  module Canvas
7
7
  class Canvas<%=@model['id'].singularize%>Input < BaseInputObject
8
- <%=graphql_field_enums(@model)-%>
8
+ <%=graphql_field_enums(@model, true)-%>
9
9
  description "<%=@description%>. API Docs: https://canvas.instructure.com/doc/api/<%=@name%>.html"
10
- <%=graphql_fields(@model, @resource_name, true, true).join(" ")%>
10
+ <%=graphql_fields(@model, @resource_name, true, true).join(" ")%>
11
11
  end
12
12
  end
13
13
  end
@@ -4,7 +4,7 @@ require_relative "../canvas_base_resolver"
4
4
  module LMSGraphQL
5
5
  module Resolvers
6
6
  module Canvas
7
- class <%= @nickname.classify %> < CanvasBaseResolver
7
+ class <%= graphql_resolver_class(@nickname) %> < CanvasBaseResolver
8
8
  type <%= type_from_operation(operation) %>, null: false
9
9
  <% if operation["type"] == "array"
10
10
  %> argument :get_all, Boolean, required: false