bright 0.2.0 → 1.2
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 +4 -4
- data/LICENSE.txt +1 -1
- data/bright.gemspec +6 -5
- data/lib/bright/address.rb +6 -8
- data/lib/bright/connection.rb +49 -42
- data/lib/bright/contact.rb +53 -0
- data/lib/bright/cursor_response_collection.rb +45 -0
- data/lib/bright/errors.rb +13 -3
- data/lib/bright/helpers/blank_helper.rb +40 -2
- data/lib/bright/helpers/boolean_parser_helper.rb +13 -0
- data/lib/bright/phone_number.rb +14 -2
- data/lib/bright/response_collection.rb +20 -19
- data/lib/bright/sis_apis/aeries.rb +34 -33
- data/lib/bright/sis_apis/base.rb +31 -5
- data/lib/bright/sis_apis/bright_sis.rb +74 -18
- data/lib/bright/sis_apis/focus.rb +377 -0
- data/lib/bright/sis_apis/infinite_campus.rb +257 -37
- data/lib/bright/sis_apis/power_school.rb +111 -68
- data/lib/bright/sis_apis/skyward.rb +276 -0
- data/lib/bright/sis_apis/tsis.rb +42 -40
- data/lib/bright/student.rb +27 -1
- data/lib/bright/version.rb +1 -1
- data/lib/bright.rb +28 -19
- metadata +33 -15
@@ -5,10 +5,10 @@ module Bright
|
|
5
5
|
class InfiniteCampus < Base
|
6
6
|
|
7
7
|
@@description = "Connects to the Infinite Campus OneRoster API for accessing student information"
|
8
|
-
@@doc_url = "https://content.infinitecampus.com/sis/
|
9
|
-
@@api_version = "
|
8
|
+
@@doc_url = "https://content.infinitecampus.com/sis/latest/documentation/oneroster-api"
|
9
|
+
@@api_version = "1.1"
|
10
10
|
|
11
|
-
attr_accessor :connection_options
|
11
|
+
attr_accessor :connection_options, :schools_cache, :school_years_cache
|
12
12
|
|
13
13
|
DEMOGRAPHICS_CONVERSION = {
|
14
14
|
"americanIndianOrAlaskaNative"=>"American Indian Or Alaska Native",
|
@@ -24,13 +24,24 @@ module Bright
|
|
24
24
|
# {
|
25
25
|
# :client_id => "",
|
26
26
|
# :client_secret => "",
|
27
|
-
# :
|
27
|
+
# :api_version => "", (defaults to @@api_version)
|
28
|
+
# :uri => "",
|
29
|
+
# :token_uri => "" (api_version 1.2 required)
|
28
30
|
# }
|
29
31
|
end
|
30
32
|
|
33
|
+
def api_version
|
34
|
+
Gem::Version.new(self.connection_options.dig(:api_version) || @@api_version)
|
35
|
+
end
|
36
|
+
|
31
37
|
def get_student_by_api_id(api_id, params = {})
|
38
|
+
if api_version <= Gem::Version.new("1.1")
|
39
|
+
params = {:role => "student"}.merge(params)
|
40
|
+
else
|
41
|
+
params = {:roles => "student"}.merge(params)
|
42
|
+
end
|
32
43
|
st_hsh = self.request(:get, "users/#{api_id}", params)
|
33
|
-
Student.new(
|
44
|
+
Student.new(convert_to_user_data(st_hsh["user"])) if st_hsh and st_hsh["user"]
|
34
45
|
end
|
35
46
|
|
36
47
|
def get_student(params = {}, options = {})
|
@@ -38,14 +49,19 @@ module Bright
|
|
38
49
|
end
|
39
50
|
|
40
51
|
def get_students(params = {}, options = {})
|
52
|
+
if api_version <= Gem::Version.new("1.1")
|
53
|
+
params = {:role => "student"}.merge(params)
|
54
|
+
else
|
55
|
+
params = {:roles => "student"}.merge(params)
|
56
|
+
end
|
41
57
|
params[:limit] = params[:limit] || options[:limit] || 100
|
42
|
-
students_response_hash = self.request(:get, 'users', self.
|
58
|
+
students_response_hash = self.request(:get, 'users', self.map_search_params(params))
|
43
59
|
total_results = students_response_hash[:response_headers]["x-total-count"].to_i
|
44
60
|
if students_response_hash and students_response_hash["users"]
|
45
61
|
students_hash = [students_response_hash["users"]].flatten
|
46
62
|
|
47
63
|
students = students_hash.compact.collect {|st_hsh|
|
48
|
-
Student.new(
|
64
|
+
Student.new(convert_to_user_data(st_hsh))
|
49
65
|
}
|
50
66
|
end
|
51
67
|
if options[:wrap_in_collection] != false
|
@@ -59,7 +75,8 @@ module Bright
|
|
59
75
|
:seed_page => students,
|
60
76
|
:total => total_results,
|
61
77
|
:per_page => params[:limit],
|
62
|
-
:load_more_call => load_more_call
|
78
|
+
:load_more_call => load_more_call,
|
79
|
+
:no_threads => options[:no_threads]
|
63
80
|
})
|
64
81
|
else
|
65
82
|
students
|
@@ -74,8 +91,48 @@ module Bright
|
|
74
91
|
raise NotImplementedError
|
75
92
|
end
|
76
93
|
|
77
|
-
def
|
78
|
-
|
94
|
+
def get_school_by_api_id(api_id, params = {})
|
95
|
+
sc_hsh = self.request(:get, "schools/#{api_id}", params)
|
96
|
+
School.new(convert_to_school_data(sc_hsh["org"])) if sc_hsh and sc_hsh["org"]
|
97
|
+
end
|
98
|
+
|
99
|
+
def get_school(params = {}, options = {})
|
100
|
+
self.get_schools(params, options.merge(:limit => 1, :wrap_in_collection => false)).first
|
101
|
+
end
|
102
|
+
|
103
|
+
def get_schools(params = {}, options = {})
|
104
|
+
params[:limit] = params[:limit] || options[:limit] || 100
|
105
|
+
schools_response_hash = self.request(:get, 'schools', self.map_school_search_params(params))
|
106
|
+
total_results = schools_response_hash[:response_headers]["x-total-count"].to_i
|
107
|
+
if schools_response_hash and schools_response_hash["orgs"]
|
108
|
+
schools_hash = [schools_response_hash["orgs"]].flatten
|
109
|
+
|
110
|
+
schools = schools_hash.compact.collect {|sc_hsh|
|
111
|
+
School.new(convert_to_school_data(sc_hsh))
|
112
|
+
}
|
113
|
+
end
|
114
|
+
if options[:wrap_in_collection] != false
|
115
|
+
api = self
|
116
|
+
load_more_call = proc { |page|
|
117
|
+
# pages start at one, so add a page here
|
118
|
+
params[:offset] = (params[:limit].to_i * page)
|
119
|
+
api.get_schools(params, {:wrap_in_collection => false})
|
120
|
+
}
|
121
|
+
ResponseCollection.new({
|
122
|
+
:seed_page => schools,
|
123
|
+
:total => total_results,
|
124
|
+
:per_page => params[:limit],
|
125
|
+
:load_more_call => load_more_call,
|
126
|
+
:no_threads => options[:no_threads]
|
127
|
+
})
|
128
|
+
else
|
129
|
+
schools
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def get_contact_by_api_id(api_id, params ={})
|
134
|
+
contact_hsh = self.request(:get, "users/#{api_id}", params)
|
135
|
+
Contact.new(convert_to_user_data(contact_hsh["user"], bright_type: "Contact")) if contact_hsh and contact_hsh["user"]
|
79
136
|
end
|
80
137
|
|
81
138
|
def request(method, path, params = {})
|
@@ -87,10 +144,12 @@ module Bright
|
|
87
144
|
else
|
88
145
|
body = JSON.dump(params)
|
89
146
|
end
|
90
|
-
headers = self.headers_for_auth(uri)
|
91
147
|
|
92
|
-
|
93
|
-
|
148
|
+
response = connection_retry_wrapper {
|
149
|
+
connection = Bright::Connection.new(uri)
|
150
|
+
headers = self.headers_for_auth(uri)
|
151
|
+
connection.request(method, body, headers)
|
152
|
+
}
|
94
153
|
|
95
154
|
if !response.error?
|
96
155
|
response_hash = JSON.parse(response.body)
|
@@ -105,14 +164,53 @@ module Bright
|
|
105
164
|
protected
|
106
165
|
|
107
166
|
def headers_for_auth(uri)
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
167
|
+
case api_version
|
168
|
+
when Gem::Version.new("1.1")
|
169
|
+
site = URI.parse(self.connection_options[:uri])
|
170
|
+
site = "#{site.scheme}://#{site.host}"
|
171
|
+
consumer = OAuth::Consumer.new(self.connection_options[:client_id], self.connection_options[:client_secret], { :site => site, :scheme => :header })
|
172
|
+
options = {:timestamp => Time.now.to_i, :nonce => SecureRandom.uuid}
|
173
|
+
{"Authorization" => consumer.create_signed_request(:get, uri, nil, options)["Authorization"]}
|
174
|
+
when Gem::Version.new("1.2")
|
175
|
+
if self.connection_options[:access_token].nil? or self.connection_options[:access_token_expires] < Time.now
|
176
|
+
self.retrieve_access_token
|
177
|
+
end
|
178
|
+
{
|
179
|
+
"Authorization" => "Bearer #{self.connection_options[:access_token]}",
|
180
|
+
"Accept" => "application/json;charset=UTF-8",
|
181
|
+
"Content-Type" =>"application/json;charset=UTF-8"
|
182
|
+
}
|
183
|
+
end
|
113
184
|
end
|
114
185
|
|
115
|
-
def
|
186
|
+
def retrieve_access_token
|
187
|
+
connection = Bright::Connection.new(self.connection_options[:token_uri])
|
188
|
+
response = connection.request(:post,
|
189
|
+
{
|
190
|
+
"grant_type" => "client_credentials",
|
191
|
+
"username" => self.connection_options[:client_id],
|
192
|
+
"password" => self.connection_options[:client_secret]
|
193
|
+
},
|
194
|
+
self.headers_for_access_token
|
195
|
+
)
|
196
|
+
if !response.error?
|
197
|
+
response_hash = JSON.parse(response.body)
|
198
|
+
end
|
199
|
+
if response_hash["access_token"]
|
200
|
+
self.connection_options[:access_token] = response_hash["access_token"]
|
201
|
+
self.connection_options[:access_token_expires] = (Time.now - 10) + response_hash["expires_in"]
|
202
|
+
end
|
203
|
+
response_hash
|
204
|
+
end
|
205
|
+
|
206
|
+
def headers_for_access_token
|
207
|
+
{
|
208
|
+
"Authorization" => "Basic #{Base64.strict_encode64("#{self.connection_options[:client_id]}:#{self.connection_options[:client_secret]}")}",
|
209
|
+
"Content-Type" => "application/x-www-form-urlencoded;charset=UTF-8"
|
210
|
+
}
|
211
|
+
end
|
212
|
+
|
213
|
+
def map_search_params(params)
|
116
214
|
params = params.dup
|
117
215
|
default_params = {}
|
118
216
|
|
@@ -127,6 +225,10 @@ module Bright
|
|
127
225
|
filter << "email='#{v}'"
|
128
226
|
when "student_id"
|
129
227
|
filter << "identifier='#{v}'"
|
228
|
+
when "last_modified"
|
229
|
+
filter << "dateLastModified>='#{v.to_time.utc.xmlschema}'"
|
230
|
+
when "role"
|
231
|
+
filter << "role='#{v}'"
|
130
232
|
else
|
131
233
|
default_params[k] = v
|
132
234
|
end
|
@@ -137,31 +239,149 @@ module Bright
|
|
137
239
|
default_params.merge(params).reject{|k,v| v.respond_to?(:empty?) ? v.empty? : v.nil?}
|
138
240
|
end
|
139
241
|
|
140
|
-
def
|
141
|
-
|
142
|
-
|
242
|
+
def map_school_search_params(params)
|
243
|
+
params = params.dup
|
244
|
+
default_params = {}
|
245
|
+
filter = []
|
246
|
+
params.each do |k,v|
|
247
|
+
case k.to_s
|
248
|
+
when "number"
|
249
|
+
filter << "identifier='#{v}'"
|
250
|
+
when "last_modified"
|
251
|
+
filter << "dateLastModified>='#{v.to_time.utc.xmlschema}'"
|
252
|
+
else
|
253
|
+
default_params[k] = v
|
254
|
+
end
|
255
|
+
end
|
256
|
+
unless filter.empty?
|
257
|
+
params = {"filter" => filter.join(" AND ")}
|
258
|
+
end
|
259
|
+
default_params.merge(params).reject{|k,v| v.respond_to?(:empty?) ? v.empty? : v.nil?}
|
260
|
+
end
|
143
261
|
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
:
|
148
|
-
:
|
149
|
-
:
|
150
|
-
:last_modified =>
|
262
|
+
def convert_to_school_data(school_params)
|
263
|
+
return {} if school_params.blank?
|
264
|
+
school_data_hsh = {
|
265
|
+
:api_id => school_params["sourcedId"],
|
266
|
+
:name => school_params["name"],
|
267
|
+
:number => school_params["identifier"],
|
268
|
+
:last_modified => school_params["dateLastModified"]
|
151
269
|
}
|
152
|
-
|
153
|
-
|
270
|
+
return school_data_hsh
|
271
|
+
end
|
272
|
+
|
273
|
+
def convert_to_user_data(user_params, bright_type: "Student")
|
274
|
+
return {} if user_params.blank?
|
275
|
+
user_data_hsh = {
|
276
|
+
:api_id => user_params["sourcedId"],
|
277
|
+
:first_name => user_params["givenName"],
|
278
|
+
:middle_name => user_params["middleName"],
|
279
|
+
:last_name => user_params["familyName"],
|
280
|
+
:last_modified => user_params["dateLastModified"]
|
281
|
+
}.reject{|k,v| v.blank?}
|
282
|
+
unless user_params["identifier"].blank?
|
283
|
+
user_data_hsh[:sis_student_id] = user_params["identifier"]
|
154
284
|
end
|
155
|
-
unless
|
156
|
-
|
285
|
+
unless user_params["userMasterIdentifier"].blank?
|
286
|
+
user_data_hsh[:state_student_id] = user_params["userMasterIdentifier"]
|
287
|
+
end
|
288
|
+
unless user_params["userIds"].blank?
|
289
|
+
if (state_id_hsh = user_params["userIds"].detect{|user_id_hsh| user_id_hsh["type"] == "stateID"})
|
290
|
+
user_data_hsh[:state_student_id] = state_id_hsh["identifier"]
|
291
|
+
end
|
292
|
+
end
|
293
|
+
unless user_params["email"].blank?
|
294
|
+
user_data_hsh[:email_address] = {
|
295
|
+
:email_address => user_params["email"]
|
296
|
+
}
|
297
|
+
end
|
298
|
+
unless user_params["orgs"].blank?
|
299
|
+
if (s = user_params["orgs"].detect{|org| org["href"] =~ /\/schools\//})
|
300
|
+
self.schools_cache ||= {}
|
301
|
+
if (attending_school = self.schools_cache[s["sourcedId"]]).nil?
|
302
|
+
attending_school = self.get_school_by_api_id(s["sourcedId"])
|
303
|
+
self.schools_cache[attending_school.api_id] = attending_school
|
304
|
+
end
|
305
|
+
end
|
306
|
+
if attending_school
|
307
|
+
user_data_hsh[:school] = attending_school
|
308
|
+
end
|
309
|
+
end
|
310
|
+
unless user_params["phone"].blank?
|
311
|
+
user_data_hsh[:phone_numbers] = [{:phone_number => user_params["phone"]}]
|
312
|
+
end
|
313
|
+
unless user_params["sms"].blank?
|
314
|
+
user_data_hsh[:phone_numbers] ||= []
|
315
|
+
user_data_hsh[:phone_numbers] << {:phone_number => user_params["sms"]}
|
316
|
+
end
|
317
|
+
|
318
|
+
#add the demographic information
|
319
|
+
demographics_hash = get_demographic_information(user_data_hsh[:api_id])
|
320
|
+
user_data_hsh.merge!(demographics_hash) unless demographics_hash.blank?
|
321
|
+
|
322
|
+
#if you're a student, build the contacts too
|
323
|
+
if bright_type == "Student" and !user_params["agents"].blank?
|
324
|
+
user_data_hsh[:contacts] = user_params["agents"].collect do |agent_hsh|
|
325
|
+
begin
|
326
|
+
self.get_contact_by_api_id(agent_hsh["sourcedId"])
|
327
|
+
rescue Bright::ResponseError => e
|
328
|
+
if !e.message.to_s.include?("404")
|
329
|
+
raise e
|
330
|
+
end
|
331
|
+
end
|
332
|
+
end.compact
|
333
|
+
user_data_hsh[:grade] = (user_params["grades"] || []).first
|
334
|
+
if !user_data_hsh[:grade].blank?
|
335
|
+
user_data_hsh[:grade_school_year] = get_grade_school_year
|
336
|
+
end
|
337
|
+
end
|
338
|
+
|
339
|
+
return user_data_hsh
|
340
|
+
end
|
341
|
+
|
342
|
+
def get_demographic_information(api_id)
|
343
|
+
demographic_hsh = {}
|
344
|
+
|
345
|
+
begin
|
346
|
+
demographics_params = request(:get, "demographics/#{api_id}")["demographics"]
|
347
|
+
rescue Bright::ResponseError => e
|
348
|
+
if e.message.to_s.include?('404')
|
349
|
+
return demographic_hsh
|
350
|
+
else
|
351
|
+
raise e
|
352
|
+
end
|
353
|
+
end
|
354
|
+
|
355
|
+
unless (bday = demographics_params["birthdate"] || demographics_params["birthDate"]).blank?
|
356
|
+
demographic_hsh[:birth_date] = Date.parse(bday).to_s
|
357
|
+
end
|
358
|
+
unless demographics_params["sex"].to_s[0].blank?
|
359
|
+
demographic_hsh[:gender] = demographics_params["sex"].to_s[0].upcase
|
157
360
|
end
|
158
361
|
DEMOGRAPHICS_CONVERSION.each do |demographics_key, demographics_value|
|
159
|
-
if demographics_params[demographics_key]
|
160
|
-
|
161
|
-
|
362
|
+
if demographics_params[demographics_key].to_bool
|
363
|
+
if demographics_value == "Hispanic Or Latino"
|
364
|
+
demographic_hsh[:hispanic_ethnicity] = true
|
365
|
+
else
|
366
|
+
demographic_hsh[:race] ||= []
|
367
|
+
demographic_hsh[:race] << demographics_value
|
368
|
+
end
|
369
|
+
end
|
370
|
+
end
|
371
|
+
return demographic_hsh
|
372
|
+
end
|
373
|
+
|
374
|
+
def get_grade_school_year(date = Date.today)
|
375
|
+
#return the school year of a specific date
|
376
|
+
self.school_years_cache ||= {}
|
377
|
+
if self.school_years_cache[date].nil?
|
378
|
+
academic_periods_params = self.request(:get, "academicSessions", {"filter" => "startDate<='#{date.to_s}' AND endDate>='#{date.to_s}' AND status='active'"})["academicSessions"]
|
379
|
+
school_years = academic_periods_params.map{|ap| ap["schoolYear"]}.uniq
|
380
|
+
if school_years.size == 1
|
381
|
+
self.school_years_cache[date] = school_years.first
|
162
382
|
end
|
163
383
|
end
|
164
|
-
return
|
384
|
+
return self.school_years_cache[date]
|
165
385
|
end
|
166
386
|
|
167
387
|
end
|